class: center, middle # Optics from the ground up Nicolas Rinaudo • [@NicolasRinaudo] • [Besedo] --- class: center, middle # Algebraic Data Types --- ## Product types ```scala case class Classifier( name : String, classCount: Int ) ``` --- ## Product types ```scala `case class Classifier`( name : String, classCount: Int ) ``` --- ## Product types ```scala case class Classifier( `name : String`, classCount: Int ) ``` --- ## Product types ```scala case class Classifier( name : String, `classCount: Int` ) ``` --- ## Product types .center[![Classifier](img/classifier.svg)] --- ## Product types .center[![Classifier](img/classifier-1.svg)] --- ## Product types .center[![Classifier](img/classifier-2.svg)] --- ## Product types .center[![Classifier](img/classifier-3.svg)] --- ## Product types .center[![Classifier](img/classifier.svg)] --- ## Sum types ```scala sealed trait Auth case class Token( token: String ) extends Auth case class Login( user : String, password: String ) extends Auth ``` --- ## Sum types ```scala `sealed trait Auth` case class Token( token: String ) extends Auth case class Login( user : String, password: String ) extends Auth ``` --- ## Sum types ```scala sealed trait Auth case `class Token`( token: String ) `extends Auth` case class Login( user : String, password: String ) extends Auth ``` --- ## Sum types ```scala sealed trait Auth case class Token( token: String ) extends Auth case `class Login`( user : String, password: String ) `extends Auth` ``` --- ## Sum types .center[![Auth](img/auth.svg)] --- ## Sum types .center[![Auth](img/auth-1.svg)] --- ## Sum types .center[![Auth](img/auth-2.svg)] --- ## Sum types .center[![Auth](img/auth-3.svg)] --- ## Sum types .center[![Auth](img/auth.svg)] --- ## ADTs ```scala case class MlService( auth : Auth, classifier: Classifier ) ``` --- ## ADTs ```scala `case class MlService`( auth : Auth, classifier: Classifier ) ``` --- ## ADTs ```scala case class MlService( `auth : Auth`, classifier: Classifier ) ``` --- ## ADTs ```scala case class MlService( auth : Auth, `classifier: Classifier` ) ``` --- ## ADTs .center[![MlService](img/mlservice-1.svg)] --- ## ADTs .center[![MlService](img/mlservice-2.svg)] --- ## ADTs .center[![MlService](img/mlservice-3.svg)] --- ## ADTs .center[![MlService](img/mlservice-4.svg)] --- ## ADTs .center[![MlService](img/mlservice.svg)] --- ## Key takeaways -- * Product types aggregate values with an `⋀`. -- * Sum types aggregate values with an `⋁`. -- * ADTs are nested sum and product types. --- class: center, middle # Lenses --- ## Motivation ```scala val service = MlService( Login("jsmith", "Tr0ub4dor&3"), Classifier("news20", 20) ) ``` --- ## Motivation ```scala val service = MlService( Login("jsmith", "Tr0ub4dor&3"), Classifier(`"news20"`, 20) ) ``` --- ## Motivation .center[![Complete ADT](img/mlservice.svg)] --- ## Motivation .center[![Service to classifier name](img/service-name.svg)] --- ## Motivation .section[Java] ```scala service.classifier.name = "NEWS20" ``` -- .section[Scala] ```scala service.copy( classifier = service.classifier.copy( name = "NEWS20" ) ) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S } ``` ```scala val nameSetter: Setter[MlService, String] = ??? val updated: MlService = nameSetter.set("NEWS20")(service) ``` --- ## Generic setter type ```scala trait Setter[`S`, A] { def set(a: A)(s: S): S } ``` ```scala val nameSetter: Setter[`MlService`, String] = ??? val updated: MlService = nameSetter.set("NEWS20")(service) ``` --- ## Generic setter type ```scala trait Setter[S, `A`] { def set(a: A)(s: S): S } ``` ```scala val nameSetter: Setter[MlService, `String`] = ??? val updated: MlService = nameSetter.set("NEWS20")(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { `def set(a: A)(s: S): S` } ``` ```scala val nameSetter: Setter[MlService, String] = ??? val updated: MlService = nameSetter.`set("NEWS20")(service)` ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(`a: A`)(s: S): S } ``` ```scala val nameSetter: Setter[MlService, String] = ??? val updated: MlService = nameSetter.set(`"NEWS20"`)(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(`s: S`): S } ``` ```scala val nameSetter: Setter[MlService, String] = ??? val updated: MlService = nameSetter.set("NEWS20")(`service`) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): `S` } ``` ```scala val nameSetter: Setter[MlService, String] = ??? val `updated: MlService` = nameSetter.set("NEWS20")(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A } ``` ```scala val classifierName: String = nameSetter.get(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S `def get(s: S): A` } ``` ```scala val classifierName: String = nameSetter.`get(service)` ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(`s: S`): A } ``` ```scala val classifierName: String = nameSetter.get(`service`) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): `A` } ``` ```scala val `classifierName: String` = nameSetter.get(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A def modify(f: A => A)(s: S): S = set(f(get(s)))(s) } ``` ```scala val updated: MlService = nameSetter.modify(_.toUpperCase)(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A `def modify(f: A => A)(s: S): S = set(f(get(s)))(s)` } ``` ```scala val updated: MlService = nameSetter.`modify(_.toUpperCase)(service)` ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A def modify(`f: A => A`)(s: S): S = set(f(get(s)))(s) } ``` ```scala val updated: MlService = nameSetter.modify(`_.toUpperCase`)(service) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A def modify(f: A => A)(`s: S`): S = set(f(get(s)))(s) } ``` ```scala val updated: MlService = nameSetter.modify(_.toUpperCase)(`service`) ``` --- ## Generic setter type ```scala trait Setter[S, A] { def set(a: A)(s: S): S def get(s: S): A def modify(f: A => A)(s: S): `S` = set(f(get(s)))(s) } ``` ```scala val `updated: MlService` = nameSetter.modify(_.toUpperCase)(service) ``` --- ## Lens ```scala trait Lens[S, A] { def set(a: A)(s: S): S def get(s: S): A def modify(f: A => A)(s: S): S = set(f(get(s)))(s) } ``` --- ## Lens ```scala trait `Lens[S, A]` { def set(a: A)(s: S): S def get(s: S): A def modify(f: A => A)(s: S): S = set(f(get(s)))(s) } ``` --- ## Lens ```scala object Lens { def apply[S, A]( setter: (A, S) => S, getter: S => A ) = new Lens[S, A] { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Lens ```scala `object Lens` { def apply[S, A]( setter: (A, S) => S, getter: S => A ) = new Lens[S, A] { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Lens ```scala object Lens { `def apply[S, A]`( setter: (A, S) => S, getter: S => A ) = `new Lens[S, A]` { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Lens ```scala object Lens { def apply[S, A]( `setter: (A, S) => S`, getter: S => A ) = new Lens[S, A] { override `def set(a: A)(s: S) = setter(a, s)` override def get(s: S) = getter(s) } } ``` --- ## Lens ```scala object Lens { def apply[S, A]( setter: (A, S) => S, `getter: S => A` ) = new Lens[S, A] { override def set(a: A)(s: S) = setter(a, s) override `def get(s: S) = getter(s)` } } ``` --- ## Lens ```scala type Set[S, A] = (A, S) => S type Get[S, A] = S => A ``` --- ## Lens ```scala type `Set[S, A]` = `(A, S) => S` type Get[S, A] = S => A ``` --- ## Lens ```scala type Set[S, A] = (A, S) => S type `Get[S, A]` = `S => A` ``` --- ## Lens ```scala object Lens { def apply[S, A]( setter: Set[S, A], getter: Get[S, A] ) = new Lens[S, A] { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Lens ```scala object Lens { def apply[S, A]( `setter: Set[S, A]`, `getter: Get[S, A]` ) = new Lens[S, A] { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Service → Classifier name .center[![Service to classifier name](img/service-name.svg)] --- ## Service → Classifier .center[![Service to classifier](img/service-classifier.svg)] --- ## Service → Classifier ```scala val serviceClassifier = Lens[MlService, Classifier]( setter = (a, s) => s.copy(classifier = a), getter = s => s.classifier ) ``` --- ## Service → Classifier ```scala val serviceClassifier = `Lens[MlService, Classifier]`( setter = (a, s) => s.copy(classifier = a), getter = s => s.classifier ) ``` --- ## Service → Classifier ```scala val serviceClassifier = Lens[MlService, Classifier]( setter = `(a, s) => s.copy(classifier = a)`, getter = s => s.classifier ) ``` --- ## Service → Classifier ```scala val serviceClassifier = Lens[MlService, Classifier]( setter = (a, s) => s.copy(classifier = a), getter = `s => s.classifier` ) ``` --- ## Classifier → name .center[![Classifier to name](img/classifier-name.svg)] --- ## Classifier → name ```scala val classifierName = Lens[Classifier, String]( setter = (a, s) => s.copy(name = a), getter = s => s.name ) ``` --- ## Classifier → name ```scala serviceClassifier.modify(classifierName.set("NEWS20"))(service) // res1: MlService = MlService(Login(jsmith,Tr0ub4dor&3),Classifier(NEWS20,20)) ``` --- ## Composing lenses ```scala def setName(name: String, service: MlService) = serviceClassifier.modify(classifierName.set(name))(service) ``` --- ## Composing lenses ```scala def setName(`name: String`, service: MlService) = serviceClassifier.modify(classifierName.set(`name`))(service) ``` --- ## Composing lenses ```scala def setName(name: String, `service: MlService`) = serviceClassifier.modify(classifierName.set(name))(`service`) ``` --- ## Composing lenses ```scala def setName( serviceClassifier: Lens[MlService, Classifier], classifierName : Lens[Classifier, String] ) (name: String, service: MlService): MlService = serviceClassifier.modify(classifierName.set(name))(service) ``` --- ## Composing lenses ```scala def setName( `serviceClassifier: Lens[MlService, Classifier]`, classifierName : Lens[Classifier, String] ) (name: String, service: MlService): MlService = `serviceClassifier`.modify(classifierName.set(name))(service) ``` --- ## Composing lenses ```scala def setName( serviceClassifier: Lens[MlService, Classifier], `classifierName : Lens[Classifier, String]` ) (name: String, service: MlService): MlService = serviceClassifier.modify(`classifierName`.set(name))(service) ``` --- ## Composing lenses ```scala def setName( serviceClassifier: Lens[MlService, `Classifier`], classifierName : Lens[`Classifier`, String] ) (name: String, service: MlService): MlService = serviceClassifier.modify(classifierName.set(name))(service) ``` --- ## Composing lenses ```scala def setter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) (b: B, s: S): S = l1.modify(l2.set(b))(s) ``` --- ## Composing lenses ```scala def setter[S, A, B]( l1: Lens[S, `A`], l2: Lens[`A`, B] ) (b: B, s: S): S = l1.modify(l2.set(b))(s) ``` --- ## Composing lenses ```scala def setter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) `(b: B, s: S): S` = l1.modify(l2.set(b))(s) ``` --- ## Composing lenses ```scala def setter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : Set[S, B] = (b, s) => l1.modify(l2.set(b))(s) ``` --- ## Composing lenses ```scala def setter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : `Set[S, B]` = (b, s) => l1.modify(l2.set(b))(s) ``` --- ## Composing lenses ```scala def getter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : Get[S, B] = s => l2.get(l1.get(s)) ``` --- ## Composing lenses ```scala def getter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : `Get[S, B]` = s => l2.get(l1.get(s)) ``` --- ## Composing lenses ```scala def getter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : Get[S, B] = s => l2.get(`l1.get(s)`) ``` --- ## Composing lenses ```scala def getter[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) : Get[S, B] = s => `l2.get`(l1.get(s)) ``` --- ## Composing lenses ```scala def composeLL[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) = Lens[S, B]( setter(l1, l2), getter(l1, l2) ) ``` --- ## Composing lenses ```scala def composeLL[S, A, B]( l1: `Lens[S, A]`, l2: Lens[A, B] ) = Lens[S, B]( setter(l1, l2), getter(l1, l2) ) ``` --- ## Composing lenses ```scala def composeLL[S, A, B]( l1: Lens[S, A], l2: `Lens[A, B]` ) = Lens[S, B]( setter(l1, l2), getter(l1, l2) ) ``` --- ## Composing lenses ```scala def composeLL[S, A, B]( l1: Lens[S, A], l2: Lens[A, B] ) = `Lens[S, B]`( setter(l1, l2), getter(l1, l2) ) ``` --- ## Service → Classifier → name .center[![Service to classifier name](img/service-name.svg)] --- ## Service → Classifier → name ```scala val serviceClassifierName = composeLL( serviceClassifier, classifierName ) ``` --- ## Service → Classifier → name ```scala serviceClassifierName.modify(_.toUpperCase)(service) // res2: MlService = MlService(Login(jsmith,Tr0ub4dor&3),Classifier(NEWS20,20)) ``` --- ## Key takeaways -- * Lenses are used to focus on specific parts of a product type. -- * They compose to build arbitrarily deep paths in nested structures. --- class: center, middle # Prisms & Optionals --- ## Motivation ```scala val service = MlService( Login("jsmith", "Tr0ub4dor&3"), Classifier("news20", 20) ) ``` --- ## Motivation ```scala val service = MlService( Login(`"jsmith"`, "Tr0ub4dor&3"), Classifier("news20", 20) ) ``` --- ## Motivation .center[![Complete ADT](img/mlservice.svg)] --- ## Motivation .center[![Service to user](img/service-user.svg)] --- ## Service → Auth .center[![Service to auth](img/service-auth.svg)] --- ## Service → Auth ```scala val serviceAuth = Lens[MlService, Auth]( setter = (a, s) => s.copy(auth = a), getter = s => s.auth ) ``` --- ## Auth → Login .center[![Auth to login](img/auth-login.svg)] --- ## Auth → user? .center[![Auth to user](img/auth-user.svg)] --- ## Auth → user? ```scala val authUser = Lens[Auth, String]( setter = (a, s) => ???, // Auth -> String getter = s => s match { case Login(user, _) => user case Token(token) => ??? } ) ``` --- ## Auth → user? ```scala val authUser = `Lens[Auth, String]`( setter = (a, s) => ???, // Auth -> String getter = s => s match { case Login(user, _) => user case Token(token) => ??? } ) ``` --- ## Auth → user? ```scala val authUser = Lens[Auth, String]( setter = (a, s) => ???, * // Auth -> String * getter = s => s match { * case Login(user, _) => user * case Token(token) => ??? * } ) ``` --- ## Auth → user? ```scala val authUser = Lens[Auth, String]( setter = (a, s) => ???, // Auth -> String getter = `s => s match` { case Login(user, _) => user case Token(token) => ??? } ) ``` --- ## Auth → user? ```scala val authUser = Lens[Auth, String]( setter = (a, s) => ???, // Auth -> String getter = s => s match { `case Login(user, _) => user` case Token(token) => ??? } ) ``` --- ## Auth → user? ```scala val authUser = Lens[Auth, String]( setter = (a, s) => ???, // Auth -> String getter = s => s match { case Login(user, _) => user `case Token(token) => ???` } ) ``` --- ## Auth → Login .center[![Auth to login](img/auth-login.svg)] --- ## Prism ```scala trait Prism[S, A] { def set(a: A): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[`S`, A] { def set(a: A): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[`Auth`, Login] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[S, `A`] { def set(a: A): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, `Login`] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[S, A] { def set(a: A): S `def get(s: S): Option[A]` def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val `login: Option[Login] = authLogin.get(service.auth)` ``` --- ## Prism ```scala trait Prism[S, A] { def set(a: A): S def get(`s: S`): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val login: Option[Login] = authLogin.get(`service.auth`) ``` --- ## Prism ```scala trait Prism[S, A] { def set(a: A): S def get(s: S): `Option[A]` def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val updated: Auth = authLogin.set(Login("foo", "bar")) val `login: Option[Login]` = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[S, A] { `def set(a: A): S` def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val `updated: Auth = authLogin.set(Login("foo", "bar"))` val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[S, A] { def set(`a: A`): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val updated: Auth = authLogin.set(`Login("foo", "bar")`) val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala trait Prism[S, A] { def set(a: A): `S` def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a)) case None => s } } ``` ```scala val authLogin: Prism[Auth, Login] = ??? val `updated: Auth` = authLogin.set(Login("foo", "bar")) val login: Option[Login] = authLogin.get(service.auth) ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } } ``` --- ## Prism ```scala `object Prism` { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } } ``` --- ## Prism ```scala object Prism { `def apply[S, A]`( setter: A => S, getter: S => Option[A] ) = new `Prism[S, A]` { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } } ``` --- ## Prism ```scala object Prism { def apply[S, A]( `setter: A => S`, getter: S => Option[A] ) = new Prism[S, A] { override `def set(a: A) = setter(a)` override def get(s: S) = getter(s) } } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, `getter: S => Option[A]` ) = new Prism[S, A] { override def set(a: A) = setter(a) override `def get(s: S) = getter(s)` } } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, `getter: S => Option[A]` ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } def fromPartial[S, A]( setter: A => S, getter: PartialFunction[S, A] ) = Prism(setter, getter.lift) } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } * def fromPartial[S, A]( * setter: A => S, * getter: PartialFunction[S, A] * ) = Prism(setter, getter.lift) } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } def fromPartial[S, A]( setter: A => S, `getter: PartialFunction[S, A]` ) = Prism(setter, getter.lift) } ``` --- ## Prism ```scala object Prism { def apply[S, A]( setter: A => S, getter: S => Option[A] ) = new Prism[S, A] { override def set(a: A) = setter(a) override def get(s: S) = getter(s) } def fromPartial[S, A]( setter: A => S, getter: PartialFunction[S, A] ) = Prism(setter, `getter.lift`) } ``` --- ## Auth → Login .center[![Auth to login](img/auth-login.svg)] --- ## Auth → Login ```scala val authLogin = Prism.fromPartial[Auth, Login]( setter = a => a, getter = { case s: Login => s } ) ``` --- ## Auth → Login ```scala val authLogin = `Prism`.fromPartial`[Auth, Login]`( setter = a => a, getter = { case s: Login => s } ) ``` --- ## Auth → Login ```scala val authLogin = Prism.fromPartial[Auth, Login]( setter = `a => a`, getter = { case s: Login => s } ) ``` --- ## Auth → Login ```scala val authLogin = Prism.fromPartial[Auth, Login]( setter = a => a, getter = `{ case s: Login => s }` ) ``` --- ## Service → Login .center[![Service to login](img/service-login.svg)] --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // B => S setter = b => { val a: A = p.set(b) val s: S = ??? s }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( * // B => S * setter = b => { * val a: A = p.set(b) * val s: S = ??? * s * }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // `B` => S setter = `b` => { val a: A = p.set(b) val s: S = ??? s }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // B => `S` setter = b => { val a: A = p.set(b) val s: S = ??? `s` }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // B => S setter = b => { `val a: A = p.set(b)` val s: S = ??? s }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // B => S setter = b => { val a: A = p.set(b) `val s: S = ???` s }, // S => B getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Prism ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Prism[S, B]( // B => S setter = b => { val a: A = p.set(b) val s: S = ??? s }, * // S => B * getter = s => p.get(l.get(s)) ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), // S => B getter = s => { val a: A = l.get(s) val b: B = ??? b } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( * // (B, S) => S * setter = (b, s) => l.set(p.set(b))(s), // S => B getter = s => { val a: A = l.get(s) val b: B = ??? b } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), * // S => B * getter = s => { * val a: A = l.get(s) * val b: B = ??? * b * } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), // `S` => B getter = `s` => { val a: A = l.get(s) val b: B = ??? b } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), // S => `B` getter = s => { val a: A = l.get(s) val b: B = ??? `b` } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), // S => B getter = s => { `val a: A = l.get(s)` val b: B = ??? b } ) ``` --- ## Lens ∘ Prism ≟ Lens ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Lens[S, B]( // (B, S) => S setter = (b, s) => l.set(p.set(b))(s), // S => B getter = s => { val a: A = l.get(s) `val b: B = ???` b } ) ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(s: S): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[`S`, A] { def set(a: A)(s: S): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[`MlService`, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, `A`] { def set(a: A)(s: S): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, `Login`] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, A] { `def set(a: A)(s: S): S` def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val `updated: MlService` = `serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service)` val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, A] { def set(`a: A`)(s: S): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(`Login("psmith", "Tr0ub4dor&3")`)(service) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(`s: S`): S def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(`service`) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(s: S): `S` def get(s: S): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val `updated: MlService` = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val login: Option[Login] = serviceLogin.get(service) ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(s: S): S `def get(s: S): Option[A]` def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val `login: Option[Login] = serviceLogin.get(service)` ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(s: S): S def get(`s: S`): Option[A] def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val login: Option[Login] = serviceLogin.get(`service`) ``` --- ## Optional ```scala trait Optional[S, A] { def set(a: A)(s: S): S def get(s: S): `Option[A]` def modify(f: A => A)(s: S): S = get(s) match { case Some(a) => set(f(a))(s) case None => s } } ``` ```scala val serviceLogin: Optional[MlService, Login] = ??? val updated: MlService = serviceLogin.set(Login("psmith", "Tr0ub4dor&3"))(service) val `login: Option[Login]` = serviceLogin.get(service) ``` --- ## Optional ```scala object Optional { def apply[S, A]( setter: (A, S) => S, getter: S => Option[A] ) = new Optional[S, A] { override def set(a: A)(s: S) = setter(a, s) override def get(s: S) = getter(s) } } ``` --- ## Lens ∘ Prism = Optional ```scala def composeLP[S, A, B]( l: Lens[S, A], p: Prism[A, B] ) = Optional[S, B]( setter = (b, s) => l.set(p.set(b))(s), getter = s => p.get(l.get(s)) ) ``` --- class: center, middle # Intermission: composition galore --- ## Prism ∘ Prism = Prism ```scala def composePP[S, A, B]( p1: Prism[S, A], p2: Prism[A, B] ) = Prism[S, B]( setter = b => p1.set(p2.set(b)), getter = s => p1.get(s).flatMap(p2.get) ) ``` --- ## Prism ∘ Lens = Optional ```scala def composePL[S, A, B]( p: Prism[S, A], l: Lens[A, B] ) = Optional[S, B]( setter = (b, s) => p.modify(l.set(b))(s), getter = s => p.get(s).map(l.get) ) ``` --- ## Optional ∘ Optional = Optional ```scala def composeOO[S, A, B]( o1: Optional[S, A], o2: Optional[A, B] ) = Optional[S, B]( setter = (b, s) => o1.modify(o2.set(b))(s), getter = s => o1.get(s).flatMap(o2.get) ) ``` --- ## Optional ∘ Prism = Optional ```scala def composeOP[S, A, B]( o: Optional[S, A], p: Prism[A, B] ) = Optional[S, B]( setter = (b, s) => o.set(p.set(b))(s), getter = s => o.get(s).flatMap(p.get) ) ``` --- ## Prism ∘ Optional = Optional ```scala def composePO[S, A, B]( p: Prism[S, A], o: Optional[A, B] ) = Optional[S, B]( setter = (b, s) => p.modify(o.set(b))(s), getter = s => p.get(s).flatMap(o.get) ) ``` --- ## Optional ∘ Lens = Optional ```scala def composeOL[S, A, B]( o: Optional[S, A], l: Lens[A, B] ) = Optional[S, B]( setter = (b, s) => o.modify(l.set(b))(s), getter = s => o.get(s).map(l.get) ) ``` --- ## Lens ∘ Optional = Optional ```scala def composeLO[S, A, B]( l: Lens[S, A], o: Optional[A, B] ) = Optional[S, B]( setter = (b, s) => l.modify(o.set(b))(s), getter = s => o.get(l.get(s)) ) ``` --- class: center, middle # End of intermission --- ## Service → Login .center[![Service to login](img/service-login.svg)] --- ## Service → Login ```scala val serviceLogin = composeLP( serviceAuth, authLogin ) ``` --- ## Login → user .center[![Login to user](img/login-user.svg)] --- ## Login → user ```scala val loginUser = Lens[Login, String]( setter = (a, s) => s.copy(user = a), getter = s => s.user ) ``` --- ## Service → user .center[![Service to user](img/service-user.svg)] --- ## Service → user ```scala val serviceUser = composeOL( serviceLogin, loginUser ) ``` --- ## Service → user ```scala serviceUser.set("psmith")(service) // res3: MlService = MlService(Login(psmith,Tr0ub4dor&3),Classifier(news20,20)) ``` --- ## Key takeaways -- * Prisms are used to explore sum types. -- * They compose with lenses and yield optionals. --- class: center, middle # Concrete use case: ConfigPath --- ## Configuration data structure ```json { "auth": { "user" : "psmith", "password": "Tr0ub4dor&3" }, "classifier": { "name" : "news20", "classCount": 20 } } ``` --- ## Configuration data structure ```json { "auth": { `"user"` : "psmith", "password": "Tr0ub4dor&3" }, "classifier": { "name" : "news20", "classCount": 20 } } ``` --- ## Configuration data structure ```json { "auth": { "user" : `"psmith"`, "password": "Tr0ub4dor&3" }, "classifier": { "name" : "news20", "classCount": 20 } } ``` --- ## Configuration data structure ```json { `"auth"`: { "user" : "psmith", "password": "Tr0ub4dor&3" }, "classifier": { "name" : "news20", "classCount": 20 } } ``` --- ## Configuration data structure ```scala sealed trait Config case class Value( value: String ) extends Config case class Section( children: Map[String, Config] ) extends Config ``` --- ## Configuration data structure ```scala *sealed trait Config case class Value( value: String ) extends Config case class Section( children: Map[String, Config] ) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config *case class Value( * value: String *) extends Config case class Section( children: Map[String, Config] ) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config case class Value( value: String ) extends Config *case class Section( * children: Map[String, Config] *) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config *case class Value( * value: String *) extends Config case class Section( children: Map[String, Config] ) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config case class Value( `value: String` ) extends Config case class Section( children: Map[String, Config] ) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config case class Value( value: String ) extends Config *case class Section( * children: Map[String, Config] *) extends Config ``` --- ## Configuration data structure ```scala sealed trait Config case class Value( value: String ) extends Config case class Section( `children: Map[String, Config]` ) extends Config ``` --- ## Configuration data structure ```scala val conf = Section(Map( "auth" -> Section(Map( "user" -> Value("psmith"), "password" -> Value("Tr0ub4dor&3") )), "classifier" -> Section(Map( "name" -> Value("news20"), "classCount" -> Value("20") )) )) ``` --- ## Configuration data structure ```scala val conf = Section(Map( "auth" -> Section(Map( `"user"` -> Value("psmith"), "password" -> Value("Tr0ub4dor&3") )), "classifier" -> Section(Map( "name" -> Value("news20"), "classCount" -> Value("20") )) )) ``` --- ## Configuration data structure ```scala val conf = Section(Map( "auth" -> Section(Map( "user" -> `Value("psmith")`, "password" -> Value("Tr0ub4dor&3") )), "classifier" -> Section(Map( "name" -> Value("news20"), "classCount" -> Value("20") )) )) ``` --- ## Configuration data structure ```scala val conf = Section(Map( `"auth" -> Section`(Map( "user" -> Value("psmith"), "password" -> Value("Tr0ub4dor&3") )), "classifier" -> Section(Map( "name" -> Value("news20"), "classCount" -> Value("20") )) )) ``` --- ## Configuration optics ```scala val section = Prism.fromPartial[Config, Section]( setter = a => a, getter = { case a: Section => a } ) val value = Prism.fromPartial[Config, Value]( setter = a => a, getter = { case a: Value => a } ) ``` --- ## Configuration optics ```scala val section = `Prism`.fromPartial`[Config, Section]`( setter = a => a, getter = { case a: Section => a } ) val value = Prism.fromPartial[Config, Value]( setter = a => a, getter = { case a: Value => a } ) ``` --- ## Configuration optics ```scala val section = Prism.fromPartial[Config, Section]( setter = a => a, getter = { case a: Section => a } ) val value = `Prism`.fromPartial`[Config, Value]`( setter = a => a, getter = { case a: Value => a } ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => Section(s.children + (name -> a)), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(`name: String`) = Optional[Section, Config]( setter = (a, s) => Section(s.children + (name -> a)), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = `Optional[Section, Config]`( setter = (a, s) => Section(s.children + (name -> a)), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = `(a, s) => Section(s.children + (name -> a))`, getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => `Section`(s.children + (name -> a)), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => Section(`s.children` + (name -> a)), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => Section(s.children + `(name -> a)`), getter = s => s.children.get(name) ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => Section(s.children + (name -> a)), getter = `s => s.children.get(name)` ) ``` --- ## Configuration optics ```scala def sectionChild(name: String) = Optional[Section, Config]( setter = (a, s) => Section(s.children + (name -> a)), getter = s => `s.children.get(name)` ) ``` --- ## Configuration optics ```scala val identityOpt = Optional[Config, Config]( setter = (a, _) => a, getter = s => Some(s) ) ``` --- ## Configuration optics ```scala val identityOpt = `Optional[Config, Config]`( setter = (a, _) => a, getter = s => Some(s) ) ``` --- ## Configuration optics ```scala val identityOpt = Optional[Config, Config]( setter = `(a, _) => a`, getter = s => Some(s) ) ``` --- ## Configuration optics ```scala val identityOpt = Optional[Config, Config]( setter = (a, _) => a, getter = `s => Some(s)` ) ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def child(name: String) = ConfigPath( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(`current: Optional[Config, Config]`) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def child(name: String) = ConfigPath( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { `val asValue = composeOP(current, value)` val asSection = composeOP(current, section) def child(name: String) = ConfigPath( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) `val asSection = composeOP(current, section)` def child(name: String) = ConfigPath( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) * def child(name: String) = ConfigPath( * composeOO( * asSection, * sectionChild(name) * ) * ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def child(`name: String`) = ConfigPath( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def child(name: String) = `ConfigPath`( composeOO( asSection, sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala case class ConfigPath(current: Optional[Config, Config]) { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def child(name: String) = ConfigPath( * composeOO( * asSection, * sectionChild(name) ) ) } ``` --- ## ConfigPath ```scala val classifierName: Optional[Config, Value] = ConfigPath(identityOpt). child("classifier"). child("name"). asValue ``` --- ## ConfigPath ```scala val classifierName: `Optional[Config, Value]` = ConfigPath(identityOpt). child("classifier"). child("name"). asValue ``` --- ## ConfigPath ```scala val classifierName: Optional[Config, Value] = `ConfigPath(identityOpt)`. child("classifier"). child("name"). asValue ``` --- ## ConfigPath ```scala val classifierName: Optional[Config, Value] = ConfigPath(identityOpt). `child("classifier")`. child("name"). asValue ``` --- ## ConfigPath ```scala val classifierName: Optional[Config, Value] = ConfigPath(identityOpt). child("classifier"). `child("name")`. asValue ``` --- ## ConfigPath ```scala val classifierName: Optional[Config, Value] = ConfigPath(identityOpt). child("classifier"). child("name"). `asValue` ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def selectDynamic(missingMember: String): String = missingMember.toUpperCase } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object `UpCase` extends Dynamic { def selectDynamic(missingMember: String): String = missingMember.toUpperCase } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase `extends Dynamic` { def selectDynamic(missingMember: String): String = missingMember.toUpperCase } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def selectDynamic(missingMember: String): String = missingMember.toUpperCase } ``` ```scala `UpCase.bar` ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def selectDynamic(`missingMember: String`): String = missingMember.toUpperCase } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def `selectDynamic`(missingMember: String): String = missingMember.toUpperCase } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def selectDynamic(missingMember: String): String = `missingMember.toUpperCase` } ``` ```scala UpCase.bar ``` --- ## Dynamic ```scala import scala.language.dynamics object UpCase extends Dynamic { def selectDynamic(missingMember: String): String = missingMember.toUpperCase } ``` ```scala UpCase.bar // res5: String = BAR ``` --- ## Dynamic ConfigPath ```scala case class ConfigPath( current: Optional[Config, Config] ) extends Dynamic { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def selectDynamic(child: String) = ConfigPath( composeOO( asSection, sectionChild(child) ) ) } ``` --- ## Dynamic ConfigPath ```scala case class ConfigPath( current: Optional[Config, Config] ) `extends Dynamic` { val asValue = composeOP(current, value) val asSection = composeOP(current, section) def selectDynamic(child: String) = ConfigPath( composeOO( asSection, sectionChild(child) ) ) } ``` --- ## Dynamic ConfigPath ```scala case class ConfigPath( current: Optional[Config, Config] ) extends Dynamic { val asValue = composeOP(current, value) val asSection = composeOP(current, section) `def selectDynamic(child: String)` = ConfigPath( composeOO( asSection, sectionChild(child) ) ) } ``` --- ## Dynamic ConfigPath ```scala val classifierName = ConfigPath(identityOpt). classifier. name. asValue ``` --- ## Dynamic ConfigPath ```scala val classifierName = `ConfigPath(identityOpt)`. classifier. name. asValue ``` --- ## Dynamic ConfigPath ```scala val classifierName = ConfigPath(identityOpt). `classifier`. name. asValue ``` --- ## Dynamic ConfigPath ```scala val classifierName = ConfigPath(identityOpt). classifier. `name`. asValue ``` --- ## Dynamic ConfigPath ```scala val classifierName = ConfigPath(identityOpt). classifier. name. `asValue` ``` --- ## Dynamic ConfigPath ```scala val classifierName = `ConfigPath(identityOpt)`. classifier. name. asValue ``` --- ## Dynamic ConfigPath ```scala val root = ConfigPath(identityOpt) ``` --- ## Dynamic ConfigPath ```scala val classifierName = root. classifier. name. asValue ``` --- ## Dynamic ConfigPath ```scala classifierName.get(conf) // res6: Option[Value] = Some(Value(news20)) ``` --- ## Key takeaway -- Optics work not only with ADTs, but with any nested immutable data structure. --- class: center, middle # In closing --- ## If you only remember 1 slide... -- * Lenses are used to drill down arbitrarily deep in nested product types. -- * Prisms are used to explore sum types. -- * They compose into optionals. -- * You can use optics to navigate any immutable nested data structure. --- class: center, middle # Questions? Nicolas Rinaudo • [@NicolasRinaudo] • [Besedo] --- ## Bonus Material! * [Available optics libraries](./libraries.html) [Besedo]:https://twitter.com/besedo_official [@NicolasRinaudo]:https://twitter.com/NicolasRinaudo