QFPL @ Data61, CSIRO
sean.chalmers@data61.csiro.au
lens
package.ocaml-optics
, ocaml-lens
.monocle
.-- Getter
(^.) :: s -> Getting a s a -> a
-- Setter
(.~) :: ASetter s t a b -> b -> s -> t
Talk over, right?
data Foo = Foo
{ _petCamelName :: Text
, _petCamelTopSpeed :: Int
}
-- Template Haskell to write the lenses for us
makeLenses ''Foo
_petCamelName foo = "Fred" = foo ^. petCamelName
Yay?
Reaching deeper into a data structure:
data Bar = Bar { _camelPerson :: Foo }
"Sally" = bar ^. camelPerson . petCamelName
--
"Sally" = _petCamelName . _camelPerson $ bar
Lenses compose to make new lenses:
camelPerson :: Lens' Bar Foo
petCamelName :: Lens' Foo Text
Compose these with (.
)
camelPerson . petCamelName :: Lens' Bar Text
Simple replacement using (.~)
(petCamelName .~) :: Text -> Foo -> Foo
--
a & petCamelName .~ "Sally" = a { _petCamelName = "Sally" }
Using reverse apply (&)
:
foo & petCamelName .~ "Sally"
What about updating a thing in a thing in a thing?
nestedUpdate a =
let
nn = _petCamelName . _camelPerson $ a
in
a { _camelPerson =
_camelPerson a {
_petCamelName = nn <> " the Wise"
}
}
Helper.returnNullOrCallFunction(
Helper.returnNullOrCallFunction(
myObject.getSomeOtherObject(),
SomeOtherObject::getAnotherObject
),
AnotherObject::increaseTheThing
);
Taken from: https://stackoverflow.com/a/26414202
nestedUpdate =
-- Using `set with function`
camelPerson . petCamelName %~ (<> " the Wise")
-- Using a mappend setter
camelPerson . petCamelname <>~ " the Wise"
foo & petCamelName .~ "Sally"
& petCamelTopSpeed .~ 48
--
camelPerson %~ \f -> f
& petCamelName .~ "Sally"
& petCamelTopSpeed .~ 48
Apply a function to your lens target
petCamelTopSpeed %~ (+10)
There are heaps of prebaked operators:
petCamelTopSpeed +~ 10 -- Add 10 cabbages
petCamelTopSpeed //~ 2 -- Divide by 2 cabbages
petCamelName <>~ " the Wise" -- Reward victory with a title
Geddit? eh? eh? ...fine.
data A = A { _bars :: [Bar] }
traverse
<3tBars = bars . traverse
-- (^..) is an alias of 'toListOf'
a ^.. tBars . camelPerson . petCamelName
-- Works with setters too, of course
tBars . camelPerson . petCamelTopSpeed +~ 10
-- Can we do this? What is the answer?
a ^. tBars . camelPerson . petCamelName
tBars :: Traversal' A Bar
tBars = bars . traverse
Which we can extend and reuse!
topSpeeds :: Traversal' A Int
topSpeeds = tBars . camelPerson . petCamelTopSpeed
Exercise: Write the same function without lens
topSpeeds :: Applicative f => (Int -> f Int) -> A -> f A
Composing a Getter with a Traversal will yield a Fold.
-- We saw a Fold earlier : (^..)
a ^.. tBars . camelPerson . petCamelName
-- Only want the first camel person?
a ^? bars . _head :: Maybe Bar
Fold Setter
/ \_____/
\ / \
Getter Traversal
/ ____/
\ / \
Lens Prism
\ /
Iso
Update a value in a StateT?
-- Assuming : StateT Foo m
petCamelName .= "Bub" -- Replace
petCamelTopSpeed %= (-10) -- Map
petCamelName <>= " the Swift" -- Mappend
-- Assuming : StateT Bar m
camelPerson . petCamelTopSpeed %= (-10)
Update several different fields on a record.
\n ds ->
ds & dataSet_max %~ max n
& dataSet_min %~ min n
& dataSet_lines . traverse %~
( ( line_end . _Point . _1 +~ stepToTheRight )
. ( line_start . _Point . _1 +~ stepToTheRight )
)
& dataSet_lines %~ (\xs -> addNewDataPoint n xs (uncons xs))
_Point
/ _1
That was a Prism
.
Right 'c' ^? _Left == Nothing
Left 'c' ^? _Left == Just 'c'
5 ^. re _Left == Left 5
( ( Left 5 ) & _Left .~ 6 ) == Left 6
Consider the following JSON:
{ "alpha":
{ "beta":
[.., { "gamma": [.., {"delta": <some bool> }, ...] }, ..]
}
}
gamma
is at index 3, and delta
is at index 2. > Your mission is to flip the value at delta
.a = getAtKey blob "alpha"
if ( null != a ) {
b = getAtKey a "beta" {
if ( null != b && isArray b ) {
c = getAtIndex b 3
if ( null != c ) {
fooList = getAtKey "gamma"
if ( null != fooList && isArray fooList ) {
fooObj = getAtIndex c 2
if ( null != fooObj ) {
fooVal = getBoolValAtKey "delta" fooObj
if ( null != fooVal ) {
newFoo = setValueAtKey "delta" (not fooVal)
return ( setValueAtKey "beta" a
( setValueAtIndex 3 b
( setValueAtKey "gamma" c
( setValueAtIndex 2 fooList newFoo )
)
)
)
}
}
}
}
}
}
}
This pseudo-code is an exaggeration, but not by much and you know it.
Using lens-aeson
and lens
.
( key "alpha"
. key "beta" . _Array . ix 3
. key "gamma" . _Array . ix 2
. key "delta" . _Bool %~ not
) :: AsValue t => t -> t
ahem
The _Array
and _Bool
things are both a Prism
.
"[1,2,3]" ^? _Array
--
[1,2,3] ^. re _Array
Just 3 ^? _Just = Just 3
Nothing ^? _Just = Nothing
('a', Just 3) & _2 . _Just %~ (+1) = ('a', Just 4)
Lens operators are formed from a symbol DSL.
^
- refers to a fold~
- modification or setting?
- results optional<
- return the new value<<
- return the old value%
- use a given function%%
- use a given traversal=
- apply lens to MonadState
.
- which side should have the lenseslet
m = Map.fromList [(1, "susan"), (2, "jo")]
in
m & at 1 .~ "bob"
& at 3 ?~ "pixie"
& at 2 %~ _f :: ??
Modifying a map in your StateT, setting a new value if one exists, but creating a new key-value pair if it doesn't exist, and returning the previous value if there was one?
data A = A
{ _theFoo :: Foo
, _theMap :: Map String Int
}
a <- theMap . at "foo" <<?= 37
Use a lens to target a part of a structure.
REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL REPL
Questions ?
Lets Lens! - https://github.com/data61/lets-lens