Successfully reported this slideshow.
Your SlideShare is downloading. ×

Lenses for the masses - introducing Goggles

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
CoffeeScript
CoffeeScript
Loading in …3
×

Check these out next

1 of 86 Ad

Lenses for the masses - introducing Goggles

Download to read offline

Lenses, or more generally “optics”, are a technique that is indispensable to modern functional programming. However, implementations have veered between two extremes: incredible abstractive power with a steep learning curve; and limited domain-specific uses that can be picked up in minutes. Why can’t we have our cake and eat it too?

Goggles is a new Scala macro built over the powerful & popular Monocle optics library. It uses Scala’s macros and scandalously flexible syntax to create a compiler-checked mini-language to concisely construct, compose and apply optics, with a gentle, familiar interface, and informative compiler errors.

In this talk, I introduce the motivation for lenses, why lens usability is a problem that badly needs solving, and how the Goggles library, with Monocle, addresses this in an important way.

Lenses, or more generally “optics”, are a technique that is indispensable to modern functional programming. However, implementations have veered between two extremes: incredible abstractive power with a steep learning curve; and limited domain-specific uses that can be picked up in minutes. Why can’t we have our cake and eat it too?

Goggles is a new Scala macro built over the powerful & popular Monocle optics library. It uses Scala’s macros and scandalously flexible syntax to create a compiler-checked mini-language to concisely construct, compose and apply optics, with a gentle, familiar interface, and informative compiler errors.

In this talk, I introduce the motivation for lenses, why lens usability is a problem that badly needs solving, and how the Goggles library, with Monocle, addresses this in an important way.

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Similar to Lenses for the masses - introducing Goggles (20)

Advertisement

Recently uploaded (20)

Advertisement

