one of the most common complaints about the lens
package is how many operators it defines. fortunately, you can get away with using a very minimal set and lens
is still very powerful. here are some examples of use of the operators defined in the core Control.Lens.Lens
part of the library (without all the category theory "nonsense" because you don't need to know that to use the library effectively):
this post assumes that you have some lenses defined on your types (unless you just want to use the pre-defined ones that come with the lens library), but doesn't assume that you have much knowledge of the theory behind lenses – this is not intended in any way to be a lens tutorial
&
: flipped function application
&
is literally just the $
function application with the arguments reversed. fairly straightforward, but very helpful when you're using the lens library since it's so generally useful
(&) = flip ($)
"hello" & reverse = "olleh"
50 & (* 2) = 100
<&>
: flipped fmap
<&>
is to &
as <$>
is to $
, its just x <&> f = fmap f x
. not as commonly used as &
but helpful nonetheless
Just 50 <&> (* 2) = Just 100
[1,2,3,4] <&> show = ["1","2","3","4"]
show <&> reverse $ [1,2,3,4,5] = "]5,4,3,2,1[" -- essentially flip (.)
&~
: state-y function application
lets you use the state combinators on regular not-state values (you can't change the type of the value from within though)
(1, "hello world", [1..5]) &~ do
_2 .= "goodbye friends"
_3 %= (9:)
_1 <~ (head <$> use _3)
= (9, "goodbye friends", [9,1,2,3,4,5])
??
: flipped functor functions
flipped functor function application. if you want to apply a function that's in a functor to something that isn't in a functor, this is your operator
Just (* 5) ?? 5 = Just 25
Nothing ?? 5 = Nothing
[(*2), (*3), (*6)] ?? 40 = [80, 120, 240]
^.
: get (view) from a lens
aka view
. gets you a result of a lens
(1, "what") ^. _2 = "what"
[1,2,3] ^. to reverse = [3,2,1]
-- data Thing = Thing { _boolThing :: Bool, _intThing :: Int }
-- makeLens ''Thing
Thing False 0 ^. boolThing = False
secret sneaky tip: you can use use
to do something similar if you're in a state-y context
.~
: update with a lens
updates a value that you can access from a lens
(1, 2) & _1 .~ 5 = (5, 2)
Thing False 0 & intThing .~ 5 = Thing False 5
Just 5 & _Just .~ 10 = Just 10
%~
: adjust with a lens
adjusts a value that you can access from a lens
("what", "how") & _2 %~ (++ " amazing") = ("what", "how amazing")
Thing False 0 & intThing %~ (+ 5) = Thing False 5
Right "hello" & _Right %~ (,) 0 = Right (0, "hello")
+~
, -~
, *~
, //~
: pure numeric operators
adjustment operators for the associated numeric operators. there are also similar operators for things like logic (&&~
/ ||~
), bit-wise operations (.&.~
/ .|.~
in Data.Bits.Lens), monoidal operations (<>~
), powers (^~
/ ^^~
) etc.
(1, "hello", 5) & _3 *~ 2 = (1, "hello", 10)
(1, "hello", 5) & _1 +~ 6 = (7, "hello", 5)
(1, "hello", 5) & _1 -~ 1 & _3 //~ 2 = (0, "hello", 2.5)
.=
: state update
updates a value inside a state-y context
doSomething :: State Thing ()
doSomething = do
intThing .= 5
boolThing .= True
runState doSomething (Thing False 0) = Thing True 5
%=
: state update
adjusts a value inside a state-y context
doSomething :: State Thing ()
doSomething = do
intThing %= const 15
boolThing %= not
runState doSomething (Thing False 0) = Thing True 15
+=
, -=
, *=
, //=
: numeric state operators
you should be able to guess what these do, they do numeric adjustments inside a state-y context. just like the pure operators, there are matching =
versions of the logic / bit-wise operations and a few others
doSomething :: State (Int, Double, Integer)
doSomething = do
_1 += 1
_2 -= 5.0
_3 *= 4
_2 //= 2
runState doSomething (1, 17.0, 3) = (2, 6.0, 12)
extra rules
if [operator]
modifies a thing, then <[operator]
returns a tuple with the result of the actual modification, plus the modified thing – for example, (1,"hello", 5) & _3 <*~ 2 = (10, (1, "hello, 10))
similarly, if [operator]
modifies a thing, then <<[operator]
returns a tuple with the result of the actual modification, plus the old (i.e. before the modification) result of the modified thing – for example, (1,"hello", 5) & _3 <>*~ 2 = (5, (1, "hello, 10))
if there's an .[operator]
, there's most likely a matching #[operator]
. these are mostly functionally identical but the #
variant works on ALens
es, which are Lens
es that you can put inside a container (since they are rank-1 and won't choke the compiler)
as you can imagine you can find more (and probably more mathematically accurate) information on lens
' haddock pages, and there's a bunch more operators on the Control.Lens.Operators
docs page. beware, there are lots of terrifying category theory bits on there, and if you understand it all you are a more enlightened person than me
i wrote this partially as a personal reference for when im writing some of my own lens-heavy code, which is available on github here. it's pretty shit though, so don't look too hard. if u have any criticisms or comments about this or my code, u can fire an issue at my repo or message me on reddit