epicartificials blog

Lenses, Prisms and Traversable

25.04.2020

lens is a package that allows you to focus deeply into a complex structure and retrieve or modify the elements. Lenses work in a similar way to how the imperative style of programming would retrieve elements from a complex object.

In languages like C++, Java, Javascript, Python etc. we would use something like person.children[0].pet.age = 4, but in Haskell without lenses we would have to do some ugly nesting such as person { children = { ... } }.

What the library Control.Lens allowes us to do it make something like person.children.ix 0.pet.age .~ 4

There are many useful operators such as

object ^.  lenses                       -- get single value
object ^.. lenses                       -- get list of values
object ^?  lenses                       -- maybe get a value
object &   lenses .~  value             -- set a value
object &   lenses %~  function          -- apply a function to the value
object &   lenses %%~ function          -- apply a function modifying the result structure
object &   lenses +~  number            -- increment by number
object &   lenses <>~ value             -- append (<>) a value

There are also many predefined lenses such as

_1, _2, ..., both, each       -- get n-th values
_Just, _Right, _Left         -- apply constructors
ix n                         -- get n-th value or key in map
traverse                     -- get all values one-by-one
to f                         -- apply a result function
filtered p                   -- filter by predicate
_head, _last, _init, _tail   -- traverse parts of a list
worded, lined                -- traverse over words / lines

Lets look at an example of how this can be used

First we need to define some example

("hello", "world") ^. _1                 ~>     "hello"
[1, 2, 3, 4, 5] ^. _init                 ~>     [1, 2, 3, 4]

[1, 2, 3, 4, 5] ^.. _last                ~>      [5]
[1, 2, 3, 4, 5] ^.. _init                ~>      [[1, 2, 3, 4]]

("hello", "world") & _1 .~ 5             ~>     (5, "world")
("Hello", "world") & _2 %~ (++ "!!")     ~>     ("Hello", "world!!")

("Hello", "world") ^ _2 %%~ id           ~>     [("Hello",'w'),("Hello",'o'),("Hello",'r'),("Hello",'l'),("Hello",'d')]

[1, 2, 3, 4, 5] & _head +~ 5             ~>     [6, 2, 3, 1, 5]

[1, 2, 3, 4, 5] & _init <>~ [6, 7]       ~>     [1, 2, 3, 4, 6, 7]

[1, 2, 3, 4, 5] & traverse %~ show       ~>     ["1", "2", "3", "4", "5"]
#haskell