2012-01-20 2 views
7

Я очень новичок в Scala, и я все еще пытаюсь привыкнуть к синтаксису и стилю, так что это, вероятно, очень простой вопрос.Как отделить классы Case, заполненные параметрами в 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 ' s PantsPocket, если они действительно носят брюки ... с карманами, а если у них вообще есть деньги?

ответ

8

Большое время для for-comprehensions:

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

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

+0

Awesome, спасибо! Подход к пониманию - это именно то, что я пытался сделать, но структура, с которой я работаю, не такая чистая, как пример, который я дал выше. По крайней мере, это подтверждает, что я на правильном пути. –

2

ziggystar является то, что я хотел бы использовать, но для полноты сравнения с шаблоном также может быть использован, например,

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

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

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

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

Во-первых, некоторые шаблоны: определить экземпляр объектива для каждого поля классов корпуса. A @[email protected] B означает то же, что и Lens[A, B].

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

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

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

val value: Cash @[email protected] 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))))))) 
(!) 0

Теперь, если я не носить любые штаны, мы можем видеть, как попытка изменить значение моей наличности не будет иметь никакого эффекта:

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

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res4: Person = Person(None) 
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))) 

} 
+1

Отличный ответ, но здесь большой камень преткновения при настройке вложенных значений: что, если какие-либо значения являются «Нет»? «PLens» в вашем примере разрешит присвоить 'blub: Option [String]', если 'blub'_already имеет значение' Some'_. Я обнаружил, что монадические переходы состояний могут инициализировать членов верхнего уровня, но я не понимаю, как это будет сделано для тех, кто дальше. –

+1

Хмм, да. Я обновил ответ для настройки бара на Foo, но я вижу проблему установки значения blub в Bar - мне нужно подумать об этом. –

+0

[Целесообразность переходов состояний с использованием экземпляров «PLens»] (https://gist.github.com/michaelahlers/20ec194410f89422847fdd3a71777c69) - это лучшее, что я мог сделать, инициализируя свойства по мере необходимости (что имеет смысл, особенно в свете [ ответ с участием брюк] (http://stackoverflow.com/a/9978488/700420) @ ben-james дал). Это кажется мне слишком трудоемким, и оно не масштабируется, поэтому я предполагаю, что должен быть лучший подход. –