如何拆分Scala中填充了Options的Case Classes

Ada*_*ser 7 scala option case-class

我是Scala的新手,我仍然习惯于习惯语法和风格,所以这可能是一个非常简单的问题.

我正在使用一个代码库,其中有很多用Options填充的case类,如下所示:

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

在上面的例子中,你将如何去恢复多少钱是在PersonPants Pocket,如果他们确实穿开裆裤......口袋,如果他们有任何的钱呢?

Cha*_*ton 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)))

}
Run Code Online (Sandbox Code Playgroud)


zig*_*tar 8

理解的好时机:

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

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

val someCash: Option[Cash] = 
   somePerson.pants.flatMap(_.pocket.flatMap(_.cash))
Run Code Online (Sandbox Code Playgroud)

(我不完全确定你是否可以使用_通配符编写最后一个表达式,就像我做的那样).


Ben*_*mes 6

问题没有提到修改数据,但是当你需要这样做时,你很快发现Scala库没有工具来简化这些(当数据是不可变的时).如果你还没有经历过这样的呢,试着写一个函数,它会替换或修改,该valueCash持有一个Person,使用问题定义的类型.

如Tony Morris 在Scala中非对称镜头所述,镜头是解决这个问题的合适解决方案.

以下是我们如何使用Scalaz的scalaz-seven分支和(部分镜头)实现来访问和更新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))
Run Code Online (Sandbox Code Playgroud)

然而,我们无法构成所有这些镜头,因为大多数领域都是包装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))
Run Code Online (Sandbox Code Playgroud)

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

val someCashValue: Person @-? String =
  pants.partial <=< somePLens <=<
  pocket.partial <=< somePLens <=<
  cash.partial <=< somePLens <=<
  value.partial
Run Code Online (Sandbox Code Playgroud)

创建要使用的Person实例:

val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch")))))))
Run Code Online (Sandbox Code Playgroud)

使用部分镜头获取现金的价值我有:

scala> someCashValue.get(ben)
res2: Option[String] = Some(zilch)
Run Code Online (Sandbox Code Playgroud)

使用部分镜头修改值:

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

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

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

scala> someCashValue.mod(_ + ", zero, nada", ben)
res4: Person = Person(None)
Run Code Online (Sandbox Code Playgroud)