Providence Salumu basic lens operator examples

Intolerable

basic lens operator examples

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 ALenses, which are Lenses 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

Providence Salumu