Как мне отделить Case Class, заполненный опциями в Scala - PullRequest
7 голосов
/ 20 января 2012

Я очень новичок в Scala и все еще пытаюсь привыкнуть к синтаксису и стилю, так что это, вероятно, очень простой вопрос.

Я работаю с базой кода, где есть много классов 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"
)

В приведенном выше примере, как бы вы вернули сумму денег в Person Pants Pocket, если они действительно носят брюки ... с карманами и если у них есть деньги вообще?

Ответы [ 4 ]

12 голосов
/ 06 июня 2013

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

}
8 голосов
/ 20 января 2012

Прекрасное время для для понимания :

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

(я не совсем уверен, что вы можете написать последнее выражение, используя символы _, как я).

6 голосов
/ 02 апреля 2012

В вопросе не упоминалось изменение данных, но, когда вам нужно сделать это, вы быстро обнаружите, что в библиотеке Scala нет инструментов, позволяющих сделать это легко (когда данные неизменяемы).Если вы еще этого не испытали, попробуйте написать функцию, которая заменит или изменит value из Cash, хранящегося в Person, используя типы, определенные в вопросе.

Как описано в Tony Morris ' Асимметричные линзы в Scala , линзы являются подходящим решением этой проблемы.

Вот пример того, как мы можем получить доступ и обновить value человека Cash с использованием реализаций Lens и PLens (частичная линза) из ветви scalaz-seven из Скалаза.

Во-первых, несколько шаблонных: определитеЭкземпляр объектива для каждого поля классов дел.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 типах.

Частичные линзы для спасения: они позволяют нам получать доступ и обновлять части структуры, которые могут не существовать , такие как Some значение Option илиhead из List.

Мы можем использовать функцию somePLens из Scalaz 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 наших линз и сэндвич-экземпляров somePLens.Здесь я использовал оператор <=<, псевдоним для 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)
2 голосов
/ 23 января 2012

Ответ ziggystar - это то, что я бы использовал, но для полноты можно использовать сопоставление с образцом, например,

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