1 of 61

Haskell Lecture #10

September 9, 2015

2 of 61

Penultimatum

This is the second-to-last session. Last is on Sept. 23.

I want to focus my energy on building up the Chicago Haskell community. Planning on a 1- or 2-day weekend Haskell class (probably Dec. 5-6).

Last class will be a survey of other topics and include a list of further reading.

3 of 61

Topic of the day: lenses

4 of 61

We often deal with nested records

type Party = [Character]

data Character = Character {

name :: String, klass :: String,

maxHP :: Int, maxMP :: Int, status :: Status}

data Status = Status {currentHP :: Int,

currentMP :: Int}

5 of 61

recall: update syntax

farisa = Character {name = "Farisa",

klass = "Mage",

maxHP = 47,

maxMP = 83,

status = Status {currentHP = 34,

currentMP = 62}}

-- a heal spell

farisa {status = (status farisa) {

currentHP = (min (maxHP farisa) (25 + (currentHP . status $ farisa)),

currentMP = (currentMP . status $ farisa) - 4}

}

6 of 61

Writing a heal spell

farisa {

status = (status farisa) {

currentHP =

(min (maxHP farisa)

(25 + (currentHP .

status $ farisa)),

currentMP =

(currentMP . status $

farisa) - 4}}

farisa.status.mp -= 4

farisa.status.hp += 25

farisa.status.hp =

min(farisa.status.hp,

farisa.maxHP)

“Imperative way” looks cleaner!

7 of 61

Update syntax...

… is not easy to use.

It’s especially when dealing with nested data structures.

What if we could have nice-looking updates in a functional way?

8 of 61

A Lens, conceptually

A Lens captures imperative programming’s often-convenient notion of place.

farisa.status.hp += 25 isn’t about her current hit point value; it’s about the place where the character’s HP is stored.

A place can be get or set.

9 of 61

Purely functional get and set

getA :: s -> a

Not fundamentally different in FP vs. OOP.

setA :: s -> a -> s

setAIntoB :: s -> b -> t (t = s[b/a])

A new object is returned; the old one is not modified. Remember that data sharing makes this performant.

10 of 61

Helper methods...

getHP = currentHP . status

setHP character n =

character {status =

(status character) {currentHP = n}}

11 of 61

Problems with helper methods

  • Clean getters but ugly setters.
  • Repetitive ugly boilerplate.
  • Not always easy to compose.
  • Getters written separately from setters, meaning that you have to encode your notion of “place” twice.

12 of 61

Setter/getter pairs

data Place s a =

Place {getter :: s -> a; setter :: s -> a -> s}

currentHPPlace character = Place {

getter = currentHP . status,

setter =

character {status = (status character) {currentHP = n}}

(setter . currentHPPlace) farisa ((getter . currentHPPlace) farisa + 25)

13 of 61

setter/getter pair helper methods...

{-# LANGUAGE RecordWildCards #-}

get' :: Place s a -> s -> a

get' Place{..} = getter

set' :: Place s a -> s -> a -> s

set' Place{..} = setter

modify' :: Place s a -> s -> (a -> a) -> s

modify' place obj f = set' place obj (f (get' place obj))

modify' currentHPPlace farisa (+25)

14 of 61

Three things missing...

  1. Attractive, concise syntax.
    • farisa.status.currentHP += 25 is better.
    • farisa.currentHP += 25 is even better.
  2. Changes of types. Might we be able to improve on Place s a with something more flexible?
  3. Composition. If we have a Place s t and Place t a, can we set or get on a Place s a?

15 of 61

type Lens s t a b

type Lens s t a b

A purely functional reference to an a inside an s.

view :: Lens s t a b -> s -> a

set :: Lens s t a b -> s -> b -> t

over :: Lens s t a b -> (a -> b) -> s -> t

16 of 61

_1 on type (a, b)

_1 :: Lens (a, b) (a, b) a a

λ view _1 (1, 2)

1

λ set _1 8 (1, 2)

(8,2)

λ over _1 (+3) (1, 2)

(4,2)

17 of 61

_1 is more general...

_1 :: Lens (a, b) (c, b) a c

λ set _1 "cat" (1, 2)

("cat",2)

_1 :: Lens (a, b, c) (d, b, c) a d

λ set _1 "cat" (5, 7, [Nothing, Just 3])

("cat",7,[Nothing,Just 3])

18 of 61

19 of 61

Actual type sig. of _1

λ :t _1

_1 :: (Functor f, Field1 s t a b) =>

(a -> f b) -> s -> f t

Field1 is a typeclass that allows _1 to cover multiple tuple types. It’s more versatile than fst, which requires a pair.

The use of Functor will make sense when we describe what Lenses actually are.

20 of 61

Lenses compose

λ view (_3 . _2) (8, "dog", (32, 'X'))

'X'

Something weird here. Anyone see it?

21 of 61

Lenses compose

λ view (_3 . _2) (8, "dog", (32, 'X'))

'X'

Something weird here. Anyone see it?

Lenses compose “backward”.

22 of 61

Lens composition

A lens’s “backward” composition makes sense in light of this interpretation (“small to big”) of a Lens:

a Lens s t a b maps an a-to-b transformation at the “focus” into an s-to-t transformation.

_3 . _2 = “Focus on the 3rd element, then focus on the 2nd element of that.”

OR: Mapping from transformations on the (_3 . _2) “place” to those on the larger object.

23 of 61

Lens s t a b = ??

Lens s t a b = (a -> b) -> (s -> t)??

Not quite. That can cover sets but not gets. Without gets, it’s not that useful.

24 of 61

Lens s t a b = ??

Lens s t a b = (a -> b) -> (s -> t)??

Not quite. That can cover sets but not gets. Without gets, it’s not that useful.

Lens s t a b = forall f . Functor f =>

(a -> f b) -> (s -> f t)

25 of 61

f = Identity for over

When f = Identity, the Lens type becomes

(a -> Identity b) -> (s -> Identity t), which is isomorphic to (a -> b) -> (s -> t).

over :: Lens s t a b -> (a -> b) -> (s -> t)

over lens f =

runIdentity . (lens (Identity . f))

26 of 61

set from over

set is just a subcase of over, with f = (\_ -> val), also known as const val.

set :: Lens s t a b -> b -> s -> t

set lens val obj = over lens (const val) obj

27 of 61

What about getters?

We’ve seen how (f = Identity) to get from

Functor f => (a -> f b) -> (s -> f t)

to the setter’s type signature of:

(a -> b) -> (s -> t).

How do we get an s -> a? What Functors might help?

28 of 61

f = Const a for view

When f = Const a, the Lens type becomes

(a -> Const a b) -> (s -> Const a t), which is isomorphic to (a -> a) -> (s -> a).

Conveniently, we have an a -> a for any a. It’s id.

view lens = getConst . (lens Const)

29 of 61

Other Functors...

f = Identity → iso. to (a -> b) -> (s -> t)

f = Const a → iso. to (a -> a) -> (s -> a), usable as s -> a.

f = [] → (a -> [b]) -> s -> [t]

f = Maybe → (a -> Maybe b) -> s -> Maybe t

f = IO → (a -> IO b) -> s -> IO t

f = State r → (a -> State r b) -> s -> State r t

30 of 61

A lens... from a setter and getter?

type Lens s t a b = forall f . (Functor f) =>

(a -> f b) -> (s -> f t)

lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b

lens getter setter =

\f obj ->

(setter obj) `fmap` (f $ getter obj)

Don’t feel bad if this is not at all intuitive.

31 of 61

Ok, so why is this useful?

Recall that we wanted to capture the notion of place in a larger data structure, and use familiar notions of set/get.

farisa.status.mp -= 4

farisa.status.hp += 25

farisa.status.hp =

min(farisa.status.hp,

farisa.maxHP)

32 of 61

Creating your own Lenses

data Character = Character {

name :: String, class_ :: String,

maxHP :: Int, maxMP :: Int, status :: Status}

nameLens = lens name (\c v -> c {name = v})

… works but not necessary!

33 of 61

Auto-deriving lenses

Template Haskell is Haskell’s analogue to Lisp macros. It’s (mostly) beyond the scope of the course.

This is one tool set that I haven’t been able to make work in ghci. So, if you want to auto-derive lenses on your own datatypes, you need to compile.

34 of 61

makeLenses example

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data Card =

Card {_rank :: Int,

_suit :: Char} deriving (Eq, Show)

data Board =

Board {_flop :: (Card, Card, Card),

_turn :: Card, _river :: Card} deriving (Eq, Show)

makeLenses ''Card

makeLenses ''Board

λ :t _rank

_rank :: Card -> Int

λ :t rank

rank :: Functor f =>

(Int -> f Int) -> Card -> f Card

-- Lens Card Card Int Int

λ view rank (Card 8 'h')

8

λ set suit 'd' (Card 8 'h')

Card {_rank = 8, _suit = 'd'}

35 of 61

makeLenses example

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data Card =

Card {_rank :: Int,

_suit :: Char} deriving (Eq, Show)

data Board =

Board {_flop :: (Card, Card, Card),

_turn :: Card, _river :: Card} deriving (Eq, Show)

makeLenses ''Card

makeLenses ''Board

λ :t (flop . _1 . rank)

(flop . _1 . rank) :: Functor f =>

(Int -> f Int) -> Board -> f Board

λ let board = Board

(Card 8 'd', Card 9 'h', Card 2 's')

(Card 6 'h') (Card 10 'c')

λ view (flop . _3) board

Card {_rank = 2, _suit = 's'}

λ view (flop . _3 . suit) board

's'

36 of 61

Lens laws

  1. Set-get: set lens (view lens obj) obj = obj
  2. Get-set: view lens (set lens obj x) = x
  3. Set-set: set lens (set lens obj b) a = � set lens obj a

These stipulate that view and set round-trip in the expected way and that a 2nd set obliterates the first one.

37 of 61

With great power comes great responsibility…

38 of 61

Beyond view, over, and set

The lens library has a lot (over 100!) of infix operators. They make getting and setting into nested data structures a lot more attractive (arguably).

There’s a lot to learn here and the best way is to look at examples.

39 of 61

(&) :: a -> (a -> b) -> b

(&) = flip ($)

Since lenses tend to flow left-to-right, this is really useful.

λ ((2, 3), (5, 7)) & view (_1 . _2)

3

λ ((2, 3), (5, 7)) & set _2 "Lost!"

((2,3),"Lost!")

40 of 61

(^.) :: s -> Lens s t a b -> a

(^.) = flip get

λ (5, (27, Nothing)) ^. _2

(27, Nothing)

λ (5, (27, Nothing)) ^. _2 . _1

27

41 of 61

(.~) :: Lens s t a b -> b -> s -> t

(.~) is just an infix set.

λ _1 .~ "Yo" $ (3, 12)

("Yo",12)

λ (3, 12) & _1 .~ "Hi"

("Hi",12)

42 of 61

(%~) :: Lens s t a b -> s -> b -> t

(%~) is just an infix over.

λ _1 %~ (^2) $ (5, "cats")

(25,"cats")

λ (5, "cats") & _2 %~ (map toUpper)

(5,"CATS")

43 of 61

(+~), (-~), (*~), etc.

(+~) :: (Num a) => Lens s t a a -> a -> s -> t

λ (5, "cats") & _1 +~ 10

(15,"cats")

λ (5, "cats") & _1 ^~ 3

(125,"cats")

44 of 61

Something should be bugging the crap out of you...

(5, "cats") & _1 +~ 10 doesn’t look like normal Haskell.

How does it work?

45 of 61

Parsing (5, "cats") & _1 +~ 10

λ :info (&)

infixl 1 &

λ :info (+~)

infixr 4 +~

(5, "c") & _1 +~ 10 →

�(&) (5, "c")

(_1 +~ 10) ~

(_1 +~ 10) $ (5, "c")

over _1 (+10) (5, "c")

46 of 61

(+=), (-=), (*=), etc.

(+=) :: (MonadState s m) =>

Lens s s a a -> a -> m ()

λ (_1 += 10) `runState` (5, "cats")

((),(15,"cats"))

λ do {_1 ^= 5; _2 <>= "!"; get} `evalState`

(2, "Hi")

(32,"Hi!")

47 of 61

Heuristics and organization

There’s some regularity to the infix operators. It takes time to figure it out. In general…

  1. Operators that begin with ^ are kinds of views.
  2. Operators that end with ~ are like over or set. In fact, (.~) == set and (%~) is over.
  3. Operators that have . in them are usually somehow "basic".
  4. Operators that have % in them usually take functions.
  5. Operators that have = in them are just like their cousins where = is replaced by ~, but instead of taking the whole object as an argument, they apply their modifications in a State monad.

Source: A Little Lens Starter Tutorial by Joseph Abrahamson.

48 of 61

at as a lens into maps

λ M.fromList [(1, "one")] ^. at 1

Just "one"

λ M.fromList [(1, "one")] & (at 2) .~ (Just "two")

fromList [(1,"one"),(2,"two")]

λ M.fromList [(1, "one")] & (at 1) .~ Nothing

fromList []

49 of 61

Many, many more...

I’m nowhere near finished with what you can do with lenses. See https://github.com/ekmett/lens/wiki/Examples

for much more.

50 of 61

Beyond lenses

λ :t (+~)

(+~) :: Num a => ASetter s t a a -> a -> s -> t

No reference to lenses per se. It’s more general.

51 of 61

both

You can use the lens-like operators like (+~) with both, which is not a lens and touches more than one location. It’s a (multi-location) setter but not a getter.

λ (2, 3) & both ^~ 6

(64,729)

λ (5, 7) & both .~ "gone"

("gone","gone")

52 of 61

mapped

mapped makes a Functor settable in all locations. It’s not a lens either.

λ [3, 5, 9] & mapped ^~ 2

[9,25,81]

λ (5, ["wørd", ""]) & _2 . mapped %~ length

(5,[4,0])

53 of 61

Prisms, Traversals

Related concepts. Prisms deal with sum types rather than products, and Traversals are setters that can target multiple (or no) locations. With these, you can work on almost all data types (e.g. JSON).

A Lens is a Traversal and a Getter; both and mapped aren’t Getters because they focus on multiple places.

54 of 61

_Left and _Right prisms

λ (Left 5) & _Left +~ 4

Left 9

λ (Left 5) & _Right +~ 4

Left 5

These prisms refer to places that may or may not occur in the respective data.

55 of 61

(^.) with a Monoid

You can only use (^.) on a Prism if your type is a Monoid. You can use to to create a (non-settable) getter that applies a function.

λ (Left 5) ^. _Left . (to Sum)

Sum {getSum = 5}

λ (Left 5) ^. _Right . (to Sum)

Sum {getSum = 0}

56 of 61

(^.) with a Monoid

You can only use (^.) on a Prism if your type is a Monoid. You can use to to create a (non-settable) getter that applies a function.

λ (Left 5) ^. _Left . (to Sum)

Sum {getSum = 5}

λ (Left 5) ^. _Right . (to Sum)

Sum {getSum = 0}

57 of 61

(^..) goes into the list monoid

λ (Left 5) ^.. _Right

[]

λ (Right 9) ^.. _Right

[9]

λ (7, 8) ^.. both

[7,8]

58 of 61

59 of 61

Heal spell revisited

type Party = Map Int Character

heal :: Character -> Character -> (Bool, Character, Character)

heal caster target =

if (caster ^. mp) < 4 then (False, caster, target)

else (True, caster & mp -~ 4, target & hp %= (hpUp 25 tMaxHP))

where hp = status . currentHP

mp = status . currentMP

tMaxHP = target ^. maxHP

hpUp n limit = \hp -> min (hp + n) limit

60 of 61

Finally...

We’ve only scratched the surface of Lens and related types (e.g. Prism, Traversal).

Go here for much more: https://github.com/ekmett/lens/wiki

61 of 61

Questions?