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.
10. 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 = _
}
14. “Nevertheless… it is a powerful
technique for maintaining a
modular design.”
Ch. 3.1.2
15. • 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?
16. 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)
19. • 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
20. Apparently it is terrible at a bunch of
common problems
😳
FP is supposed to be all about modularity!
76. 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)
77. 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?
78. 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
79. 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!
85. Optics are here to
stay!
• Benefits of immutability
• Benefits of mutation
• Vast abstractive power
86. 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!