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 -> tTalk over, right?
data Foo = Foo
{ _petCamelName :: Text
, _petCamelTopSpeed :: Int
}
-- Template Haskell to write the lenses for us
makeLenses ''Foo_petCamelName foo = "Fred" = foo ^. petCamelNameYay?
Reaching deeper into a data structure:
data Bar = Bar { _camelPerson :: Foo }
"Sally" = bar ^. camelPerson . petCamelName
--
"Sally" = _petCamelName . _camelPerson $ barLenses compose to make new lenses:
camelPerson :: Lens' Bar Foo
petCamelName :: Lens' Foo TextCompose these with (.)
camelPerson . petCamelName :: Lens' Bar TextSimple 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 .~ 48Apply 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 titleGeddit? 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 . petCamelNametBars :: Traversal' A Bar
tBars = bars . traverseWhich we can extend and reuse!
topSpeeds :: Traversal' A Int
topSpeeds = tBars . camelPerson . petCamelTopSpeedExercise: Write the same function without lens
topSpeeds :: Applicative f => (Int -> f Int) -> A -> f AComposing 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 / _1That was a Prism.
Right 'c' ^? _Left == Nothing
Left 'c' ^? _Left == Just 'c'5 ^. re _Left == Left 5( ( Left 5 ) & _Left .~ 6 ) == Left 6Consider 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 -> tahem
The _Array and _Bool things are both a Prism.
"[1,2,3]" ^? _Array
--
[1,2,3] ^. re _ArrayJust 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" <<?= 37Use 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