如何在 Scala 中分解充满选项的案例类

发布于 2024-12-28 05:56:13 字数 399 浏览 1 评论 0原文

我对 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中有多少钱裤子 口袋,如果他们确实穿着有口袋的裤子,并且他们是否有钱?

I'm very new to Scala and I'm still trying to get used to the syntax and style, so this is probably a very simple question.

I'm working with a codebase where there are lots of case classes populated with Options like so:

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

In the example above, how would you go about returning how much money is in a Person's Pants Pocket, if they are indeed wearing pants... with pockets, and if they have any money at all?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

别念他 2025-01-04 05:56:13

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)))

}

Scalaz 7 has changed a little so here is another example:

  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)))

}
看轻我的陪伴 2025-01-04 05:56:13

这是 for-compressiveions 的好时机:

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))

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

A great time for for-comprehensions:

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

Equivalently you can write the following, for which the first code is syntactic sugar (ignoring some subtleties):

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

(I'm not totally sure if you can write the last expression using the _ wildcards, as I did).

橘和柠 2025-01-04 05:56:13

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

正如 Tony Morris 的Scala 中的非对称透镜中所述,透镜是一种合适的这个问题的解决方案。

以下是我们如何使用 LensPLens 访问和更新个人 Cashvalue 的示例(部分镜头)来自 Scalaz 的 scalaz-7 分支的实现。

首先,一些样板文件:为案例类的每个字段定义 Lens 实例。 A @-@ BLens[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 类型中。

部分透镜来救援:它们允许我们访问和更新可能不存在的结构部分,例如某个结构的SomeOption,或Listhead

我们可以使用 Scalaz 7 中的 somePLens 函数来创建查看每个可选字段的部分镜头。然而,为了用我们的常规镜头之一组成部分镜头,我们需要使用每个 Lens< 上存在的 partial 方法来访问常规镜头的等效部分镜头实例。 /代码>。

// @-? 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))

以同样的方式,我们可以通过组合所有镜头的 partial 实例,并将 somePLens< 的实例夹在中间,来创建查看 Person 持有现金的部分镜头。 /代码>。在这里,我使用了 <=< 运算符,它是 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)

The question didn't mention modifying the data, but when you need to do this you quickly find the Scala library doesn't have the tools to make this easy (when the data is immutable). If you haven't experienced this yet, try writing a function which will replace, or modify, the value of the Cash held by a Person, using the types defined in the question.

As described in Tony Morris' Asymmetric Lenses in Scala, lenses are an appropriate solution to this problem.

Here's an example of how we can access and update the value of a person's Cash using the Lens and PLens (partial lens) implementations from the scalaz-seven branch of Scalaz.

First, some boilerplate: define the Lens instance for each field of the case classes. A @-@ B means the same as 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))

We can't compose all of these lenses, however, because most of the fields are wrapped in Option types.

Partial Lenses to the rescue: these allow us to access and update parts of a structure that may not exist, such as the Some value of an Option, or the head of a List.

We can use the somePLens function from Scalaz 7 to create a partial lens viewing each optional field. In order to compose a partial lens with one of our regular lenses, however, we need to access the equivalent partial lens instance for the regular lens, using the partial method that exists on every 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))

In the same way, we can create our partial lens viewing the cash held by a Person by composing all our lenses' partial instances, and sandwiching instances of somePLens. Here, I've used the <=< operator, an alias for andThen (which is equivalent to compose with the operands switched).

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

Creating a Person instance to play with:

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

Using the partial lens to access the value of cash I have:

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

Using the partial lens to modify the value:

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

Now, if I'm not wearing any pants (!), we can see how an attempt to modify the value of my cash will have no effect:

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

scala> someCashValue.mod(_ + ", zero, nada", ben)
res4: Person = Person(None)
雨夜星沙 2025-01-04 05:56:13

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

val someCash: Option[Cash] = person match {
  case Person(Some(Pants(Some(Pocket(Some(cash)))))) => Some(cash)
  case _ => None
}

ziggystar's answer is what I would use, but for completeness, pattern matching can also be used, eg,

val someCash: Option[Cash] = person match {
  case Person(Some(Pants(Some(Pocket(Some(cash)))))) => Some(cash)
  case _ => None
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文