KEMBAR78
Beyond Scala Lens | PDF
Beyond Scala Lenses
github: @julien-truffaut or twitter: @JulienTruffaut
Function
S A
A function transforms all s in S into an A
Function
Isomorphism
S A
For all s: S, g(f(s)) == s For all a: A, f(g(a)) == a
f
g
Iso
case class Iso[S,A](
get : S => A,
reverseGet: A => S
)
Properties:
For all s: S, reverseGet(get(s)) == s
For all a: A, get(reverseGet(a)) == a
Modify
A A
f
Modify
A A
S S
f
get
reverseGet
modify
Compose
S A
Iso
B
Iso
Iso
Iso Derived Methods
case class Iso[S,A](
get : S => A,
reverseGet: A => S
){
def modify(m: A => A): S => S
def reverse: Iso[A,S]
def compose[B](other: Iso[A,B]): Iso[S,B]
}
Units
class Robot{
def moveBy(d: Double): Robot
}
val nono: Robot = …
nono.moveBy(100.5) // Meters
nono.moveBy(3) // Kilometers
nono.moveBy(-2.5) // Yards
Safe Unit
case class Meter(d: Double)
case class Yard(d: Double)
class Robot{
def moveBy(m: Meter): Robot
}
nono.moveBy(Meter(100.5))
nono.moveBy(10) // does not compile
nono.moveBy(Yard(3.0)) // does not compile
Iso
Meter Yard
Meter to Yard
val meterToYard: Iso[Meter, Yard] = Iso(
m => Yard(m.value * 1.09361),
y => Meter(y.value / 1.09361)
)
meterToYard.get(Meter(200)) == Yard(218.7219999…)
nono.moveBy(meterToYard.reverseGet(Yard(2.5))
Iso
Meter Yard
Kilometer
Iso
Meter Yard
Kilometer Mile
Iso Composition
case class Kilometer(value: Double)
case class Mile(value: Double)
val meterToKilometer: Iso[Meter, Kilometer] = …
val yardToMile : Iso[Yard, Mile] = …
val kilometerToMile: Iso[Kilometer, Mile] =
meterToKilometer.reverse compose
meterToYard compose
yardToMile
Different Representation
val stringToList: Iso[String, List[Char]] =
Iso(_.toList, _.mkString(“”))
def listToVector[A]: Iso[List[A], Vector[A]] =
Iso(_.toVector, _.toList)
Generic Programming
case object Red
val red: Iso[Red, Unit] = Iso(Red => (), () => Red)
case class Person(name: String, age: Int)
val personToTuple: Iso[Person, (String, Int)] = …
Iso Properties
For all s: S, reverseGet(get(s)) == s
For all a: A, get(reverseGet(a)) == a
Scalacheck
def isoLaws[S,A](iso: Iso[S,A]) = new Properties {
property(“One Way”) = forAll { s: S =>
iso.reverseGet(iso.get(s)) == s
}
property(“Other Way”) = forAll { a: A =>
iso.get(iso.reverseGet(a)) == a
}
}
Scalacheck
import org.spec2.scalaz.Spec
class IsoSpec extends Spec {
checkAll(“meter to yard”, isoLaws(meterToYard))
}
val meterToYard: Iso[Meter, Yard] = Iso(
m => Yard(m.value * 1.09361),
y => Meter(y.value / 1.09361)
)
Scalacheck
A counter-example is:
[Yard(1.518707721E9)] (after 24 tries)
scala> meterToYard.get(meterToYard.reverseGet(
Yard(1.518707721E9)))
scala> res0: Yard= Yard(1.5187077210000002E9)
Relaxed Isomorphism
S
A
For all s: S such as f(s) exists, g(f(s)) == s
For all a: A, f(g(a)) == a
?
f
g
f is a Function[S, Option[A]]
g is a Function[A, S]
Prism
case class Prism[S,A](
getOption : S => Option[A],
reverseGet: A => S
)
Properties:
For all s: S, getOption(s) map reverseGet == Some(s) || None
For all a: A, getOption(reverseGet(a)) == Some(a)
Pattern matching / Constructor
sealed trait List[A]
case class Cons[A](h: A, t: List[A]) extends List[A]
case class Nil[A]() extends List[A]
Cons.unapply(List(1,2,3)) == Some((1, List(2,3)))
Cons.unapply(Nil) == None
Cons.apply(1, List(2,3)) == List(1,2,3)
Prism
sealed trait List[A]
case class Cons[A](h: A, t: List[A]) extends List[A]
case class Nil[A]() extends List[A]
def cons[A]: Prism[List[A], (A, List[A])] = …
cons.getOption(List(1,2,3)) == Some((1, List(2,3)))
cons.getOption(Nil) == None
cons.reverseGet(1, List(2,3)) == List(1,2,3)
Prism Derived Methods
case class Prism[S,A](
getOption: S => Option[A],
reverseGet: A => S
){
def isMatching(s: S): Boolean
def modify(f: A => A): S=> S
def modifyOption(f: A => A): S => Option[S]
def compose[B](other: Prism[A,B]): Prism[S,B]
def compose[B](other: Iso[A,B]): ???[S,B]
}
Iso – Prism
Optic f g
Iso S => A A => S
Prism S => Option[A] A => S
def isoToPrism[S,A](iso: Iso[S,A]): Prism[S,A] =
Prism(
getOption = s => Some(iso.get(s)),
reverseGet = iso.reverseGet
)
Iso – Prism
case class Prism[S,A]{
def compose[B](other: Prism[A,B]): Prism[S,B]
def compose[B](other: Iso[A,B]): Prism[S,B]
}
case class Iso[S,A]{
def compose[B](other: Iso[A,B]): Iso[S,B]
def compose[B](other: Prism[A,B]): Prism[S,B]
}
Enum
sealed trait Day
case object Monday extends Day
case object Tuesday extends Day
val tuesday: Prism[Day, Unit] = …
tuesday.getOption(Monday) == None
tuesday.getOption(Tuesday) == Some(())
tuesday.reverseGet(()) == Tuesday
Json
sealed trait Json
case class JNumber(v: Double) extends Json
case class JString(s: String) extends Json
val jNum: Prism[Json, Double] = …
jNum.modify(_ + 1)(JNumber(2.0)) == JNumber(3.0)
jNum.modify(_ + 1)(JString(“”)) == JString(“”)
jNum.modifyOption(_ + 1)(JString(“”)) == None
Safe Down Casting
val doubleToInt: Prism[Double, Int] = …
doubleToInt.getOption(3.4) == None
doubleToInt.getOption(3.0) == Some(3)
doubleToInt.reverseGet(5) == 5.0
Prism Composition
sealed trait Json
case class JNumber(v: Double) extends Json
case class JString(s: String) extends Json
val jInt: Prism[Json, Int] = jNum compose doubleToInt
jInt.getOption(JNumber(3.0)) == Some(3)
jInt.getOption(JNumber(5.9)) == None
jInt.getOption(JString(“”)) == None
Where is the bug?
def stringToInt: Prism[String, Int] = Prism(
getOption = s => Try(s.toInt).toOption,
reverseGet = _.toString
)
stringToInt.modify(_ * 2)(“5”) == “10”
stringToInt.getOption(“5”) == Some(5)
stringToInt.getOption(“-3”) == Some(-3)
stringToInt.getOption(“5.7”) == None
stringToInt.getOption(“99999999999999999”) == None
stringToInt.getOption(“Hello”) == None
Tadam !
” .toInt = 9“
Prism
S A B COr Or
sealed trait S
class A extends S
class B extends S
class C extends S
Lens
S A B CAnd And
case class S(a: A, b: B, c: C)
Lens
case class Lens[S,A](
get: S => A,
set:(A, S) => S
)
Properties:
For all s: S, set(get(s), s) == s
For all a: A, s: S, get(set(a, s)) == a
Iso – Lens
Optic f g
Iso S => A A => S
Lens S => A (A, S) => S
def isoToLens[S,A](iso: Iso[S,A]): Lens[S,A] =
Lens(
get = iso.get,
set = (a, _) => iso.reverseGet(a)
)
Accessors
case class Person(name: String, age: Int)
val age = Lens[Person, Int](_.age, (a, p) => p.copy(age = a))
val zoe = Person(“Zoe”, 25)
age.get(zoe) == 25
age.set(20, zoe) == Person(“Zoe”, 20)
age.modify(_ + 1)(zoe) == Person(“Zoe”, 26)
Nested Accessors
case class Person(name: String, age: Int, address: Address)
case class Address(streetNumber: Int, StreetName: String)
val address: Lens[Person, Address] = …
val streetName: Lens[Address, String] = …
val zoe = Person(“Zoe”, 25, Address(10, “High Street”))
(address compose streetName).get(zoe) == “High Street”
(address compose streetName).set(“Iffley Road”, zoe)
== Person(“Zoe”, 25, Address(10, “Iffley Road”))
Tuples
def first[A,B]: Lens[(A,B), A] = …
def second[A,B]: Lens[(A,B), B] = …
val tuple = (2,“Zoe”)
first.set(4, tuple) == (4,“Zoe”)
second.modify(_.reverse)(tuple) == (2,“eoZ”)
Universal Case Class Accessors
case class Person(name: String, age: Int)
val personToTuple: Iso[Person,(String,Int)] = …
val zoe = Person(“Zoe”, 25)
(personToTuple compose second).set(30, zoe)
== Person(“Zoe”, 30)
At
def at[K,V](k: K): Lens[Map[K,V], Option[V]] =
Lens(
get = m => m.get(k),
set = (optV, m) => optV match {
case None => m – k
case Some(v)=> m + (k -> v)
}
)
Map
1“one”
2“two”
1“one”
at(“three”).set(Some(3), m)
1“one”
2“two”
at(“two”).set(None, m)
3“three”
What Next?
Optic f g
Iso S => A A => S
Prism S => Option[A] A => S
Lens S => A (A, S) => S
Optional
Optic f g
Iso S => A A => S
Prism S => Option[A] A => S
Lens S => A (A, S) => S
Optional S => Option[A] (A, S) => S
Optional
PrismLens
Iso
Optional
case class Optional[S,A](
getOption: S => Option[A],
set :(A, S) => S
)
Properties:
For all s: S, getOption(s) map set(_, s) == Some(s)
For all a: A, s: S, getOption(set(a, s)) == Some(a) || None
Head
def cons[A]: Prism[List[A], (A, List[A])] = …
def first[A, B]: Lens[(A, B), A] = …
def head[A]: Optional[List[A], A] = cons compose first
head.getOption(List(1,2,3)) = Some(1)
head.set(0, List(1,2,3)) = List(0,2,3)
Void
def void[S,A]: Optional[S, A] =
Optional(
getOption = s => None,
set = (a, s) => s
)
void.getOption(“Hello”) = None
void.set(1,“Hello”) = “Hello”
void.setOption(1,“Hello”) = None
Index
def index[A](i: Int): Optional[List[A], A] =
if(i < 0) void
else if(i == 0) head
else cons compose second compose index(i – 1)
index(-1).getOption(List(1,2,3)) == None
index(1).getOption(List(1,2,3)) == Some(2)
index(5).getOption(List(1,2,3)) == None
index(1).set(10, List(1,2,3)) == List(1,10,3)
Index for Vector
def vectorToList[A]: Iso[Vector[A], List[A]] = …
def indexV(i: Int): Optional[Vector[A], A] =
vectorToList compose index(i)
Index != At
3 5 6 2
0 1 2 3
3 5 99 2
0 1 2 3
index(2).set(99, l)
val l = List(3,5,6,2)
3 5 6 2 ? ? ? 99
0 1 2 3 4 5 6 7
3 5 6 2
0 1 2 3
index(7).set(99, l)
Study Case: Http Request
sealed trait Method
case object GET extends Method
case object POST extends Method
case class URI(
host: String, port: Int,
path: String, query: Map[String, String]
)
case class Request(
method: Method, uri: URI,
headers: Map[String, String], body: String
)
Study Case: Http Request
val r = Request(
method = GET,
uri = URI(“localhost”,8080,“/ping”,Map(“age”->“15”)),
headers = Map.empty,
body = “”
)
Which method?
val method: Lens[Request, Method] = …
val GET: Prism[Method, Unit] = …
method.get(r) // GET
(method compose GET).isMatching(r) // true
How to update host?
val uri: Lens[Request, URI] = …
val host: Lens[URI, String] = …
(uri compose host).set(“foo.io”)(r)
Request(
method = GET,
uri = URI(“foo.io”,8080,“/ping”,Map(“age”->“15”)),
headers = Map.empty,
body = “”
)
How to increase age query?
val query: Lens[URI, Map[String, String]] = …
(uri compose query compose
index(“age”) compose stringToInt
).modify(_ + 1)(r)
Request(
method = GET,
uri = URI(“localhost”,8080,“/ping”,Map(“age”->“16”)),
headers = Map.empty,
body = “”
)
How to add header?
val headers: Lens[Request, Map[String, String]] = …
(headers compose at(“Content-Length”)).set(Some(“0”))(r)
Request(
method = GET,
uri = URI(“localhost”,8080,“/ping”,Map(“age”->“15”)),
headers = Map(“Content-Length”->“0”),
body = “”
)
More power!!!
val r2 = Request(
method = POST,
uri = URI(“localhost”,8080,“/pong”,Map.empty),
headers = Map(“x-custom1”->“5”, “x-custom2”->“10”),
body = “Hello World”
)
Traversal
(headers compose
filterIndex(_.startsWith(“x-”)) compose
stringToInt
).modify(_ * 2)(r2)
Request(
method = POST,
uri = URI(“localhost”,8080,“/pong”,Map.empty),
headers = Map(“x-custom1”->“10”, “x-custom2”->“20”),
body = “Hello World”
)
Optional
PrismLens
Iso
Traversal
Setter
Getter
Fold
Monocle goodies
▪ Provides lots of built-in optics and functions
▪ Macros for creating Lenses, Iso and Prism
▪ Syntax to use optics as infix operator
▪ Experimental state support
Resources
▪ Monocle on github
▪ Simon Peyton Jones’s lens talk at Scala Exchange 2013
▪ Edward Kmett on Lenses with the State Monad
Acknowledgement
▪ Member Monocle and Cats gitter channel for advice and
review
▪ Special thanks to Ilan Godik (@NightRa) for helping with
slides and content
Thank you!

Beyond Scala Lens

  • 1.
    Beyond Scala Lenses github:@julien-truffaut or twitter: @JulienTruffaut
  • 2.
    Function S A A functiontransforms all s in S into an A
  • 3.
  • 4.
    Isomorphism S A For alls: S, g(f(s)) == s For all a: A, f(g(a)) == a f g
  • 5.
    Iso case class Iso[S,A]( get: S => A, reverseGet: A => S ) Properties: For all s: S, reverseGet(get(s)) == s For all a: A, get(reverseGet(a)) == a
  • 6.
  • 7.
  • 8.
  • 9.
    Iso Derived Methods caseclass Iso[S,A]( get : S => A, reverseGet: A => S ){ def modify(m: A => A): S => S def reverse: Iso[A,S] def compose[B](other: Iso[A,B]): Iso[S,B] }
  • 10.
    Units class Robot{ def moveBy(d:Double): Robot } val nono: Robot = … nono.moveBy(100.5) // Meters nono.moveBy(3) // Kilometers nono.moveBy(-2.5) // Yards
  • 11.
    Safe Unit case classMeter(d: Double) case class Yard(d: Double) class Robot{ def moveBy(m: Meter): Robot } nono.moveBy(Meter(100.5)) nono.moveBy(10) // does not compile nono.moveBy(Yard(3.0)) // does not compile
  • 12.
  • 13.
    Meter to Yard valmeterToYard: Iso[Meter, Yard] = Iso( m => Yard(m.value * 1.09361), y => Meter(y.value / 1.09361) ) meterToYard.get(Meter(200)) == Yard(218.7219999…) nono.moveBy(meterToYard.reverseGet(Yard(2.5))
  • 14.
  • 15.
  • 16.
    Iso Composition case classKilometer(value: Double) case class Mile(value: Double) val meterToKilometer: Iso[Meter, Kilometer] = … val yardToMile : Iso[Yard, Mile] = … val kilometerToMile: Iso[Kilometer, Mile] = meterToKilometer.reverse compose meterToYard compose yardToMile
  • 17.
    Different Representation val stringToList:Iso[String, List[Char]] = Iso(_.toList, _.mkString(“”)) def listToVector[A]: Iso[List[A], Vector[A]] = Iso(_.toVector, _.toList)
  • 18.
    Generic Programming case objectRed val red: Iso[Red, Unit] = Iso(Red => (), () => Red) case class Person(name: String, age: Int) val personToTuple: Iso[Person, (String, Int)] = …
  • 19.
    Iso Properties For alls: S, reverseGet(get(s)) == s For all a: A, get(reverseGet(a)) == a
  • 20.
    Scalacheck def isoLaws[S,A](iso: Iso[S,A])= new Properties { property(“One Way”) = forAll { s: S => iso.reverseGet(iso.get(s)) == s } property(“Other Way”) = forAll { a: A => iso.get(iso.reverseGet(a)) == a } }
  • 21.
    Scalacheck import org.spec2.scalaz.Spec class IsoSpecextends Spec { checkAll(“meter to yard”, isoLaws(meterToYard)) } val meterToYard: Iso[Meter, Yard] = Iso( m => Yard(m.value * 1.09361), y => Meter(y.value / 1.09361) )
  • 22.
    Scalacheck A counter-example is: [Yard(1.518707721E9)](after 24 tries) scala> meterToYard.get(meterToYard.reverseGet( Yard(1.518707721E9))) scala> res0: Yard= Yard(1.5187077210000002E9)
  • 23.
    Relaxed Isomorphism S A For alls: S such as f(s) exists, g(f(s)) == s For all a: A, f(g(a)) == a ? f g f is a Function[S, Option[A]] g is a Function[A, S]
  • 24.
    Prism case class Prism[S,A]( getOption: S => Option[A], reverseGet: A => S ) Properties: For all s: S, getOption(s) map reverseGet == Some(s) || None For all a: A, getOption(reverseGet(a)) == Some(a)
  • 25.
    Pattern matching /Constructor sealed trait List[A] case class Cons[A](h: A, t: List[A]) extends List[A] case class Nil[A]() extends List[A] Cons.unapply(List(1,2,3)) == Some((1, List(2,3))) Cons.unapply(Nil) == None Cons.apply(1, List(2,3)) == List(1,2,3)
  • 26.
    Prism sealed trait List[A] caseclass Cons[A](h: A, t: List[A]) extends List[A] case class Nil[A]() extends List[A] def cons[A]: Prism[List[A], (A, List[A])] = … cons.getOption(List(1,2,3)) == Some((1, List(2,3))) cons.getOption(Nil) == None cons.reverseGet(1, List(2,3)) == List(1,2,3)
  • 27.
    Prism Derived Methods caseclass Prism[S,A]( getOption: S => Option[A], reverseGet: A => S ){ def isMatching(s: S): Boolean def modify(f: A => A): S=> S def modifyOption(f: A => A): S => Option[S] def compose[B](other: Prism[A,B]): Prism[S,B] def compose[B](other: Iso[A,B]): ???[S,B] }
  • 28.
    Iso – Prism Opticf g Iso S => A A => S Prism S => Option[A] A => S def isoToPrism[S,A](iso: Iso[S,A]): Prism[S,A] = Prism( getOption = s => Some(iso.get(s)), reverseGet = iso.reverseGet )
  • 29.
    Iso – Prism caseclass Prism[S,A]{ def compose[B](other: Prism[A,B]): Prism[S,B] def compose[B](other: Iso[A,B]): Prism[S,B] } case class Iso[S,A]{ def compose[B](other: Iso[A,B]): Iso[S,B] def compose[B](other: Prism[A,B]): Prism[S,B] }
  • 30.
    Enum sealed trait Day caseobject Monday extends Day case object Tuesday extends Day val tuesday: Prism[Day, Unit] = … tuesday.getOption(Monday) == None tuesday.getOption(Tuesday) == Some(()) tuesday.reverseGet(()) == Tuesday
  • 31.
    Json sealed trait Json caseclass JNumber(v: Double) extends Json case class JString(s: String) extends Json val jNum: Prism[Json, Double] = … jNum.modify(_ + 1)(JNumber(2.0)) == JNumber(3.0) jNum.modify(_ + 1)(JString(“”)) == JString(“”) jNum.modifyOption(_ + 1)(JString(“”)) == None
  • 32.
    Safe Down Casting valdoubleToInt: Prism[Double, Int] = … doubleToInt.getOption(3.4) == None doubleToInt.getOption(3.0) == Some(3) doubleToInt.reverseGet(5) == 5.0
  • 33.
    Prism Composition sealed traitJson case class JNumber(v: Double) extends Json case class JString(s: String) extends Json val jInt: Prism[Json, Int] = jNum compose doubleToInt jInt.getOption(JNumber(3.0)) == Some(3) jInt.getOption(JNumber(5.9)) == None jInt.getOption(JString(“”)) == None
  • 34.
    Where is thebug? def stringToInt: Prism[String, Int] = Prism( getOption = s => Try(s.toInt).toOption, reverseGet = _.toString ) stringToInt.modify(_ * 2)(“5”) == “10” stringToInt.getOption(“5”) == Some(5) stringToInt.getOption(“-3”) == Some(-3) stringToInt.getOption(“5.7”) == None stringToInt.getOption(“99999999999999999”) == None stringToInt.getOption(“Hello”) == None
  • 35.
  • 36.
    Prism S A BCOr Or sealed trait S class A extends S class B extends S class C extends S
  • 37.
    Lens S A BCAnd And case class S(a: A, b: B, c: C)
  • 38.
    Lens case class Lens[S,A]( get:S => A, set:(A, S) => S ) Properties: For all s: S, set(get(s), s) == s For all a: A, s: S, get(set(a, s)) == a
  • 39.
    Iso – Lens Opticf g Iso S => A A => S Lens S => A (A, S) => S def isoToLens[S,A](iso: Iso[S,A]): Lens[S,A] = Lens( get = iso.get, set = (a, _) => iso.reverseGet(a) )
  • 40.
    Accessors case class Person(name:String, age: Int) val age = Lens[Person, Int](_.age, (a, p) => p.copy(age = a)) val zoe = Person(“Zoe”, 25) age.get(zoe) == 25 age.set(20, zoe) == Person(“Zoe”, 20) age.modify(_ + 1)(zoe) == Person(“Zoe”, 26)
  • 41.
    Nested Accessors case classPerson(name: String, age: Int, address: Address) case class Address(streetNumber: Int, StreetName: String) val address: Lens[Person, Address] = … val streetName: Lens[Address, String] = … val zoe = Person(“Zoe”, 25, Address(10, “High Street”)) (address compose streetName).get(zoe) == “High Street” (address compose streetName).set(“Iffley Road”, zoe) == Person(“Zoe”, 25, Address(10, “Iffley Road”))
  • 42.
    Tuples def first[A,B]: Lens[(A,B),A] = … def second[A,B]: Lens[(A,B), B] = … val tuple = (2,“Zoe”) first.set(4, tuple) == (4,“Zoe”) second.modify(_.reverse)(tuple) == (2,“eoZ”)
  • 43.
    Universal Case ClassAccessors case class Person(name: String, age: Int) val personToTuple: Iso[Person,(String,Int)] = … val zoe = Person(“Zoe”, 25) (personToTuple compose second).set(30, zoe) == Person(“Zoe”, 30)
  • 44.
    At def at[K,V](k: K):Lens[Map[K,V], Option[V]] = Lens( get = m => m.get(k), set = (optV, m) => optV match { case None => m – k case Some(v)=> m + (k -> v) } )
  • 45.
  • 46.
    What Next? Optic fg Iso S => A A => S Prism S => Option[A] A => S Lens S => A (A, S) => S
  • 47.
    Optional Optic f g IsoS => A A => S Prism S => Option[A] A => S Lens S => A (A, S) => S Optional S => Option[A] (A, S) => S
  • 48.
  • 49.
    Optional case class Optional[S,A]( getOption:S => Option[A], set :(A, S) => S ) Properties: For all s: S, getOption(s) map set(_, s) == Some(s) For all a: A, s: S, getOption(set(a, s)) == Some(a) || None
  • 50.
    Head def cons[A]: Prism[List[A],(A, List[A])] = … def first[A, B]: Lens[(A, B), A] = … def head[A]: Optional[List[A], A] = cons compose first head.getOption(List(1,2,3)) = Some(1) head.set(0, List(1,2,3)) = List(0,2,3)
  • 51.
    Void def void[S,A]: Optional[S,A] = Optional( getOption = s => None, set = (a, s) => s ) void.getOption(“Hello”) = None void.set(1,“Hello”) = “Hello” void.setOption(1,“Hello”) = None
  • 52.
    Index def index[A](i: Int):Optional[List[A], A] = if(i < 0) void else if(i == 0) head else cons compose second compose index(i – 1) index(-1).getOption(List(1,2,3)) == None index(1).getOption(List(1,2,3)) == Some(2) index(5).getOption(List(1,2,3)) == None index(1).set(10, List(1,2,3)) == List(1,10,3)
  • 53.
    Index for Vector defvectorToList[A]: Iso[Vector[A], List[A]] = … def indexV(i: Int): Optional[Vector[A], A] = vectorToList compose index(i)
  • 54.
    Index != At 35 6 2 0 1 2 3 3 5 99 2 0 1 2 3 index(2).set(99, l) val l = List(3,5,6,2) 3 5 6 2 ? ? ? 99 0 1 2 3 4 5 6 7 3 5 6 2 0 1 2 3 index(7).set(99, l)
  • 55.
    Study Case: HttpRequest sealed trait Method case object GET extends Method case object POST extends Method case class URI( host: String, port: Int, path: String, query: Map[String, String] ) case class Request( method: Method, uri: URI, headers: Map[String, String], body: String )
  • 56.
    Study Case: HttpRequest val r = Request( method = GET, uri = URI(“localhost”,8080,“/ping”,Map(“age”->“15”)), headers = Map.empty, body = “” )
  • 57.
    Which method? val method:Lens[Request, Method] = … val GET: Prism[Method, Unit] = … method.get(r) // GET (method compose GET).isMatching(r) // true
  • 58.
    How to updatehost? val uri: Lens[Request, URI] = … val host: Lens[URI, String] = … (uri compose host).set(“foo.io”)(r) Request( method = GET, uri = URI(“foo.io”,8080,“/ping”,Map(“age”->“15”)), headers = Map.empty, body = “” )
  • 59.
    How to increaseage query? val query: Lens[URI, Map[String, String]] = … (uri compose query compose index(“age”) compose stringToInt ).modify(_ + 1)(r) Request( method = GET, uri = URI(“localhost”,8080,“/ping”,Map(“age”->“16”)), headers = Map.empty, body = “” )
  • 60.
    How to addheader? val headers: Lens[Request, Map[String, String]] = … (headers compose at(“Content-Length”)).set(Some(“0”))(r) Request( method = GET, uri = URI(“localhost”,8080,“/ping”,Map(“age”->“15”)), headers = Map(“Content-Length”->“0”), body = “” )
  • 61.
    More power!!! val r2= Request( method = POST, uri = URI(“localhost”,8080,“/pong”,Map.empty), headers = Map(“x-custom1”->“5”, “x-custom2”->“10”), body = “Hello World” )
  • 62.
    Traversal (headers compose filterIndex(_.startsWith(“x-”)) compose stringToInt ).modify(_* 2)(r2) Request( method = POST, uri = URI(“localhost”,8080,“/pong”,Map.empty), headers = Map(“x-custom1”->“10”, “x-custom2”->“20”), body = “Hello World” )
  • 63.
  • 64.
    Monocle goodies ▪ Provides lotsof built-in optics and functions ▪ Macros for creating Lenses, Iso and Prism ▪ Syntax to use optics as infix operator ▪ Experimental state support
  • 65.
    Resources ▪ Monocle on github ▪ SimonPeyton Jones’s lens talk at Scala Exchange 2013 ▪ Edward Kmett on Lenses with the State Monad
  • 66.
    Acknowledgement ▪ Member Monocle andCats gitter channel for advice and review ▪ Special thanks to Ilan Godik (@NightRa) for helping with slides and content
  • 67.