Lenses for the masses - introducing Goggles

  1. 1. LENSES FOR THE MASSES …introducin g Goggles @KenScambler λ
  2. 2. FP badly needs optics 1
  3. 3. class Pos { var x: Int = _ var y: Int = _ } class Entity { var pos: Pos = _ var embubbled: Boolean = _ } class Game { var player1, player2: Entity = _ var monsters: ListBuffer[Entity] = _ var bubbles: ListBuffer[Bubble] = _ var score: Int = _ }
  4. 4. game.player1.pos.x 208
  5. 5. game.player1.pos.x += 10 218
  6. 6. “Assignment… leads us into a thicket of difficult conceptual issues.” Ch. 3.1.2
  7. 7. “Nevertheless… it is a powerful technique for maintaining a modular design.” Ch. 3.1.2
  8. 8. • Tracks state over time • Local change instantly propagates without external knowledge or orchestration • Efficient • Can’t reason locally • Can’t substitute expressions with result • Spooky action at a distance • Disadvantages are infectious • Forget about concurrency • Hard to isolate things for testing • Can’t fit behaviour patterns in your head GOOD BAD Mutable?
  9. 9. case class Pos(x: Int, y: Int) sealed trait Entity { def pos: Pos } case class Monster(pos: Pos, embubbled: Boolean) extends Entity case class Player(pos: Pos, lives: Int) extends Entity case class Bubble(pos: Pos) extends Entity case class Game( player1: Player, player2: Option[Player] monsters: List[Monster], bubbles: List[Bubble], score: Int)
  10. 10. game.player.pos.x 208
  11. 11. game.copy(player = game.player.copy(pos = game.player.pos.copy(x = game.player.pos.x + 10 ) ) ) 218
  12. 12. • Referential transparency • Local reasoning • Equational reasoning • Easily testable • Need to manually orchestrate position • Need to know path information within world • Need to know how to recreate in the world • Need to write a lot of stuff • Repetitive, hard to read • Inefficient Immutable? GOOD BAD
  13. 13. Apparently it is terrible at a bunch of common problems 😳 FP is supposed to be all about modularity!
  14. 14. set: S  A  S get: S  A LENS
  15. 15. (X,Y) e => e.pos e => p => e.copy(pos = p) LENS pos
  16. 16. (X,Y) p => x p => x2 => p.copy(x = x2) LENS Int x
  17. 17. (X,Y) LENS Int pos x
  18. 18. LENS Int posX
  19. 19. 208 (playerL ∘ posL ∘ xL).get(game)
  20. 20. 218 (playerL ∘ posL ∘ xL).modify(game)(_ + 10)
  21. 21. setBack: A  S maybeGet: S  S | A PRISM
  22. 22. { case m: Player => Right(m) case e => Left(e) } PRISM asPlayer
  23. 23. TRAVERSAL
  24. 24. ISO
  25. 25. monsters each Lens Traversal embubbled Bool Lens
  26. 26. Traversal monstersEmbubblement Bool
  27. 27. (monsters ∘ each ∘ embubbled).getList(game) false true false false falsefalse false
  28. 28. Iso Lens (X,Y) ×1 ×1 Prism ×0-1 Traversal ×0-n
  29. 29. Optics libraries are too hard to learn 2
  30. 30. data Player = Player { _playerPos :: Pos , _lives :: Int } data Pos = Pos { _x :: Int, _y :: Int } data Monster = Monster { _monsterPos :: Pos , _embubbled :: Bool } data Bubble = Bubble { _bubblePos :: Pos } data Game = Game { _player1 :: Player , _monsters :: [Monster] , _bubbles :: [Bubble] , _score :: Int } makeLenses ''Player makeLenses ''Pos makeLenses ''Monster makeLenses ''Bubble makeLenses ''Game
  31. 31. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 view monsters g  Lens (1,2) (33,88)(44,102)
  32. 32. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 toListOf (monsters.each) g  Traversal (1,2) (33,88)(44,102)
  33. 33. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 toListOf (monsters.each.monsterPos) g  Traversal (1,2) (44,102) (33,88)
  34. 34. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 toListOf (monsters.each.monsterPos.x) g  Traversal 1, 44, 33
  35. 35. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 set (monsters.each.monsterPos.x) 11 g  (11,2) (11,88)(11,102)
  36. 36. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 over (monsters.each.monsterPos.x) (+1) g  (2,2) (34,88)(45,102)
  37. 37. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 monsters.each.monsterPos.x .~ 11 $ g  (11,2) (11,88)(11,102)
  38. 38. import Control.Lens g = Game (Player (Pos 33 22) 3) [ Monster (Pos 1 2) True , Monster (Pos 44 102) False , Monster (Pos 33 88) False ] [] 1000 monsters.each.monsterPos.x %~ (+1) $ g  (2,2) (34,88)(45,102)
  39. 39. class ( Choice p, Corepresentable p, Comonad (Corep p), Traversable (Corep p), Strong p, Representable p, Monad (Rep p), MonadFix (Rep p), Distributive (Rep p), Costrong p, ArrowLoop p, ArrowApply p, ArrowChoice p, Closed p) => Conjoined p where ...
  40. 40. Boring problems demand boring solutions!
  41. 41. Easy solutions are too limited 3
  42. 42. (def users [{:name "Linda" :age 30} {:name "Tom" :age 20}]) (get-in users [0 :name]) ”Linda” (update-in users [1 :age] + 1)  [{:name ”Linda" :age 30} {:name ”Tom" :age 21}] (assoc-in users [1 :name] "Sparkles")  [{:name ”Linda" :age 30} {:name ”Sparkles" :age 20}])
  43. 43. [{"name": "Linda", "age": 30}, {"name": "Tom", "age": 20}] .[0].name “Linda” .[].age += 1  [{"name": "Linda", "age": 31}, {"name": "Tom", "age": 21}] .[1].name |= "Sparkles”  [{"name": "Linda", "age": 30}, {"name": ”Sparkles", "age": 20}]
  44. 44. <people> <person> <name>Linda</name> <age>30</age> </person <person> <name>Tom</name> <age>20</age> </person> </people> /people/person[1]/name  <name>Linda</name> /people/person/age  <age>30</age> <age>20</age>
  45. 45. [1,2,3]*.multiply(2) [2,4,6]
  46. 46. grocery?.shelf?.box?.bananas
  47. 47. grocery?.shelf?.box?.bananas
  48. 48. grocery?.shelf?.box?.bananas
  49. 49. grocery?.shelf?.box?.bananas
  50. 50. Solutions that cut corners don’t scale.
  51. 51. Best of both worlds? 4
  52. 52. class Lens[S,A] class Traversal[S,A] class Prism[S,A] class Iso[S,A] class Fold[S,A] class Getter[S,A] class Setter[S,A] class Optional[S,A]
  53. 53. import monocle.macros.GenLens case class Pos(x: Int, y: Int) object Pos { val x: Lens[Pos,Int] = GenLens[Pos](_.x) val y: Lens[Pos,Int] = GenLens[Pos](_.y) }
  54. 54. import monocle.macros.Lenses @Lenses case class Pos(x: Int, y: Int) @Lenses case class Monster(pos: Pos, embubbled: Boolean) // in build.sbt addCompilerPlugin( "org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full)
  55. 55. Game.monsters.composeTraversal(each). composeLens(Monster.pos). composeLens(Pos.x).set(11)(g)  (11,2) (11,88)(11,102)
  56. 56. Goggles https://github.com/kenbot/goggles
  57. 57. Goggles principles • You already know how to use it • Gets out of the way of experts • Fabulous first-class error messages
  58. 58. Goggles syntax import goggles._ get"$g.monsters[1].embubbled” set"$g.monsters*.pos.x" := 55555 set"$g.player2?.lives" ~= (_ + 3)
  59. 59. val g: Game val posL: Lens[Monster, Pos] get"${g}.monsters*.${posL}.x"
  60. 60. ${game} ".monsters*." ${posL} ".x" Macro ERROR { or … } Mode = GET ""
  61. 61. ".monsters*." ".x" Hole Name(monsters) Each Hole Name(x) List[String] AST ""
  62. 62. trait Parse[T, Arg, A] { def apply(state: ParseState[T,Arg]): (Either[GogglesError[T],A], ParseState[T,Arg]) } Interpreter monad
  63. 63. (List[Arg], List[Info]) (Either[Error,A], List[Arg], List[Info]) Interpreter monad
  64. 64. ${game} ${posL}Args: Info: AST: Tree: Hole Name(monsters) Each Hole Name(x)
  65. 65. ${game} ${posL}Args: Info: AST: Hole Tree: ”$game", Unit, Game, Iso, Iso const(game) Name(monsters) Each Hole Name(x)
  66. 66. Info: AST: Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) ".monsters", Game, List[Monster], Getter, Getter ${posL}Args: Name(monsters) Each Hole Name(x)
  67. 67. Info: AST: Each Hole Name(x) Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) .composeTraversal(Each.each) ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], ??????, Traversal, Fold ${posL}Args: How to learn the type of values?
  68. 68. Info: AST: Each Hole Name(x) Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) .composeTraversal(Each.each) ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], Monster, Traversal, Fold ${posL}Args: typecheck( q"implicitly[ _root_.monocle.function. Each[List[Monster], _]]") Typechecking will fill it in for us
  69. 69. Info: AST: Each Hole Name(x) Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) .composeTraversal(Each.each) ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], Monster, Traversal, Fold ${posL}Args: Got it!
  70. 70. ${posL} Args: Info: AST: Hole Name(x) Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) .composeTraversal(Each.each) .composeLens(posL) ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], Monster, Traversal, Fold ”$posL", Monster, Pos, Lens, Fold
  71. 71. Info: AST: Name(x) Tree: ”$game", Unit, Game, Iso, Iso const(game).composeGetter(Getter(_.monsters)) .composeTraversal(Each.each) .composeLens(posL) .composeGetter(Getter(_.x)) ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], Monster, Traversal, Fold ”$posL", Monster, Pos, Lens, Fold ".x", Pos, Int, Getter, Fold
  72. 72. Info: Error: NameNotFound("BLAH", Int) ”$game", Unit, Game, Iso, Iso ".monsters", Game, List[Monster], Getter, Getter "*", List[Monster], Monster, Traversal, Fold ”$posL", Monster, Pos, Lens, Fold ".x", Pos, Int, Getter, Fold get”$g.monsters*.$posL.x.BLAH"
  73. 73. The future…? 5
  74. 74. Optics are here to stay! • Benefits of immutability • Benefits of mutation • Vast abstractive power
  75. 75. Optics are here to stay! • Built-in support is really needed for good usability • Future languages will have built-in optics • Perhaps Goggles can help inform this process! Watch this space!

×