2017-02-22 3 views
0

J'ai une structure de classes de cas imbriquées que je crée avec des valeurs par défaut:Définition de valeurs en option dans un chemin construit avec optique composé

case class Alpha(text: String = "content") 
case class Beta(alpha: Alpha = Alpha()) 
case class Gamma(beta: Option[Beta] = None) 

Je voudrais créer la chose avec des valeurs par défaut, puis modifier les éléments en particulier qui doivent être non-défaut en utilisant Monocle.

Avec isos c'est facile. Je peux préciser la navigation avec la composition, puis utiliser ensemble pour modifier l'élément interne:

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

Malheureusement avec prims il ne semble pas que élégante, comme set/modify ne modifiera l'élément interne si toutes les options au cours la navigation sont définis (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")))) 

la meilleure chose que je suis venu avec jusqu'à présent est en utilisant une optique composée de mettre un Some() chaque élément facultatif du chemin et l'optique composé tout le long pour définir l'élément qui nous intéressait pour commencer .

(Gamma.betaI.set(Some(Beta())) andThen navigateToText.set("foo")) (Gamma()) shouldBe Gamma(Some(Beta(Alpha("foo")))) 

Idéalement, je voudrais chaque bloc de construction de la composition pour définir les valeurs facultatives à Some(defaultValue) si elles sont à l'origine None. Évidemment, le bloc de construction devrait être défini, y compris la valeur par défaut appropriée pour l'étape du chemin. Des suggestions?

Code complet, y compris les importations: https://github.com/jcaraballo/navigating-fixtures-with-monocle/blob/master/src/test/scala/pr/NavigateIntoOptionSpec.scala

Répondre

1

Vous pouvez utiliser below de Prism tels que les types correspondent dans la composition optique:

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

Excellent, qui fonctionne très bien! Et il fonctionne également avec des lentilles lorsque le parent avec l'enfant optionnel a d'autres enfants. (Je ne peux pas obtenir SO pour formater le code correctement ici, mais j'ai essayé la composition avec un objectif dans https://github.com/jcaraballo/navigating-fixtures-with-monocle/blob/master/src/test /scala/pr/NavigateIntoOptionSpec.scala) – qtwo