Как мне разделить классы Case, заполненные параметрами в Scala

Я новичок в 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, если они действительно носят штаны ... с карманами и есть ли у них вообще деньги?


person Adam Fraser    schedule 20.01.2012    source источник


Ответы (4)


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

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

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

person ziggystar    schedule 20.01.2012
comment
Отлично, спасибо! Подход на основе понимания - это именно то, что я пытался сделать, но структура, с которой я работаю, не так чиста, как пример, который я привел выше. По крайней мере, это подтверждает, что я на правильном пути. - person Adam Fraser; 20.01.2012

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

}
person Channing Walton    schedule 05.06.2013
comment
Отличный ответ, но большой камень преткновения при установке этих вложенных значений: что, если какие-то значения равны None? PLens в вашем примере позволит назначить blub: Option[String] только в том случае, если blub уже имеет значение Some. Я обнаружил, что монадические переходы между состояниями могут инициализировать элементы верхнего уровня, но я не понимаю, как это будет сделано для тех, кто находится ниже. - person Michael Ahlers; 21.10.2016
comment
Хм да. Я обновил ответ на установку Bar на Foo, но я вижу проблему с установкой значения blub в Bar - мне нужно подумать над этим. - person Channing Walton; 21.10.2016
comment
Беспорядочные переходы между состояниями с использованием PLens экземпляров - лучшее, что я мог сделать, инициализируя свойства по мере необходимости ( что имеет смысл, особенно в свете ответа о штанах, который дал @ ben-james). Это кажется мне слишком трудоемким и не масштабируемым, поэтому я полагаю, что должен быть лучший подход. - person Michael Ahlers; 21.10.2016
comment
Подумав об этом, я думаю, что поведение и так правильное. Эта проблема сводится к тому, что линза устанавливает значение для параметра. Если для параметра установлено значение «Нет», настраивать нечего. Однако я также вижу, что для объектива было бы полезно поддерживать установку значения в Option, содержащемся в другом типе, возможно, в качестве сокращения. В его нынешнем виде вам нужно установить параметр в целом. - person Channing Walton; 25.10.2016

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

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

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

Во-первых, некоторый шаблон: определите экземпляр Lens для каждого поля классов case. 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)
person Ben James    schedule 02.04.2012

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

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