--- # intro ## source This is a digest of [Joseph Abrahamson](http://jspha.com/)'s [Basic Lensing FPComplete tutorial](https://www.fpcomplete.com/user/tel/basic-lensing). I have - shown his examples in action - added links - verified examples via unit testing see also: - [Benjamin C. Pierce](http://www.cis.upenn.edu/~bcpierce/) et. al. "Quotient Lenses" ([pdf](http://www.cis.upenn.edu/~bcpierce/papers/quotient-lenses.pdf)) - [Edward Kmett](https://plus.google.com/u/0/113063331545548237308/about)'s [`Control.Lens`](http://hackage.haskell.org/package/lens) - Simon Peyton Jones [video](http://skillsmatter.com/podcast/scala/lenses-compositional-data-access-and-manipulation) on Lens at Skills Matter Oct 2013 - - ## A lens "focuses" on a smaller part of a larger object. {-# LANGUAGE TemplateHaskell #-} module UserTelBasicLensing where import Control.Lens import Test.HUnit -- for unit testing examples data Arc = Arc { _degree :: Int, _minute :: Int, _second :: Int } deriving (Eq, Show) data Location = Location { _latitude :: Arc, _longitude :: Arc } deriving (Eq, Show) Underscores in record names above are a `Control.Lens` convention for generating template haskell (TH). The following is a TH splice. It generates lenses automatically based on record functions in `Location`: $(makeLenses ''Location) Above creates two lenses: :t latitude -- latitude :: Functor f => (Arc -> f Arc) -> Location -> f Location :t longitude -- longitude :: Functor f => (Arc -> f Arc) -> Location -> f Location The [`type` of `Lens`](http://hackage.haskell.org/package/lens-3.9.2/docs/Control-Lens-Lens.html#t:Lens) is type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t type Lens' s a = Lens s s a a So the types above can be viewed as: -- latitude :: Lens' Location Arc -- longitude :: Lens' Location Arc --- # lenses used as getters/setters A lens is a function that get be used to get (via `view`) or set (via `set`) a part of a data structure. In this example case, `longitude` (likewise for `latitude`) can be used to `view` or `set` that part of `Location` : **`view`** :t view -- view :: Control.Monad.Reader.Class.MonadReader s m => Getting a s a -> m a :i Getting -- type Getting r s a = (a -> Accessor r a) -> s -> Accessor r s :i Accessor -- newtype Accessor r a = Accessor {runAccessor :: r} :t runAccessor -- runAccessor :: Accessor r a -> r :t view longitude -- view longitude :: Control.Monad.Reader.Class.MonadReader Location m => m Arc -- i.e.,: -- view longitude :: Location -> Arc **`set`** :t set -- set :: ASetter s t a b -> b -> s -> t :i ASetter -- type ASetter s t a b = (a -> Mutator b) -> s -> Mutator t :i Mutator -- newtype Mutator a = Control.Lens.Internal.Setter.Mutator {Control.Lens.Internal.Setter.runMutator :: a} :t Control.Lens.Internal.Setter.runMutator -- Control.Lens.Internal.Setter.runMutator :: Mutator a -> a :t set longitude -- set longitude :: Arc -> Location -> Location The following examples use unit tests (rather than GHCI input/output) to ensure correctness. t :: (Eq a) => (Show a) => String -> a -> a -> Test t testName actual expected = TestCase $ assertEqual testName expected actual l = Location (Arc 1 2 3) (Arc 4 5 6) t01 = t "01" (view longitude l) (Arc 4 5 6) t02 = t "02" (set longitude (Arc 40 50 60) l) (Location (Arc 1 2 3) (Arc 40 50 60)) t03 = t "03" l (Location (Arc 1 2 3) (Arc 4 5 6)) ## getters/setters without lenses Lenses are useful because, in *immutable* Haskell, to change nested fields in a data structure you need to recreate all the objects wrapped around the value that you are changing: getLongitudeR :: Location -> Arc getLongitudeR (Location { _longitude = lat }) = lat setLongitudeR :: Arc -> Location -> Location setLongitudeR lat loc = loc { _longitude = lat } t04 = t "04" (setLongitudeR (Arc 44 55 66) l) (Location (Arc 1 2 3) (Arc 44 55 66)) The lens version does this for you "automatically". --- # another way to build lenses using `lens` :t lens -- lens :: Functor f => (s -> a) -> (s -> b -> t) -> (a -> f b) -> s -> f t -- i.e.,: -- lens :: (c -> a) -> (c -> a -> c) -> Lens' c a The following are identical: :t lens getLongitudeR (flip setLongitudeR) -- lens getLongitudeR (flip setLongitudeR) :: Functor f => (Arc -> f Arc) -> Location -> f Location :t lens (view longitude) (flip $ set longitude) -- lens (view longitude) (flip $ set longitude) :: Functor f => (Arc -> f Arc) -> Location -> f Location :t longitude -- longitude :: Functor f => (Arc -> f Arc) -> Location -> f Location Above shows a law of lenses: for all lenses, `l`: l == lens (view l) (flip $ set l) --- # lens benefits Benefits of wrapping getters/setters together: - export just the lenses instead of the record functions - use other kinds of combinators to operate on these lenses for affecting the "focal" record values E.g., modification via combinator named `over`: {-# ANN modifyLongitude "HLint: ignore Redundant bracket" #-} modifyLongitude :: (Arc -> Arc) -> (Location -> Location) modifyLongitude f = longitude `over` f arcTimes11 :: Arc -> Arc arcTimes11 (Arc a b c) = Arc (a*11) (b*11) (c*11) longitudeTimes11 :: Location -> Location longitudeTimes11 = modifyLongitude arcTimes11 t05 = t "05" (longitudeTimes11 l) (Location (Arc 1 2 3) (Arc 44 55 66)) `over` lifts given function between getter and setter to create a function which modifies a part of the greater whole. --- # composing lens via `(.)` to go deeper into structure $(makeLenses ''Arc) :t degree -- degree :: Functor f => (Int -> f Int) -> Arc -> f Arc :t minute -- minute :: Functor f => (Int -> f Int) -> Arc -> f Arc :t second -- second :: Functor f => (Int -> f Int) -> Arc -> f Arc Now use `(.)` to get deeper inside `Location`: :t (.) -- (.) :: (b -> c) -> (a -> b) -> a -> c -- i.e.,: -- (.) :: Lens' a b -> Lens' b c -> Lens' a c :t longitude . degree -- longitude . degree :: Functor f => (Int -> f Int) -> Location -> f Location -- i.e.,: -- longitude . degree :: Lens' Location Int :t view (longitude . degree) -- view (longitude . degree) :: Control.Monad.Reader.Class.MonadReader Location m => m Int -- i.e.,: -- view (longitude . degree) :: Location -> Int :t set (longitude . degree) -- set (longitude . degree) :: Int -> Location -> Location Using the above type signatures as a guide, we can get/set specific parts of `Location`: t06 = t "06" (view (longitude . degree) l) 4 t07 = t "07" (set (longitude . degree) 202 l) (Location (Arc 1 2 3) (Arc 202 5 6)) t08 = t "08" (view (longitude . second) l) 6 t09 = t "09" (set (longitude . second) 202 l) (Location (Arc 1 2 3) (Arc 4 5 202)) ## combining lenses as pairs or `Either` pairs, i.e., **`(,)`** p :: Lens' (Location, Location) (Arc, Arc) p = latitude `alongside` longitude l10 = Location (Arc 10 20 30) (Arc 40 50 60) l100 = Location (Arc 100 200 300) (Arc 400 500 600) t10 = t "10" (view p (l10, l100)) (Arc 10 20 30, Arc 400 500 600) t11 = t "11" (set p (Arc 111 222 333, Arc 444 555 666) (l10, l100)) (Location (Arc 111 222 333) (Arc 40 50 60), Location (Arc 100 200 300) (Arc 444 555 666)) **`Either`** ei :: Lens' (Either Arc Arc) Int ei = choosing degree second a10 = Arc 10 20 30 a100 = Arc 100 200 300 t12 = t "12" (view ei (Left a10)) 10 t13 = t "13" (view ei (Right a10)) 30 t14 = t "14" (view ei (Left a100)) 100 t15 = t "15" (view ei (Right a100)) 300 t16 = t "16" (set ei (-1) (Left a10)) (Left (Arc (-1) 20 30)) t17 = t "17" (set ei (-1) (Right a100)) (Right (Arc 100 200 (-1))) --- # summary lens abstraction - idea of holding on to a value that's focused on a smaller part of a larger type - algebra for combining (via pairs and eithers, products and coproducts), composing, and modifying these values - subsumes record syntax - minimizes book-keeping on getters and setters Lens can do *lots* more. Feedback/discussion at: --- # example accuracy main = runTestTT $ TestList[t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15, t16, t17] main -- Counts {cases = 17, tried = 17, errors = 0, failures = 0} Thanks for to [Gabriel Gonzalez](http://www.haskellforall.com/) for useful feedback incorporated before publishing.