I have a structure of nested case classes that I create with default values:
case class Alpha(text: String = "content")
case class Beta(alpha: Alpha = Alpha())
case class Gamma(beta: Option[Beta] = None)
I would like to create the whole thing with default values and then modify the elements in particular that need to be non-default using Monocle.
With isos is easy. I can specify the navigation with composition and then use set to modify the inner element:
object Beta {
val alphaI: Iso[Beta, Alpha] = GenIso[Beta, Alpha]
}
object Alpha {
val textI: Iso[Alpha, String] = GenIso[Alpha, String]
}
(Beta.alphaI composeIso Alpha.textI).set("foo")(Beta()) shouldBe Beta(Alpha("foo"))
Unfortunately with prims it doesn't seem that elegant, as set
/modify
will only modify the inner element if all the options during the navigation are defined (Some(...)
)
object Gamma {
val betaI: Iso[Gamma, Option[Beta]] = GenIso[Gamma, Option[Beta]]
val betaP: Prism[Gamma, Beta] = Prism[Gamma, Beta](_.beta)(a => Gamma(Some(a)))
}
val navigateToText: Prism[Gamma, String] = Gamma.betaP composeIso Beta.alphaI composeIso Alpha.textI
navigateToText.set("foo")(Gamma(None)) shouldBe Gamma(None)
navigateToText.set("foo")(Gamma(Some(Beta()))) shouldBe Gamma(Some(Beta(Alpha("foo"))))
The best thing I've come up with so far is using a composed optic to set to a Some()
each optional element of the path and then the composed optic all the way down to set the element we were interested in to begin with.
(Gamma.betaI.set(Some(Beta())) andThen navigateToText.set("foo")) (Gamma()) shouldBe Gamma(Some(Beta(Alpha("foo"))))
Ideally I'd like each building block of the composition to set the optional values to Some(defaultValue)
if they are originally None
. Obviously the building block would need to be defined including the appropriate default value for the step of the path.
Any suggestions?
Full code including imports: https://github.com/jcaraballo/navigating-fixtures-with-monocle/blob/master/src/test/scala/pr/NavigateIntoOptionSpec.scala
You could use below
from Prism
such as the types match in the optics composition:
import monocle.macros.GenIso
import scalaz.std.option._
case class Alpha(text: String = "content")
case class Beta(alpha: Alpha = Alpha())
case class Gamma(beta: Option[Beta] = None)
val navigateToText = GenIso[Gamma, Option[Beta]] composePrism
GenIso[Beta, Alpha].asPrism.below[Option] composePrism
GenIso[Alpha, String].asPrism.below[Option]
navigateToText.set(Some("foo"))(Gamma(None)) // Gamma(Some(Beta(Alpha(foo))))
navigateToText.set(Some("foo"))(Gamma(Some(Beta(Alpha("bar"))))) // Gamma(Some(Beta(Alpha(foo))))