How to split case classes populated with options in Scala

I am 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 work with a code base where there are many classes of cases filled with such parameters:

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 above example, how would you like to get the amount of money back in Person Pants Pocket if they really wear pants ... with pockets, and if they even have money

+7
source share
4 answers

Great time to understand :

 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 quite sure that you can write the last expression using _ wildcards, just like me).

+8
source

Scalaz 7 has changed a bit, 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))) } 
+12
source

The question was not about changing the data, but when you need to do this, you will quickly find that the Scala library has no tools to simplify (when the data is immutable). If you have not experienced this yet, try writing a function that replaces or changes the value Cash stored in Person using the types defined in the question.

As described in Tony Morris's Asymmetric Lenses in Scala , lenses are a suitable solution to this problem.

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

First, some pattern: define an instance of the lens 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 cannot compose all these lenses because most fields are wrapped in Option types.

Partial rescue lenses: they allow us to retrieve and update parts of a structure that may not exist, such as Some for Option or head for List .

We can use the somePLens function from Scalaz 7 to create a partial lens that scans each optional field. However, in order to compose a partial lens with one of our conventional lenses, we need to access the equivalent instance of a partial lens for a conventional lens using the partial method that exists on each 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 that looks at the cash held by Person , making up all instances of our partial lenses and somePLens sandwich instances. Here I used the <=< operator, an alias for andThen (which is equivalent to compose when switching operands).

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

Create an instance of Person to play with:

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

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

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

Using a partial lens to change the value:

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

Now, if I have no pants (!), We can see how an attempt to change the value of my money will have no effect:

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

The answer to ziggystar is what I would like to use, but for completeness, you can also use pattern matching, for example

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

All Articles