2012-01-20 10 views
7

Jestem bardzo nowy w Scali i wciąż staram się przyzwyczaić do składni i stylu, więc jest to prawdopodobnie bardzo proste pytanie.Jak rozpakować klasy Case wypełnione opcjami w Scali

pracuję w kodzie, gdzie istnieje wiele klas przypadków zaludnionych z opcjami tak:

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

W powyższym przykładzie, w jaki sposób go o powrocie, ile pieniędzy jest w Person ' s PantsPocket, jeśli rzeczywiście noszą spodnie ... z kieszeniami i czy w ogóle mają jakieś pieniądze?

Odpowiedz

8

wspaniały czas dla for-comprehensions:

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

Równoważnie można napisać następujące, dla których pierwszy kod jest cukier syntaktyczny (pomijając pewne subtelności):

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

(nie jestem Całkowicie pewny, czy możesz napisać ostatnie wyrażenie przy użyciu symboli wieloznacznych _, tak jak ja). Odpowiedź

+0

Niesamowite, dzięki! Podejście dla zrozumienia jest właściwie dokładnie tym, co próbowałem zrobić, ale struktura, z którą pracuję, nie jest tak czysta jak przykład, który podałem powyżej. Przynajmniej to potwierdza, że ​​jestem na dobrej drodze. –

2

ziggystar jest to, czego używam, ale pod względem kompletności, pasujący wzorzec może być również używany, np

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

Pytanie nie wspominając modyfikując danych, ale kiedy trzeba zrobić to szybko odkrywasz, że biblioteka Scala nie ma narzędzi, które ułatwiają to zadanie (gdy dane są niezmienne). Jeśli jeszcze tego nie doświadczyłeś, spróbuj wpisać funkcję, która zastąpi lub zmodyfikuje value z posiadaną przez Person, używając typów zdefiniowanych w pytaniu.

Jak opisano w Tony Morris 'Asymmetric Lenses in Scala, soczewki są odpowiednie rozwiązanie tego problemu.

Oto przykład, w jaki sposób możemy uzyskać dostęp i zaktualizować value osoby za Cash przy użyciu obiektywu (częściowe) implementacjach Lens i PLens od scalaz-seven gałęzi Scalaz.

Po pierwsze, niektóre dane podstawowe: zdefiniuj instancję Lens dla każdego pola klas case. A @[email protected] B oznacza to samo co 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)) 

Nie możemy komponować wszystkich tych soczewek, jednak, ponieważ większość z tych dziedzin są owinięte w Option typów.

częściowe Soczewki na ratunek: te pozwalają na dostęp i części Aktualizacja struktury, która może nie istnieć, takich jak wartość Some wystąpienia Option, albo head z List.

Możemy użyć funkcji somePLens z Scalaz 7, aby utworzyć częściowy obiektyw oglądający każde opcjonalne pole.Jednakże, aby skomponować soczewkę częściową z jedną z naszych zwykłych soczewek, musimy uzyskać dostęp do równoważnej instancji soczewki częściowej dla zwykłego obiektywu, przy użyciu metody partial, która istnieje na każdym urządzeniu 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)) 

W ten sam sposób możemy stworzyć naszą częściowy obiektyw oglądania pieniężnych posiadanych przez Person przez komponowanie wszystkie partial wystąpień naszych soczewek i umieszczając wystąpień somePLens. Tutaj użyłem operatora <=<, aliasu dla andThen (co jest równoważne compose z włączonymi operandami).

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

Tworzenie instancji Person grać z:

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

Stosując obiektyw częściowy dostęp do wartości pieniężnych mam:

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

Stosując obiektyw częściowej modyfikacji wartości :

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

Teraz, gdy jestem nie sobie jakieś spodnie, możemy zobaczyć, jak próbą zmodyfikować wartość mojego gotówce będzie miała żadnego wpływu:

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

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res4: Person = Person(None) 
12

Scalaz 7 ma więc trochę zmieniło tutaj jest inny przykład:

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

Doskonała odpowiedź, ale dużym utrudnieniem tutaj przy ustawianiu tych zagnieżdżonych wartości jest: co jeśli dowolne wartości to 'Brak'? "PLens" w twoim przykładzie zezwoli na przypisanie tylko 'blub: Option [String]', jeśli 'blub' _ już ma' Some' value_. Stwierdziłem, że monadyczne przejścia między stanami mogą inicjować członków najwyższego poziomu, ale nie jestem pewien, jak to się stało dla tych, którzy byli na dole. –

+1

hmm, tak. Zaktualizowałem odpowiedź na ustawienie Bar na Foo, ale widzę problem z ustawieniem wartości bluba w Bar - muszę się nad tym zastanowić. –

+0

[Bogotyczność przejść stanów za pomocą instancji 'Plens'ów] (https://gist.github.com/michaelahlers/20ec194410f89422847fdd3a71777c69) jest najlepszą możliwą do zrobienia, inicjującą właściwości w razie potrzeby (co ma sens, zwłaszcza w świetle [ odpowiedź z udziałem spodni] (http://stackoverflow.com/a/9978488/700420) @ ben-james dał). To wydaje mi się zbyt pracochłonne i nie skaluje się, więc wyobrażam sobie, że musi być lepsze podejście. –