7

我对 Scala 很陌生,我仍在努力适应语法和风格,所以这可能是一个非常简单的问题。

我正在使用一个代码库,其中有很多用这样的选项填充的案例类:

case class Person(
  pants: Option[Pants]
)
case class Pants(
  pocket: Option[Pocket]
)
case class Pocket(
  cash: Option[Cash]
)
case class Cash(
  value: String = "zilch"
)

Person在上面的例子中Pants Pocket,如果他们确实穿着裤子......有口袋,如果他们有钱,你将如何退还他们的钱?

4

4 回答 4

12

Scalaz 7 发生了一些变化,所以这里是另一个例子:

  object PartialLensExample extends App {

  import scalaz._
  import Lens._
  import PLens._


  case class Bar(blub: Option[String])
  case class Foo(bar: Option[Bar])

  // normal lenses for getting and setting values
  val fooBarL: Foo @> Option[Bar] = lensg(foo ⇒ bar ⇒ foo.copy(bar = bar), _.bar)
  val barBlubL: Bar @> Option[String] = lensg(bar ⇒ blub ⇒ bar.copy(blub = blub), _.blub)

  // compose the above as 'Partial Lenses', >=> is just an alias for 'andThen'
  val fooBarBlubL: Foo @?> String = fooBarL.partial >=> somePLens >=> barBlubL.partial >=> somePLens

  // try it
  val foo = Foo(Some(Bar(Some("Hi"))))

  println(fooBarBlubL.get(foo)) // Some(Hi)

  println(fooBarBlubL.set(foo, "Bye")) //Foo(Some(Bar(Some(Bye))))

  // setting values
  val foo2 = Foo(None)
  println(fooBarL.set(foo2, Some(Bar(None)))) // Foo(Some(Bar(None)))

}
于 2013-06-05T20:31:19.060 回答
8

理解的好时机:

val someCash: Option[Cash] =
   for( pants  <- somePerson.pants;
        pocket <- pants.pocket;
        cash   <- pocket.cash ) yield cash

等效地,您可以编写以下代码,其中第一个代码是语法糖(忽略一些细微之处):

val someCash: Option[Cash] = 
   somePerson.pants.flatMap(_.pocket.flatMap(_.cash))

(我不完全确定您是否可以_像我一样使用通配符编写最后一个表达式)。

于 2012-01-20T16:05:59.803 回答
6

这个问题没有提到修改数据,但是当你需要这样做时,你很快就会发现 Scala 库没有工具可以使这变得容易(当数据不可变时)。如果您还没有遇到过这种情况,请尝试编写一个函数,该函数将使用问题中定义的类型替换或修改 avalue所持Cash有的。Person

正如 Tony Morris 的Scala 中的非对称透镜中所述,透镜是解决此问题的合适方法。

这是一个示例,说明我们如何使用 Scalaz 的scalaz-7分支中的和(部分镜头)实现来访问和更新value一个人的。CashLensPLens

首先,一些样板:为案例类的每个字段定义 Lens 实例。A @-@ B意思相同Lens[A, B]

val pants: Person @-@ Option[Pants] =
  lensG(_.pants, p => ps => p.copy(pants = ps))

val pocket: Pants @-@ Option[Pocket] =
  lensG(_.pocket, ps => p => ps.copy(pocket = p))

val cash: Pocket @-@ Option[Cash] =
  lensG(_.cash, p => c => p.copy(cash = c))

val value: Cash @-@ String =
  lensG(_.value, c => v => c.copy(value = v))

但是,我们无法组合所有这些镜头,因为大多数字段都包含在Option类型中。

救援的部分镜头:这些允许我们访问和更新结构中可能不存在的部分,例如 a 的SomeOptionheada的值List

我们可以使用somePLensScalaz 7 中的函数来创建查看每个可选字段的部分镜头。然而,为了用我们的一个常规镜头组成一个部分镜头,我们需要使用partial每个Lens.

// @-? is an infix type alias for PLens
val someCash: Pocket @-? Cash = cash.partial andThen somePLens

scala> someCash.get(Pocket(Some(Cash("zilch"))))
res1: Option[Cash] = Some(Cash(zilch))

Person以同样的方式,我们可以创建我们的部分镜头,通过组合我们所有镜头的partial实例来查看 a 持有的现金,并将somePLens. 在这里,我使用了<=<运算符,一个别名 for andThen(相当于compose切换了操作数)。

val someCashValue: Person @-? String =
  pants.partial <=< somePLens <=<
  pocket.partial <=< somePLens <=<
  cash.partial <=< somePLens <=<
  value.partial

创建一个Person实例来玩:

val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch")))))))

使用部分镜头来访问我拥有的现金价值:

scala> someCashValue.get(ben)
res2: Option[String] = Some(zilch)

使用局部镜头修改数值:

scala> someCashValue.mod(_ + ", zero, nada", ben)
res3: Person = Person(Some(Pants(Some(Pocket(Some(Cash(zilch, zero, nada)))))))

现在,如果我没有穿任何裤子(!),我们可以看到修改我的现金价值的尝试是如何无效的:

scala> val ben = Person(None)
ben: Person = Person(None)

scala> someCashValue.mod(_ + ", zero, nada", ben)
res4: Person = Person(None)
于 2012-04-02T14:44:34.827 回答
2

ziggystar 的答案是我会使用的,但为了完整起见,也可以使用模式匹配,例如,

val someCash: Option[Cash] = person match {
  case Person(Some(Pants(Some(Pocket(Some(cash)))))) => Some(cash)
  case _ => None
}
于 2012-01-23T07:22:51.320 回答