lens-tutorial 1.0.0 → 1.0.1
raw patch · 5 files changed
+958/−938 lines, 5 filessetup-changedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Control.Lens.Tutorial: instance Foldable Pair
- Control.Lens.Tutorial: instance Functor Pair
- Control.Lens.Tutorial: instance Show Atom
- Control.Lens.Tutorial: instance Show Molecule
- Control.Lens.Tutorial: instance Show Point
- Control.Lens.Tutorial: instance Traversable Pair
+ Control.Lens.Tutorial: Atom :: String -> Point -> Atom
+ Control.Lens.Tutorial: Molecule :: [Atom] -> Molecule
+ Control.Lens.Tutorial: Pair :: a -> a -> Pair a
+ Control.Lens.Tutorial: Point :: Double -> Double -> Point
+ Control.Lens.Tutorial: [_atoms] :: Molecule -> [Atom]
+ Control.Lens.Tutorial: [_element] :: Atom -> String
+ Control.Lens.Tutorial: [_point] :: Atom -> Point
+ Control.Lens.Tutorial: [_x] :: Point -> Double
+ Control.Lens.Tutorial: [_y] :: Point -> Double
+ Control.Lens.Tutorial: atoms :: Iso' Molecule [Atom]
+ Control.Lens.Tutorial: data Atom
+ Control.Lens.Tutorial: data Molecule
+ Control.Lens.Tutorial: data Pair a
+ Control.Lens.Tutorial: data Point
+ Control.Lens.Tutorial: element :: Lens' Atom String
+ Control.Lens.Tutorial: instance Data.Foldable.Foldable Control.Lens.Tutorial.Pair
+ Control.Lens.Tutorial: instance Data.Traversable.Traversable Control.Lens.Tutorial.Pair
+ Control.Lens.Tutorial: instance GHC.Base.Functor Control.Lens.Tutorial.Pair
+ Control.Lens.Tutorial: instance GHC.Show.Show Control.Lens.Tutorial.Atom
+ Control.Lens.Tutorial: instance GHC.Show.Show Control.Lens.Tutorial.Molecule
+ Control.Lens.Tutorial: instance GHC.Show.Show Control.Lens.Tutorial.Point
+ Control.Lens.Tutorial: point :: Lens' Atom Point
+ Control.Lens.Tutorial: traverse :: Traversable t => forall a (f :: * -> *) b. Applicative f => (a -> f b) -> t a -> f (t b)
+ Control.Lens.Tutorial: x :: Lens' Point Double
+ Control.Lens.Tutorial: y :: Lens' Point Double
Files
- LICENSE +24/−24
- Setup.hs +2/−2
- lens-tutorial.cabal +41/−41
- src/Control/Lens/Tutorial.hs +885/−865
- test/Main.hs +6/−6
LICENSE view
@@ -1,24 +1,24 @@-Copyright (c) 2015 Gabriel Gonzalez -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Gabriel Gonzalez nor the names of other contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2015 Gabriel Gonzalez+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:+ * Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.+ * Neither the name of Gabriel Gonzalez nor the names of other contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple -main = defaultMain +import Distribution.Simple+main = defaultMain
lens-tutorial.cabal view
@@ -1,41 +1,41 @@-Name: lens-tutorial -Version: 1.0.0 -Cabal-Version: >=1.10 -Build-Type: Simple -License: BSD3 -License-File: LICENSE -Copyright: 2015 Gabriel Gonzalez -Author: Gabriel Gonzalez -Maintainer: Gabriel439@gmail.com -Bug-Reports: https://github.com/Gabriel439/Haskell-Lens-Tutorial-Library/issues -Synopsis: Tutorial for the lens library -Description: This is a basic tutorial that you can use to get started with - the @lens@ library. This tutorial covers: - . - * The motivation behind the @lens@ library - . - * How to use the library for the most common use cases - . - * How to interpret type errors - . - * Basic familiarity with how lenses work under the hood -Category: Control -Source-Repository head - Type: git - Location: https://github.com/Gabriel439/Haskell-Lens-Tutorial-Library - -Library - HS-Source-Dirs: src - Build-Depends: base < 5, lens - Exposed-Modules: Control.Lens.Tutorial - Default-Language: Haskell2010 - -test-suite tests - Type: exitcode-stdio-1.0 - HS-Source-Dirs: test - Main-Is: Main.hs - GHC-Options: -O2 -Wall - Default-Language: Haskell2010 - Build-Depends: - base , - doctest >= 0.9.12 && < 0.11 +Name: lens-tutorial+Version: 1.0.1+Cabal-Version: >=1.10+Build-Type: Simple+License: BSD3+License-File: LICENSE+Copyright: 2015 Gabriel Gonzalez+Author: Gabriel Gonzalez+Maintainer: Gabriel439@gmail.com+Bug-Reports: https://github.com/Gabriel439/Haskell-Lens-Tutorial-Library/issues+Synopsis: Tutorial for the lens library+Description: This is a basic tutorial that you can use to get started with+ the @lens@ library. This tutorial covers:+ .+ * The motivation behind the @lens@ library+ .+ * How to use the library for the most common use cases+ .+ * How to interpret type errors+ .+ * Basic familiarity with how lenses work under the hood+Category: Control+Source-Repository head+ Type: git+ Location: https://github.com/Gabriel439/Haskell-Lens-Tutorial-Library++Library+ HS-Source-Dirs: src+ Build-Depends: base < 5, lens+ Exposed-Modules: Control.Lens.Tutorial+ Default-Language: Haskell2010++test-suite tests+ Type: exitcode-stdio-1.0+ HS-Source-Dirs: test+ Main-Is: Main.hs+ GHC-Options: -O2 -Wall+ Default-Language: Haskell2010+ Build-Depends:+ base ,+ doctest >= 0.9.12 && < 0.11
src/Control/Lens/Tutorial.hs view
@@ -1,865 +1,885 @@-{-| This @lens@ tutorial targets Haskell beginners and assumes only basic - familiarity with Haskell. By the end of this tutorial you should: - - * understand what problems the @lens@ library solves, - - * know when it is appropriate to use the @lens@ library, - - * be proficient in the most common @lens@ idioms, - - * understand the drawbacks of using lenses, and: - - * know where to look if you wish to learn more advanced tricks. - - If you would like to follow along with these examples, just import this - module: - -> $ ghci -> >>> import Control.Lens.Tutorial - --} - -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE DeriveTraversable #-} - -module Control.Lens.Tutorial ( - -- * Motivation - -- $motivation - - -- * Lenses - -- $lenses - - -- * Accessor notation - -- $accessors - - -- * First-class - -- $firstclass - - -- * Traversals - -- $traversals - - -- * Types - -- $types - - -- * Drawbacks - -- $drawbacks - - -- * Conclusion - -- $conclusion - ) where - -import Control.Applicative (Applicative) -import Control.Lens hiding (element) -import Data.Foldable (Foldable) -import Data.Monoid (Monoid) - --- $motivation --- --- The simplest problem that the @lens@ library solves is updating deeply --- nested records. Suppose you had the following nested Haskell data types: --- --- > data Atom = Atom { _element :: String, _point :: Point } --- > --- > data Point = Point { _x :: Double, _y :: Double } --- --- If you wanted to increase the @x@ coordinate of an `Atom` by one unit, you --- would have to write something like this in Haskell: --- --- > shiftAtomX :: Atom -> Atom --- > shiftAtomX (Atom e (Point x y)) = Atom e (Point (x + 1) y) --- --- This unpacking and repacking of data types grows increasingly difficult the --- more fields you add to each data type or the more deeply nested your data --- structures become. --- --- The @lens@ library solves this problem by letting you instead write: --- --- > -- atom.hs --- > --- > {-# LANGUAGE TemplateHaskell #-} --- > --- > import Control.Lens hiding (element) --- > --- > data Atom = Atom { _element :: String, _point :: Point } deriving (Show) --- > --- > data Point = Point { _x :: Double, _y :: Double } deriving (Show) --- > --- > makeLenses ''Atom --- > makeLenses ''Point --- > --- > shiftAtomX :: Atom -> Atom --- > shiftAtomX = over (point . x) (+ 1) --- --- Let's convince ourselves that this works: --- --- >>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } --- >>> shiftAtomX atom --- Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}} --- --- The above solution does not change no matter how many fields we add to --- @Atom@ or @Point@. --- --- Now suppose that we added yet another data structure: --- --- > data Molecule = Molecule { _atoms :: [Atom] } deriving (Show) --- --- We could shift an entire @Molecule@ by writing: --- --- > makeLenses ''Molecule --- > --- > shiftMoleculeX :: Molecule -> Molecule --- > shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) --- --- Again, this works the way we expect: --- --- >>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } --- >>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } } --- >>> let molecule = Molecule { _atoms = [atom1, atom2] } --- >>> shiftMoleculeX molecule -- Output formatted for clarity --- Molecule {_atoms = [Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}},Atom {_element = "O", _point = Point {_x = 4.0, _y = 4.0}}]} --- --- ... or formatted for clarity: --- --- > Molecule --- > { _atoms = --- > [ Atom { _element = "C", _point = Point { _x = 2.0, _y = 2.0 } } --- > , Atom { _element = ")", _point = Point { _x = 4.0, _y = 4.0 } } --- > ] --- > } --- --- Many people stumble across lenses while trying to solve this common problem --- of working with data structures with a large number of fields or deeply --- nested values. These sorts of situations arise commonly in: --- --- * games with complex and deeply nested state --- --- * scientific data formats --- --- * sensor or instrument output --- --- * web APIs --- --- * XML and JSON --- --- * enterprise code where data structures can have tens, hundreds, or even --- thousands of fields (true story!) - -{- $lenses - You might have some basic questions like: - - /Question:/ What is a lens? - - /Answer:/ A lens is a first class getter and setter - - We already saw how to use lenses to update values using `over`, but we can - also use lenses to retrieve values using `view`: - ->>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } ->>> view (point . x) atom -1.0 - - In other words, lenses package both \"get\" and \"set\" functionality into - a single value (the lens). You could pretend that a lens is a record - with two fields: - -> data Lens a b = Lens -> { view :: a -> b -> , over :: (b -> b) -> (a -> a) -> } - - That's not how lenses are actually implemented, but it's a useful - starting intuition. - - /Question:/ What is the type of a lens? - - /Answer:/ We used two lenses in the above @Atom@ example, with these types: - -> point :: Lens' Atom Point -> x :: Lens' Point Double - - The @point@ lens contains all the information we need to get or set the - @_point@ field of the @Atom@ type (which is a `Point`). Similarly, the @x@ - lens contains all the information we need to get or set the @_x@ field of - the @Point@ data type (which is a `Double`). - - The convention for the `Lens'` type parameters is: - -> -- +-- Bigger type -> -- | -> -- v -> Lens' bigger smaller -> -- ^ -> -- | -> -- +-- Smaller type within the bigger type - - The actual definition of `Lens'` is: - -> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) - - You might wonder how you can fit both getter and setter functionality in - a single value like this. The trick is that we get to pick what `Functor` - we specialize @f@ to and depending on which `Functor` we pick we get - different features. - - For example, if you pick @(f = `Identity`)@: - -> type ASetter' a b = (b -> Identity b) -> (a -> Identity a) -> -> -- ... equivalent to: (b -> b) -> (a -> a) - - ... you can build an `over`-like function. - - Similarly, if you pick @(f = `Const` b)@: - -> type Getting b a b = (b -> Const b b) -> (a -> Const b b) -> -> -- ... equivalent to: (b -> b ) -> (a -> b ) -> -> -- ... equivalent to: (a -> b ) - - ... you can build a `view`-like function. - - Those are not the only two `Functor`s we can pick. In fact, we can do a - lot more with lenses than just get and set values, but those are the two - most commonly used features. - - /Question:/ How do I create lenses? - - /Answer:/ You can either auto-generate them using Template Haskell or - create them by hand - - In our @Atom@ example, we auto-generated the lenses using Template Haskell, - like this: - -> makeLenses ''Atom -> makeLenses ''Point - - This created four lenses of the following types: - -> element :: Lens' Atom String -> point :: Lens' Atom Point -> x :: Lens' Point Double -> y :: Lens' Point Double - - `makeLenses` creates one lens per field prefixed with an underscore. The - lens has the same name as the field without the underscore. - - However, sometimes Template Haskell is not an option, so we can also use - the `lens` utility function to build lenses. This utility has type: - -> lens :: (a -> b) -> (b -> a -> a) -> Lens' a b - - The first argument is a \"getter\" (a way to extract a @\'b\'@ from an - @\'a\'@). The second argument is a \"setter\" (given a @b@, update an - @a@). The result is a `Lens'` built from the getter and setter. You would - use `lens` like this: - -> point :: Lens' Atom Point -> point = lens _point (\newPoint atom -> atom { _point = newPoint }) - - You can even define lenses without incurring a dependency on the @lens@ - library. Remember that lenses are just higher-order functions over - `Functor`s, so we could instead write: - -> -- point :: Lens' Atom Point -> point :: Functor f => (Point -> f Point) -> Atom -> f Atom -> point k atom = fmap (\newPoint -> atom { _point = newPoint }) (k (_point atom)) - - This means that you can provide lenses for your library's types without - depending on the @lens@ library. All you need is the `fmap` function, - which is provided by the Haskell Prelude. - - /Question:/ How do I combine lenses? - - /Answer:/ You compose them, using function composition (Yes, really!) - - You can think of the function composition operator as having this type: - -> (.) :: Lens' a b -> Lens' b c -> Lens' a c - - We can compose lenses using function composition because `Lens'` is a - type synonym for a higher-order function: - -> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) - - So under the hood we are composing two higher-order functions to get back a - new higher-order function: - -> (.) :: Functor f -> => ((b -> f b) -> (a -> f a)) -> -> ((c -> f c) -> (b -> f b)) -> -> ((c -> f c) -> (a -> f a)) - - In our original @Atom@ example, we composed the @point@ and @x@ lenses to - create a new composite lens: - -> point :: Lens' Atom Point -> x :: Lens' Point Double -> -> point . x :: Lens' Atom Double - - This composite lens lets us get or set the @x@ coordinate of an @Atom@. - We can use `over` and `view` on the composite `Lens'` and they will behave - exactly the way we expect: - -> view (point . x) :: Atom -> Double -> -> over (point . x) :: (Double -> Double) -> (Atom -> Atom) - - /Question:/ How do I consume lenses? - - /Answer:/ Using `view`, `set` or `over` - - Here are their types: - -> view :: Lens' a b -> a -> b -> -> over :: Lens' a b -> (b -> b) -> a -> a -> -> set :: Lens' a b -> b -> a -> a -> set lens b = over lens (\_ -> b) - - `view` and `over` are the two fundamental functions on lenses. `set` is - just a special case of `over`. - - `view` and `over` are fundamental because they distribute over lens - composition: - -> view (lens1 . lens2) = (view lens2) . (view lens1) -> -> view id = id - -> over (lens1 . lens2) = (over lens1) . (over lens2) -> -> over id = id - - /Question:/ What else do I need to know? - - /Answer:/ That's pretty much it! - - For 90% of use cases, you just: - - * Create lenses (using `makeLens`, `lens` or plain-old `fmap`) - - * Compose them (using (`.`)) - - * Consume them (using `view`, `set`, and `over`) - - You could actually stop reading here if you are in a hurry since this - covers the overwhelmingly common use case for the library. On the other - hand, keep reading if you would like to learn additional tricks and - features. --} - -{- $accessors - You might be used to object-oriented languages where you could retrieve a - nested field using: - -> atom.point.x - - You can do almost the exact same thing using the @lens@ library, except - that the first dot will have a @^@ right before the dot: - ->>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } ->>> atom^.point.x -1.0 - - You can better understand why this works, by adding whitespace and - explicit parentheses: - -> atom ^. (point . x) - - This trick uses (`^.`), which is an infix operator equivalent to `view`: - -> (^.) :: a -> Lens' a b -> b -> x ^. l = view l x - - ... and you just keep adding dots after that for each lens you compose. - This gives the appearance of object-oriented accessors if you omit the - whitespace around the operators. --} - -{- $firstclass - Lenses are \"first class\" values, meaning that you can manipulate them - using ordinary functional programming techniques. You can take them as - inputs, return them as outputs, or stick them in data structures. Anything - goes! - - For example, suppose we don't want to define separate shift functions for - @Atom@s and @Molecule@s: - -> shiftAtomX :: Atom -> Atom -> shiftAtomX = over (point . x) (+ 1) - -> shiftMoleculeX :: Molecule -> Molecule -> shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) - - We can instead unify them into a single function by parametrizing the - shift function on the lens: - -> shift lens = over lens (+ 1) - - This lets us write: - -> shift (point . x) :: Atom -> Atom -> -> shift (atoms . traverse . point . x) :: Molecule -> Molecule - - Even better, we can define synonyms for our composite lenses: - -> atomX :: Lens' Atom Double -> atomX = point . x -> -> -- We'll learn what `Traversal` means shortly -> moleculeX :: Traversal' Molecule Double -> moleculeX = atoms . traverse . point . x - - Now we can write code almost identical to the original code: - -> shift atomX :: Atom -> Atom -> -> shift moleculeX :: Molecule -> Molecule - - ... but we also get several other utilities for free: - -> set atomX :: Double -> Atom -> Atom -> -> set moleculeX :: Double -> Molecule -> Molecule -> -> view atomX :: Atom -> Double -> -> -- We can't use `view` for `Traversal'`s. Read on to find out why -> toListOf moleculeX :: Molecule -> [Double] - - That's much more reusable, but you might wonder what this `Traversal'` and - `toListOf` business is all about. --} - --- $traversals --- /Question:/ What is a traversal? --- --- /Answer:/ A first class getter and setter for an arbitrary number of values --- --- A traversal lets you get all the values it points to as a list and it also --- lets you update or set all the values it points to. Think of a traversal --- as a record with two fields: --- --- > data Traversal' a b = Traversal' --- > { toListOf :: a -> [b] --- > , over :: (b -> b) -> (a -> a) --- > } --- --- That's not how traversals are actually implemented, but it's a useful --- starting intuition. --- --- We can still use `over` and `set` (a special case of `over`) with a --- traversal, but we use `toListOf` instead of `view`. --- --- /Question:/ What is the type of a traversal? --- --- /Answer:/ We used one traversal in the above @Molecule@ example: --- --- > moleculeX :: Traversal' Molecule Double --- --- This `Traversal'` lets us get or set an arbitrary number of x coordinates, --- each of which is a `Double`. There could be less than one x coordinate --- (i.e. 0 coordinates) or more than one x coordinate. Contrast this with a --- `Lens'` which can only get or set exactly one value. --- --- Like `Lens'`, `Traversal'` is a type synonym for a higher-order function: --- --- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) --- > --- > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) --- --- Notice that the only difference between a `Lens'` and a `Traversal'` is the --- type class constraint. A `Lens'` has a `Functor` constraint and --- `Traversal'` has an `Applicative` constraint. This means that any `Lens'` --- is automatically also a valid `Traversal'` (since `Functor` is a superclass --- of `Applicative`). --- --- Since every `Lens'` is a `Traversal'`, all of our example lenses also --- double as traversals: --- --- > atoms :: Traversal' Molecule [Atom] --- > element :: Traversal' Atom String --- > point :: Traversal' Atom Point --- > x :: Traversal' Point Double --- > y :: Traversal' Point Double --- --- We actually used yet another `Traversal'`, which was `traverse` (from --- "Data.Traversable"): --- --- > traverse :: Traversable t => Traversal' (t a) a --- --- This works because the `Traversal'` type synonym expands out to: --- --- > traverse :: (Applicative f, Traversable t) => (a -> f a) -> t a -> f (t a) --- --- ... which is exactly the traditional type signature of `traverse`. --- --- In our @Molecule@ example, we were using the special case where @t = []@: --- --- > traverse :: Traversal' [a] a --- --- In Haskell, you can derive `Functor`, `Data.Foldable.Foldable` and --- `Traversable` for many data types using the @DeriveFoldable@ and --- @DeriveTraversable@ extensions. This means that you can autogenerate a --- valid `traverse` for these data types: --- --- > {-# LANGUAGE DeriveFoldable #-} --- > {-# LANGUAGE DeriveFunctor #-} --- > {-# LANGUAGE DeriveTraversable #-} --- > --- > import Control.Lens --- > import Data.Foldable --- > --- > data Pair a = Pair a a deriving (Functor, Foldable, Traversable) --- --- We could then use `traverse` to navigate from `Pair` to its two children: --- --- > traverse :: Traversal' (Pair a) a --- > --- > over traverse :: (a -> a) -> (Pair a -> Pair a) --- > --- > over traverse (+ 1) (Pair 3 4) = Pair 4 5 --- --- /Question:/ How do I create traversals? --- --- /Answer:/ There are three main ways to create primitive traversals: --- --- * `traverse` is a `Traversal'` that you get for any type that implements --- `Traversable` --- --- * Every `Lens'` will also type-check as a `Traversal'` --- --- * You can use Template Haskell to generate `Traversal'`s using `makePrisms` --- since every `Prism'` is also a `Traversal'` (not covered in this --- tutorial) --- --- /Question:/ How do I combine traversals? --- --- /Answer:/ You compose them, using function composition --- --- You can think of the function composition operator as having this type: --- --- > (.) :: Traversal' a b -> Traversal' b c -> Traversal' a c --- --- We can compose traversals using function composition because a --- `Traversal'` is a type synonym for a higher-order function: --- --- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) --- --- So under the hood we are composing two functions to get back a new --- function: --- --- > (.) :: Applicative f --- > => ((b -> f b) -> (a -> f a)) --- > -> ((c -> f c) -> (b -> f b)) --- > -> ((c -> f c) -> (a -> f a)) --- --- In our original @Molecule@ example, we composed four `Traversal'`s --- together to create a new `Traversal'`: --- --- > -- Remember that `atoms`, `point`, and `x` are also `Traversal'`s --- > atoms :: Traversal' Molecule [Atom] --- > traverse :: Traversal' [Atom] Atom --- > point :: Traversal' Atom Point --- > x :: Traversal' Point Double --- > --- > -- Now compose them --- > atoms :: Traversal' Molecule [Atom] --- > atoms . traverse :: Traversal' Molecule Atom --- > atoms . traverse . point :: Traversal' Molecule Point --- > atoms . traverse . point . x :: Traversal' Molecule Double --- --- This composite traversal lets us get or set the @x@ coordinates of a --- @Molecule@. --- --- > over (atoms . traverse . point . x) --- > :: (Double -> Double) -> (Molecule -> Molecule) --- > --- > toListOf (atoms . traverse . point . x) --- > :: Molecule -> [Double] --- --- /Question:/ How do I consume traversals? --- --- /Answer:/ Using `toListOf`, `set` or `over` --- --- Here are their types: --- --- > toListOf :: Traversal' a b -> a -> [b] --- > --- > over :: Traversal' a b -> (b -> b) -> a -> a --- > --- > set :: Traversal' a b -> b -> a -> a --- > set traversal b = over traversal (\_ -> b) --- --- Note that `toListOf` distributes over traversal composition: --- --- > toListOf (traversal1 . traversal2) = (toListOf traversal1) >=> (toListOf traversal2) --- > --- > toListOf id = return --- --- If you prefer object-oriented syntax you can also use (`^..`), which is an --- infix operator equivalent to `toListOf`: --- --- >>> Pair 3 4 ^.. traverse --- [3,4] - -{- $types - You might wonder why you can use `over` on both a `Lens'` and a - `Traversal'` but you can only use `view` on a `Lens'`. We can see why by - studying the (simplified) type and implementation of `over`: - -> over :: ((b -> Identity b) -> (a -> Identity b)) -> (b -> b) -> a -> a -> over setter f x = runIdentity (setter (\y -> Identity (f y)) x) - - To follow the implementation, just step slowly through the types. Here - are the types of the arguments to `over`: - -> setter :: (b -> Identity b) -> (a -> Identity b) -> f :: b -> b -> x :: a - - ... and here are the types of the sub-expressions on the right-hand side: - -> \y -> Identity (f y) :: b -> Identity b -> setter (\y -> Identity (f y)) :: a -> Identity a -> setter (\y -> Identity (f y)) x :: Identity a -> runIdentity (setter (\y -> Identity (f y)) x) :: a - - We can replace @setter@ with @point@ and replace @x@ with @atom@ to see - that this generates the correct code for updating an atom's point: - -> over point f atom -> -> -- Definition of `over` -> = runIdentity (point (\y -> Identity (f y)) atom) -> -> -- Definition of `point` -> = runIdentity (fmap (\newPoint -> atom { _point = newPoint }) (Identity (f (_point atom))) -> -> -- fmap g (Identity y) = Identity (g y) -> = runIdentity (Identity (atom { _point = f (_point atom) })) -> -> -- runIdentity (Identity z) = z -> = atom { _point = f (_point atom) } - - ... which is exactly what we would have written by hand without lenses. - - The reason `over` works for both `Lens'`es and `Traversal'`s is because - `Identity` implements both `Functor` and `Applicative`: - -> instance Functor Identity where ... -> instance Applicative Identity where ... - - So both the `Lens'` type and `Traversal'` type synonyms: - -> type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) -> -> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) - - ... can both be specialized to use `Identity` in place of @f@: - -> (b -> Identity b) -> (a -> Identity a) - - ... making them valid arguments to `over`. - - Now let's study the (simplified) type and implementation of `view`: - -> view :: ((b -> Const b b) -> (a -> Const b a)) -> a -> b -> view getter x = getConst (getter Const x) - - Again, we can walk slowly through the types of the arguments: - -> getter :: (b -> Const b b) -> (a -> Const b a) -> x :: a - - ... and the types of the sub-expressions on the right-hand side: - -> getter Const :: a -> Const b a -> getter Const x :: Const b a -> getConst (getter Const x) :: b - - Let's see how this plays out for the @point@ lens: - -> view point atom -> -> -- Definition of `view` -> = getConst (point Const atom) -> -> -- Definition of `point` -> = getConst (fmap (\newPoint -> atom { _point = newPoint }) (Const (_point atom))) -> -> -- fmap g (Const y) = Const y -> = getConst (Const (_point atom)) -> -> -- getConst (Const z) = z -> = _point atom - - ... which is exactly what we would have written by hand without lenses. - - `view` accepts `Lens'`es because `Const` implements `Functor`: - -> instance Functor (Const b) - - ... so the `Lens'` type synonym: - - -> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) - - ... can be specialized to use @(`Const` b)@ in place of @f@: - -> (b -> Const b b) -> (a -> Const b b) - - - ... making it a valid argument to `view`. - - Interestingly, `Const` implements also `Applicative`, but with a - constraint: - -> instance Monoid b => Applicative (Const b) - - This implies that we *can* use `view` on a `Traversal'`, but only if the - value that we extract is a `Monoid`. Let's try this out: - ->>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } ->>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } } ->>> let molecule = Molecule { _atoms = [atom1, atom2] } ->>> view (atoms . traverse . element) molecule -"CO" - - This works because our traversal's result is a `String`: - -> atoms . traverse . element :: Traversal' Molecule String - - ... and `String` implements the `Data.Monoid.Monoid` interface. When you - try to extract multiple strings using `view` they get flattened together - into a single `String` using `Data.Monoid.mappend`. - - If you try to extract the element from an empty molecule: - ->>> view (atoms . traverse . element) (Molecule { _atoms = [] }) -"" - - You get the empty string (i.e. `Data.Monoid.mempty`). - - This is why the result of a `Traversal'` needs to be a `Data.Monoid.Monoid` - when using `view`. If the `Traversal'` points to more than one value you - need some way to combine them into a single value (using - `Data.Monoid.mappend`) and if the `Traversal'` points to less than one - value you need a default value to return (using `Data.Monoid.mempty`). - - If you try to `view` a `Traversal'` that doesn't point to a - `Data.Monoid.Monoid`, you will get the following type error: - -> >>> view (atoms . traverse . point . x) molecule -> No instance for (Data.Monoid.Monoid Double) -> arising from a use of `traverse' -> In the first argument of `(.)', namely `traverse' -> In the second argument of `(.)', namely `traverse . point . x' -> In the first argument of `view', namely -> `(atoms . traverse . point . x)' - - The compiler complains that `Double` does not implement the - `Data.Monoid.Monoid` type class, so there is no sensible way to merge all - the x coordinates that our `Traversal'` points to. For these cases you - should use `toListOf` instead. --} - -{- $drawbacks - Lenses come with trade-offs, so you should use them wisely. - - For example, lenses do not produce the best error messages. Unless you - understand how `Traversal'`s work you will probably not understand the - above error message. - - Also, lenses increase the learning curve for new Haskell programmers, so - you should consider avoiding them in tutorial code targeting novice - Haskell programmers. - - Lenses also add a level of boilerplate to all data types to auto-generate - lenses and increase compile times. So for small projects the overhead of - adding lenses may dwarf the benefits. - - @lens@ is also a library with a large dependency tree, focused on being - \"batteries included\" and covering a large cross-section of the Haskell - ecosystem. Browsing the Hackage listing you will find support modules - ranging from "System.FilePath.Lens" to "Control.Parallel.Strategies.Lens", - and many more. If you need a more light-weight alternative you can use - the @lens-simple@ or @microlens@ library, each of which provides a - restricted subset of the @lens@ library with a much smaller dependency tree. - - The ideal use case for the @lens@ library is a medium-to-large project with - rich and deeply nested types. In these large projects the benefits of using - lenses outweigh the costs. --} - -{- $conclusion - This tutorial covers an extremely small subset of this library. If you - would like to learn more, you can begin by skimming the example code in the - following modules: - - * "Control.Lens.Getter" - - * "Control.Lens.Setter" - - * "Control.Lens.Traversal" - - * "Control.Lens.Tuple" - - * "Control.Lens.Lens" - - * "Control.Lens.Review" - - * "Control.Lens.Prism" - - * "Control.Lens.Iso" - - The documentation for these modules includes several examples to get you - started and help you build an intuition for more advanced tricks that were - not covered in this tutorial. - - You can also study several long-form examples here: - - <https://github.com/ekmett/lens/tree/master/examples> - - If you prefer light-weight @lens@-compatible libraries, then check out - @lens-simple@ or @micro-lens@: - - * <http://hackage.haskell.org/package/microlens microlens> - - * <http://hackage.haskell.org/package/lens-simple lens-simple> - - If you would like a broader survey of lens features, then you can check - out these tutorials: - - * <https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial A little lens starter tutorial> - Introduces -Prisms, Isos and JSON functionality - - * <http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html Program imperatively using Haskell lenses> - Illustrates lens support for stateful code --} - -data Atom = Atom { _element :: String, _point :: Point } deriving (Show) - -data Point = Point { _x :: Double, _y :: Double } deriving (Show) - -data Molecule = Molecule { _atoms :: [Atom] } deriving (Show) - -data Pair a = Pair a a deriving (Functor, Foldable, Traversable) - -makeLenses ''Atom -makeLenses ''Point -makeLenses ''Molecule - -shiftAtomX :: Atom -> Atom -shiftAtomX = over (point . x) (+ 1) - -shiftMoleculeX :: Molecule -> Molecule -shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) - -shift :: ASetter' a Double -> a -> a -shift lens = over lens (+ 1) +{-| This @lens@ tutorial targets Haskell beginners and assumes only basic+ familiarity with Haskell. By the end of this tutorial you should:++ * understand what problems the @lens@ library solves,++ * know when it is appropriate to use the @lens@ library,++ * be proficient in the most common @lens@ idioms,++ * understand the drawbacks of using lenses, and:++ * know where to look if you wish to learn more advanced tricks.++ If you would like to follow along with these examples, just import this+ module:++> $ ghci+> >>> import Control.Lens.Tutorial++-}++{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}++module Control.Lens.Tutorial (+ -- * Motivation+ -- $motivation++ -- * Lenses+ -- $lenses++ -- * Accessor notation+ -- $accessors++ -- * First-class+ -- $firstclass++ -- * Traversals+ -- $traversals++ -- * Types+ -- $types++ -- * Drawbacks+ -- $drawbacks++ -- * Conclusion+ -- $conclusion++ -- * Exports+ -- $exports+ Atom(..)+ , element+ , point+ , Point(..)+ , x+ , y+ , Molecule(..)+ , atoms+ , Pair(..)+ , traverse+ ) where++import Control.Applicative (Applicative)+import Control.Lens hiding (element)+import Data.Foldable (Foldable)+import Data.Monoid (Monoid)++-- $motivation+--+-- The simplest problem that the @lens@ library solves is updating deeply+-- nested records. Suppose you had the following nested Haskell data types:+-- +-- > data Atom = Atom { _element :: String, _point :: Point }+-- >+-- > data Point = Point { _x :: Double, _y :: Double }+-- +-- If you wanted to increase the @x@ coordinate of an `Atom` by one unit, you+-- would have to write something like this in Haskell:+-- +-- > shiftAtomX :: Atom -> Atom+-- > shiftAtomX (Atom e (Point x y)) = Atom e (Point (x + 1) y)+-- +-- This unpacking and repacking of data types grows increasingly difficult the+-- more fields you add to each data type or the more deeply nested your data+-- structures become.+-- +-- The @lens@ library solves this problem by letting you instead write:+-- +-- > -- atom.hs+-- >+-- > {-# LANGUAGE TemplateHaskell #-}+-- >+-- > import Control.Lens hiding (element)+-- >+-- > data Atom = Atom { _element :: String, _point :: Point } deriving (Show)+-- >+-- > data Point = Point { _x :: Double, _y :: Double } deriving (Show)+-- >+-- > makeLenses ''Atom+-- > makeLenses ''Point+-- >+-- > shiftAtomX :: Atom -> Atom+-- > shiftAtomX = over (point . x) (+ 1)+-- +-- Let's convince ourselves that this works:+-- +-- >>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }+-- >>> shiftAtomX atom+-- Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}}+-- +-- The above solution does not change no matter how many fields we add to+-- @Atom@ or @Point@.+-- +-- Now suppose that we added yet another data structure:+-- +-- > data Molecule = Molecule { _atoms :: [Atom] } deriving (Show)+-- +-- We could shift an entire @Molecule@ by writing:+-- +-- > makeLenses ''Molecule+-- >+-- > shiftMoleculeX :: Molecule -> Molecule+-- > shiftMoleculeX = over (atoms . traverse . point . x) (+ 1)+-- +-- Again, this works the way we expect:+-- +-- >>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }+-- >>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } }+-- >>> let molecule = Molecule { _atoms = [atom1, atom2] }+-- >>> shiftMoleculeX molecule -- Output formatted for clarity+-- Molecule {_atoms = [Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}},Atom {_element = "O", _point = Point {_x = 4.0, _y = 4.0}}]}+--+-- ... or formatted for clarity:+--+-- > Molecule+-- > { _atoms =+-- > [ Atom { _element = "C", _point = Point { _x = 2.0, _y = 2.0 } }+-- > , Atom { _element = "O", _point = Point { _x = 4.0, _y = 4.0 } }+-- > ]+-- > }+-- +-- Many people stumble across lenses while trying to solve this common problem+-- of working with data structures with a large number of fields or deeply+-- nested values. These sorts of situations arise commonly in:+-- +-- * games with complex and deeply nested state+-- +-- * scientific data formats+-- +-- * sensor or instrument output+-- +-- * web APIs+-- +-- * XML and JSON+-- +-- * enterprise code where data structures can have tens, hundreds, or even+-- thousands of fields (true story!)++{- $lenses+ You might have some basic questions like:++ /Question:/ What is a lens?++ /Answer:/ A lens is a first class getter and setter++ We already saw how to use lenses to update values using `over`, but we can+ also use lenses to retrieve values using `view`:++>>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }+>>> view (point . x) atom+1.0++ In other words, lenses package both \"get\" and \"set\" functionality into+ a single value (the lens). You could pretend that a lens is a record+ with two fields:++> data Lens a b = Lens+> { view :: a -> b+> , over :: (b -> b) -> (a -> a)+> }++ That's not how lenses are actually implemented, but it's a useful+ starting intuition.++ /Question:/ What is the type of a lens?++ /Answer:/ We used two lenses in the above @Atom@ example, with these types:++> point :: Lens' Atom Point+> x :: Lens' Point Double++ The @point@ lens contains all the information we need to get or set the+ @_point@ field of the @Atom@ type (which is a `Point`). Similarly, the @x@+ lens contains all the information we need to get or set the @_x@ field of+ the @Point@ data type (which is a `Double`).++ The convention for the `Lens'` type parameters is:++> -- +-- Bigger type+> -- |+> -- v+> Lens' bigger smaller+> -- ^+> -- |+> -- +-- Smaller type within the bigger type++ The actual definition of `Lens'` is:++> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)++ You might wonder how you can fit both getter and setter functionality in+ a single value like this. The trick is that we get to pick what `Functor`+ we specialize @f@ to and depending on which `Functor` we pick we get+ different features.++ For example, if you pick @(f = `Identity`)@:++> type ASetter' a b = (b -> Identity b) -> (a -> Identity a)+>+> -- ... equivalent to: (b -> b) -> (a -> a)++ ... you can build an `over`-like function.++ Similarly, if you pick @(f = `Const` b)@:++> type Getting b a b = (b -> Const b b) -> (a -> Const b a)+>+> -- ... equivalent to: (b -> b ) -> (a -> b )+>+> -- ... equivalent to: (a -> b )++ ... you can build a `view`-like function.++ Those are not the only two `Functor`s we can pick. In fact, we can do a+ lot more with lenses than just get and set values, but those are the two+ most commonly used features.++ /Question:/ How do I create lenses?++ /Answer:/ You can either auto-generate them using Template Haskell or+ create them by hand++ In our @Atom@ example, we auto-generated the lenses using Template Haskell,+ like this:++> makeLenses ''Atom+> makeLenses ''Point++ This created four lenses of the following types:++> element :: Lens' Atom String+> point :: Lens' Atom Point+> x :: Lens' Point Double+> y :: Lens' Point Double++ `makeLenses` creates one lens per field prefixed with an underscore. The+ lens has the same name as the field without the underscore.++ However, sometimes Template Haskell is not an option, so we can also use+ the `lens` utility function to build lenses. This utility has type:++> lens :: (a -> b) -> (b -> a -> a) -> Lens' a b++ The first argument is a \"getter\" (a way to extract a @\'b\'@ from an+ @\'a\'@). The second argument is a \"setter\" (given a @b@, update an+ @a@). The result is a `Lens'` built from the getter and setter. You would+ use `lens` like this:++> point :: Lens' Atom Point+> point = lens _point (\newPoint atom -> atom { _point = newPoint })++ You can even define lenses without incurring a dependency on the @lens@+ library. Remember that lenses are just higher-order functions over+ `Functor`s, so we could instead write:++> -- point :: Lens' Atom Point+> point :: Functor f => (Point -> f Point) -> Atom -> f Atom+> point k atom = fmap (\newPoint -> atom { _point = newPoint }) (k (_point atom))++ This means that you can provide lenses for your library's types without+ depending on the @lens@ library. All you need is the `fmap` function,+ which is provided by the Haskell Prelude.++ /Question:/ How do I combine lenses?++ /Answer:/ You compose them, using function composition (Yes, really!)++ You can think of the function composition operator as having this type:++> (.) :: Lens' a b -> Lens' b c -> Lens' a c++ We can compose lenses using function composition because `Lens'` is a+ type synonym for a higher-order function:++> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)++ So under the hood we are composing two higher-order functions to get back a+ new higher-order function:++> (.) :: Functor f+> => ((b -> f b) -> (a -> f a))+> -> ((c -> f c) -> (b -> f b))+> -> ((c -> f c) -> (a -> f a))++ In our original @Atom@ example, we composed the @point@ and @x@ lenses to+ create a new composite lens:++> point :: Lens' Atom Point+> x :: Lens' Point Double+>+> point . x :: Lens' Atom Double++ This composite lens lets us get or set the @x@ coordinate of an @Atom@.+ We can use `over` and `view` on the composite `Lens'` and they will behave+ exactly the way we expect:++> view (point . x) :: Atom -> Double+>+> over (point . x) :: (Double -> Double) -> (Atom -> Atom)++ /Question:/ How do I consume lenses?++ /Answer:/ Using `view`, `set` or `over`++ Here are their types:++> view :: Lens' a b -> a -> b+>+> over :: Lens' a b -> (b -> b) -> a -> a+>+> set :: Lens' a b -> b -> a -> a+> set lens b = over lens (\_ -> b)++ `view` and `over` are the two fundamental functions on lenses. `set` is+ just a special case of `over`.++ `view` and `over` are fundamental because they distribute over lens+ composition:++> view (lens1 . lens2) = (view lens2) . (view lens1)+>+> view id = id++> over (lens1 . lens2) = (over lens1) . (over lens2)+>+> over id = id++ /Question:/ What else do I need to know?++ /Answer:/ That's pretty much it!++ For 90% of use cases, you just:++ * Create lenses (using `makeLens`, `lens` or plain-old `fmap`)++ * Compose them (using (`.`))++ * Consume them (using `view`, `set`, and `over`)++ You could actually stop reading here if you are in a hurry since this+ covers the overwhelmingly common use case for the library. On the other+ hand, keep reading if you would like to learn additional tricks and+ features.+-}++{- $accessors+ You might be used to object-oriented languages where you could retrieve a+ nested field using:++> atom.point.x++ You can do almost the exact same thing using the @lens@ library, except+ that the first dot will have a @^@ right before the dot:++>>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }+>>> atom^.point.x+1.0++ You can better understand why this works, by adding whitespace and+ explicit parentheses:++> atom ^. (point . x)++ This trick uses (`^.`), which is an infix operator equivalent to `view`:++> (^.) :: a -> Lens' a b -> b+> x ^. l = view l x++ ... and you just keep adding dots after that for each lens you compose.+ This gives the appearance of object-oriented accessors if you omit the+ whitespace around the operators.+-}++{- $firstclass+ Lenses are \"first class\" values, meaning that you can manipulate them+ using ordinary functional programming techniques. You can take them as+ inputs, return them as outputs, or stick them in data structures. Anything+ goes!++ For example, suppose we don't want to define separate shift functions for+ @Atom@s and @Molecule@s:++> shiftAtomX :: Atom -> Atom+> shiftAtomX = over (point . x) (+ 1)++> shiftMoleculeX :: Molecule -> Molecule+> shiftMoleculeX = over (atoms . traverse . point . x) (+ 1)++ We can instead unify them into a single function by parametrizing the+ shift function on the lens:++> shift lens = over lens (+ 1)++ This lets us write:++> shift (point . x) :: Atom -> Atom+>+> shift (atoms . traverse . point . x) :: Molecule -> Molecule++ Even better, we can define synonyms for our composite lenses:++> atomX :: Lens' Atom Double+> atomX = point . x+>+> -- We'll learn what `Traversal` means shortly+> moleculeX :: Traversal' Molecule Double+> moleculeX = atoms . traverse . point . x++ Now we can write code almost identical to the original code:++> shift atomX :: Atom -> Atom+>+> shift moleculeX :: Molecule -> Molecule++ ... but we also get several other utilities for free:++> set atomX :: Double -> Atom -> Atom+>+> set moleculeX :: Double -> Molecule -> Molecule+>+> view atomX :: Atom -> Double+>+> -- We can't use `view` for `Traversal'`s. Read on to find out why+> toListOf moleculeX :: Molecule -> [Double]++ That's much more reusable, but you might wonder what this `Traversal'` and+ `toListOf` business is all about.+-}++-- $traversals+-- /Question:/ What is a traversal?+-- +-- /Answer:/ A first class getter and setter for an arbitrary number of values+-- +-- A traversal lets you get all the values it points to as a list and it also+-- lets you update or set all the values it points to. Think of a traversal+-- as a record with two fields:+-- +-- > data Traversal' a b = Traversal'+-- > { toListOf :: a -> [b]+-- > , over :: (b -> b) -> (a -> a)+-- > }+-- +-- That's not how traversals are actually implemented, but it's a useful+-- starting intuition.+-- +-- We can still use `over` and `set` (a special case of `over`) with a+-- traversal, but we use `toListOf` instead of `view`.+-- +-- /Question:/ What is the type of a traversal?+-- +-- /Answer:/ We used one traversal in the above @Molecule@ example:+-- +-- > moleculeX :: Traversal' Molecule Double+-- +-- This `Traversal'` lets us get or set an arbitrary number of x coordinates,+-- each of which is a `Double`. There could be less than one x coordinate+-- (i.e. 0 coordinates) or more than one x coordinate. Contrast this with a+-- `Lens'` which can only get or set exactly one value.+-- +-- Like `Lens'`, `Traversal'` is a type synonym for a higher-order function:+-- +-- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a)+-- >+-- > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)+-- +-- Notice that the only difference between a `Lens'` and a `Traversal'` is the+-- type class constraint. A `Lens'` has a `Functor` constraint and+-- `Traversal'` has an `Applicative` constraint. This means that any `Lens'`+-- is automatically also a valid `Traversal'` (since `Functor` is a superclass+-- of `Applicative`).+-- +-- Since every `Lens'` is a `Traversal'`, all of our example lenses also+-- double as traversals:+-- +-- > atoms :: Traversal' Molecule [Atom]+-- > element :: Traversal' Atom String+-- > point :: Traversal' Atom Point+-- > x :: Traversal' Point Double+-- > y :: Traversal' Point Double+-- +-- We actually used yet another `Traversal'`, which was `traverse` (from+-- "Data.Traversable"):+-- +-- > traverse :: Traversable t => Traversal' (t a) a+-- +-- This works because the `Traversal'` type synonym expands out to:+-- +-- > traverse :: (Applicative f, Traversable t) => (a -> f a) -> t a -> f (t a)+-- +-- ... which is exactly the traditional type signature of `traverse`.+-- +-- In our @Molecule@ example, we were using the special case where @t = []@:+-- +-- > traverse :: Traversal' [a] a+-- +-- In Haskell, you can derive `Functor`, `Data.Foldable.Foldable` and+-- `Traversable` for many data types using the @DeriveFoldable@ and+-- @DeriveTraversable@ extensions. This means that you can autogenerate a+-- valid `traverse` for these data types:+-- +-- > {-# LANGUAGE DeriveFoldable #-}+-- > {-# LANGUAGE DeriveFunctor #-}+-- > {-# LANGUAGE DeriveTraversable #-}+-- >+-- > import Control.Lens+-- > import Data.Foldable+-- >+-- > data Pair a = Pair a a deriving (Functor, Foldable, Traversable)+-- +-- We could then use `traverse` to navigate from `Pair` to its two children:+-- +-- > traverse :: Traversal' (Pair a) a+-- >+-- > over traverse :: (a -> a) -> (Pair a -> Pair a)+-- >+-- > over traverse (+ 1) (Pair 3 4) = Pair 4 5+-- +-- /Question:/ How do I create traversals?+-- +-- /Answer:/ There are three main ways to create primitive traversals:+-- +-- * `traverse` is a `Traversal'` that you get for any type that implements+-- `Traversable`+-- +-- * Every `Lens'` will also type-check as a `Traversal'`+-- +-- * You can use Template Haskell to generate `Traversal'`s using `makePrisms`+-- since every `Prism'` is also a `Traversal'` (not covered in this+-- tutorial)+-- +-- /Question:/ How do I combine traversals?+-- +-- /Answer:/ You compose them, using function composition+-- +-- You can think of the function composition operator as having this type:+-- +-- > (.) :: Traversal' a b -> Traversal' b c -> Traversal' a c+-- +-- We can compose traversals using function composition because a+-- `Traversal'` is a type synonym for a higher-order function:+-- +-- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a)+-- +-- So under the hood we are composing two functions to get back a new+-- function:+-- +-- > (.) :: Applicative f+-- > => ((b -> f b) -> (a -> f a))+-- > -> ((c -> f c) -> (b -> f b))+-- > -> ((c -> f c) -> (a -> f a))+-- +-- In our original @Molecule@ example, we composed four `Traversal'`s+-- together to create a new `Traversal'`:+-- +-- > -- Remember that `atoms`, `point`, and `x` are also `Traversal'`s+-- > atoms :: Traversal' Molecule [Atom]+-- > traverse :: Traversal' [Atom] Atom+-- > point :: Traversal' Atom Point+-- > x :: Traversal' Point Double+-- >+-- > -- Now compose them+-- > atoms :: Traversal' Molecule [Atom]+-- > atoms . traverse :: Traversal' Molecule Atom+-- > atoms . traverse . point :: Traversal' Molecule Point+-- > atoms . traverse . point . x :: Traversal' Molecule Double+-- +-- This composite traversal lets us get or set the @x@ coordinates of a+-- @Molecule@.+-- +-- > over (atoms . traverse . point . x)+-- > :: (Double -> Double) -> (Molecule -> Molecule)+-- >+-- > toListOf (atoms . traverse . point . x)+-- > :: Molecule -> [Double]+-- +-- /Question:/ How do I consume traversals?+-- +-- /Answer:/ Using `toListOf`, `set` or `over`+-- +-- Here are their types:+-- +-- > toListOf :: Traversal' a b -> a -> [b]+-- >+-- > over :: Traversal' a b -> (b -> b) -> a -> a+-- >+-- > set :: Traversal' a b -> b -> a -> a+-- > set traversal b = over traversal (\_ -> b)+--+-- Note that `toListOf` distributes over traversal composition:+-- +-- > toListOf (traversal1 . traversal2) = (toListOf traversal1) >=> (toListOf traversal2)+-- >+-- > toListOf id = return+--+-- If you prefer object-oriented syntax you can also use (`^..`), which is an+-- infix operator equivalent to `toListOf`:+--+-- >>> Pair 3 4 ^.. traverse+-- [3,4]++{- $types+ You might wonder why you can use `over` on both a `Lens'` and a+ `Traversal'` but you can only use `view` on a `Lens'`. We can see why by+ studying the (simplified) type and implementation of `over`:++> over :: ((b -> Identity b) -> (a -> Identity a)) -> (b -> b) -> a -> a+> over setter f x = runIdentity (setter (\y -> Identity (f y)) x)++ To follow the implementation, just step slowly through the types. Here+ are the types of the arguments to `over`:++> setter :: (b -> Identity b) -> (a -> Identity a)+> f :: b -> b+> x :: a++ ... and here are the types of the sub-expressions on the right-hand side:++> \y -> Identity (f y) :: b -> Identity b+> setter (\y -> Identity (f y)) :: a -> Identity a+> setter (\y -> Identity (f y)) x :: Identity a+> runIdentity (setter (\y -> Identity (f y)) x) :: a++ We can replace @setter@ with @point@ and replace @x@ with @atom@ to see+ that this generates the correct code for updating an atom's point:++> over point f atom+>+> -- Definition of `over`+> = runIdentity (point (\y -> Identity (f y)) atom)+>+> -- Definition of `point`+> = runIdentity (fmap (\newPoint -> atom { _point = newPoint }) (Identity (f (_point atom)))+>+> -- fmap g (Identity y) = Identity (g y)+> = runIdentity (Identity (atom { _point = f (_point atom) }))+>+> -- runIdentity (Identity z) = z+> = atom { _point = f (_point atom) }++ ... which is exactly what we would have written by hand without lenses.++ The reason `over` works for both `Lens'`es and `Traversal'`s is because+ `Identity` implements both `Functor` and `Applicative`:++> instance Functor Identity where ...+> instance Applicative Identity where ...++ So both the `Lens'` type and `Traversal'` type synonyms:++> type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a)+>+> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)++ ... can both be specialized to use `Identity` in place of @f@:++> (b -> Identity b) -> (a -> Identity a)++ ... making them valid arguments to `over`.++ Now let's study the (simplified) type and implementation of `view`:++> view :: ((b -> Const b b) -> (a -> Const b a)) -> a -> b+> view getter x = getConst (getter Const x)++ Again, we can walk slowly through the types of the arguments:++> getter :: (b -> Const b b) -> (a -> Const b a)+> x :: a++ ... and the types of the sub-expressions on the right-hand side:++> getter Const :: a -> Const b a+> getter Const x :: Const b a+> getConst (getter Const x) :: b++ Let's see how this plays out for the @point@ lens:++> view point atom+>+> -- Definition of `view`+> = getConst (point Const atom)+> +> -- Definition of `point`+> = getConst (fmap (\newPoint -> atom { _point = newPoint }) (Const (_point atom)))+>+> -- fmap g (Const y) = Const y+> = getConst (Const (_point atom))+>+> -- getConst (Const z) = z+> = _point atom++ ... which is exactly what we would have written by hand without lenses.++ `view` accepts `Lens'`es because `Const` implements `Functor`:++> instance Functor (Const b)++ ... so the `Lens'` type synonym:+++> type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)++ ... can be specialized to use @(`Const` b)@ in place of @f@:++> (b -> Const b b) -> (a -> Const b a)+++ ... making it a valid argument to `view`.++ Interestingly, `Const` implements also `Applicative`, but with a+ constraint:++> instance Monoid b => Applicative (Const b)++ This implies that we *can* use `view` on a `Traversal'`, but only if the+ value that we extract is a `Monoid`. Let's try this out:++>>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }+>>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } }+>>> let molecule = Molecule { _atoms = [atom1, atom2] }+>>> view (atoms . traverse . element) molecule+"CO"++ This works because our traversal's result is a `String`:++> atoms . traverse . element :: Traversal' Molecule String++ ... and `String` implements the `Data.Monoid.Monoid` interface. When you+ try to extract multiple strings using `view` they get flattened together+ into a single `String` using `Data.Monoid.mappend`.++ If you try to extract the element from an empty molecule:++>>> view (atoms . traverse . element) (Molecule { _atoms = [] })+""++ You get the empty string (i.e. `Data.Monoid.mempty`).++ This is why the result of a `Traversal'` needs to be a `Data.Monoid.Monoid`+ when using `view`. If the `Traversal'` points to more than one value you+ need some way to combine them into a single value (using+ `Data.Monoid.mappend`) and if the `Traversal'` points to less than one+ value you need a default value to return (using `Data.Monoid.mempty`).++ If you try to `view` a `Traversal'` that doesn't point to a+ `Data.Monoid.Monoid`, you will get the following type error:++> >>> view (atoms . traverse . point . x) molecule+> No instance for (Data.Monoid.Monoid Double)+> arising from a use of `traverse'+> In the first argument of `(.)', namely `traverse'+> In the second argument of `(.)', namely `traverse . point . x'+> In the first argument of `view', namely+> `(atoms . traverse . point . x)'++ The compiler complains that `Double` does not implement the+ `Data.Monoid.Monoid` type class, so there is no sensible way to merge all+ the x coordinates that our `Traversal'` points to. For these cases you+ should use `toListOf` instead.+-}++{- $drawbacks+ Lenses come with trade-offs, so you should use them wisely.++ For example, lenses do not produce the best error messages. Unless you+ understand how `Traversal'`s work you will probably not understand the+ above error message.++ Also, lenses increase the learning curve for new Haskell programmers, so+ you should consider avoiding them in tutorial code targeting novice+ Haskell programmers.++ Lenses also add a level of boilerplate to all data types to auto-generate+ lenses and increase compile times. So for small projects the overhead of+ adding lenses may dwarf the benefits.++ @lens@ is also a library with a large dependency tree, focused on being+ \"batteries included\" and covering a large cross-section of the Haskell+ ecosystem. Browsing the Hackage listing you will find support modules+ ranging from "System.FilePath.Lens" to "Control.Parallel.Strategies.Lens",+ and many more. If you need a more light-weight alternative you can use+ the @lens-simple@ or @microlens@ library, each of which provides a+ restricted subset of the @lens@ library with a much smaller dependency tree.++ The ideal use case for the @lens@ library is a medium-to-large project with+ rich and deeply nested types. In these large projects the benefits of using+ lenses outweigh the costs.+-}++{- $conclusion+ This tutorial covers an extremely small subset of this library. If you+ would like to learn more, you can begin by skimming the example code in the+ following modules:++ * "Control.Lens.Getter"++ * "Control.Lens.Setter"++ * "Control.Lens.Traversal"++ * "Control.Lens.Tuple"++ * "Control.Lens.Lens"++ * "Control.Lens.Review"++ * "Control.Lens.Prism"++ * "Control.Lens.Iso"++ The documentation for these modules includes several examples to get you+ started and help you build an intuition for more advanced tricks that were+ not covered in this tutorial.++ You can also study several long-form examples here:++ <https://github.com/ekmett/lens/tree/master/examples>++ If you prefer light-weight @lens@-compatible libraries, then check out+ @lens-simple@ or @micro-lens@:++ * <http://hackage.haskell.org/package/microlens microlens>++ * <http://hackage.haskell.org/package/lens-simple lens-simple>++ If you would like a broader survey of lens features, then you can check+ out these tutorials:++ * <https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial A little lens starter tutorial> - Introduces+Prisms, Isos and JSON functionality++ * <http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html Program imperatively using Haskell lenses> - Illustrates lens support for stateful code+-}++{- $exports+ These are the same types and lenses used throughout the tutorial, exported+ for your convenience.+-}++data Atom = Atom { _element :: String, _point :: Point } deriving (Show)++data Point = Point { _x :: Double, _y :: Double } deriving (Show)++data Molecule = Molecule { _atoms :: [Atom] } deriving (Show)++data Pair a = Pair a a deriving (Functor, Foldable, Traversable)++makeLenses ''Atom+makeLenses ''Point+makeLenses ''Molecule++-- These purely exist to ensure that the examples still type-check. I don't+-- export them, though, so that they won't conflict with the user's code.+shiftAtomX :: Atom -> Atom+shiftAtomX = over (point . x) (+ 1)++shiftMoleculeX :: Molecule -> Molecule+shiftMoleculeX = over (atoms . traverse . point . x) (+ 1)++shift :: ASetter' a Double -> a -> a+shift lens = over lens (+ 1)
test/Main.hs view
@@ -1,6 +1,6 @@-module Main where - -import Test.DocTest - -main :: IO () -main = doctest ["src/Control/Lens/Tutorial.hs"] +module Main where++import Test.DocTest++main :: IO ()+main = doctest ["src/Control/Lens/Tutorial.hs"]