├── LICENSE ├── Setup.hs ├── default.nix ├── lens-tutorial.cabal ├── release.nix ├── src └── Control │ └── Lens │ └── Tutorial.hs ├── stack.yaml └── test └── Main.hs /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, doctest, lens, stdenv }: 2 | mkDerivation { 3 | pname = "lens-tutorial"; 4 | version = "1.0.3"; 5 | src = ./.; 6 | libraryHaskellDepends = [ base lens ]; 7 | testHaskellDepends = [ base doctest ]; 8 | description = "Tutorial for the lens library"; 9 | license = stdenv.lib.licenses.bsd3; 10 | } 11 | -------------------------------------------------------------------------------- /lens-tutorial.cabal: -------------------------------------------------------------------------------- 1 | Name: lens-tutorial 2 | Version: 1.0.5 3 | Cabal-Version: >=1.10 4 | Build-Type: Simple 5 | License: BSD3 6 | License-File: LICENSE 7 | Copyright: 2015 Gabriella Gonzalez 8 | Author: Gabriella Gonzalez 9 | Maintainer: GenuineGabriella@gmail.com 10 | Bug-Reports: https://github.com/Gabriella439/Haskell-Lens-Tutorial-Library/issues 11 | Synopsis: Tutorial for the lens library 12 | Description: This is a basic tutorial that you can use to get started with 13 | the @lens@ library. This tutorial covers: 14 | . 15 | * The motivation behind the @lens@ library 16 | . 17 | * How to use the library for the most common use cases 18 | . 19 | * How to interpret type errors 20 | . 21 | * Basic familiarity with how lenses work under the hood 22 | Category: Control 23 | Source-Repository head 24 | Type: git 25 | Location: https://github.com/Gabriella439/Haskell-Lens-Tutorial-Library 26 | 27 | Library 28 | HS-Source-Dirs: src 29 | Build-Depends: base < 5, lens 30 | Exposed-Modules: Control.Lens.Tutorial 31 | Default-Language: Haskell2010 32 | 33 | test-suite tests 34 | Type: exitcode-stdio-1.0 35 | HS-Source-Dirs: test 36 | Main-Is: Main.hs 37 | GHC-Options: -O2 -Wall 38 | Default-Language: Haskell2010 39 | Build-Depends: 40 | base < 5 , 41 | doctest >= 0.9.12 && < 0.11 42 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | # You can build this repository using Nix by running: 2 | # 3 | # $ nix-build -A lens-tutorial release.nix 4 | # 5 | # You can also open up this repository inside of a Nix shell by running: 6 | # 7 | # $ nix-shell -A lens-tutorial.env release.nix 8 | # 9 | # ... and then Nix will supply the correct Haskell development environment for 10 | # you 11 | let 12 | config = { 13 | packageOverrides = pkgs: { 14 | haskellPackages = pkgs.haskellPackages.override { 15 | overrides = haskellPackagesNew: haskellPackagesOld: { 16 | lens-tutorial = haskellPackagesNew.callPackage ./default.nix { }; 17 | }; 18 | }; 19 | }; 20 | }; 21 | 22 | pkgs = 23 | import { inherit config; }; 24 | 25 | in 26 | { lens-tutorial = pkgs.haskellPackages.lens-tutorial; 27 | } 28 | -------------------------------------------------------------------------------- /src/Control/Lens/Tutorial.hs: -------------------------------------------------------------------------------- 1 | {-| This @lens@ tutorial targets Haskell beginners and assumes only basic 2 | familiarity with Haskell. By the end of this tutorial you should: 3 | 4 | * understand what problems the @lens@ library solves, 5 | 6 | * know when it is appropriate to use the @lens@ library, 7 | 8 | * be proficient in the most common @lens@ idioms, 9 | 10 | * understand the drawbacks of using lenses, and: 11 | 12 | * know where to look if you wish to learn more advanced tricks. 13 | 14 | If you would like to follow along with these examples, just import this 15 | module: 16 | 17 | > $ ghci 18 | > >>> import Control.Lens.Tutorial 19 | 20 | -} 21 | 22 | {-# LANGUAGE TemplateHaskell #-} 23 | {-# LANGUAGE DeriveFoldable #-} 24 | {-# LANGUAGE DeriveFunctor #-} 25 | {-# LANGUAGE DeriveTraversable #-} 26 | 27 | module Control.Lens.Tutorial ( 28 | -- * Motivation 29 | -- $motivation 30 | 31 | -- * Lenses 32 | -- $lenses 33 | 34 | -- * Accessor notation 35 | -- $accessors 36 | 37 | -- * First-class 38 | -- $firstclass 39 | 40 | -- * Traversals 41 | -- $traversals 42 | 43 | -- * Types 44 | -- $types 45 | 46 | -- * Drawbacks 47 | -- $drawbacks 48 | 49 | -- * Conclusion 50 | -- $conclusion 51 | 52 | -- * Exports 53 | -- $exports 54 | Atom(..) 55 | , element 56 | , point 57 | , Point(..) 58 | , x 59 | , y 60 | , Molecule(..) 61 | , atoms 62 | , Pair(..) 63 | , traverse 64 | ) where 65 | 66 | import Control.Applicative (Applicative) 67 | import Control.Lens hiding (element) 68 | import Control.Lens.TH 69 | import Data.Foldable (Foldable) 70 | import Data.Monoid (Monoid) 71 | 72 | -- $motivation 73 | -- 74 | -- The simplest problem that the @lens@ library solves is updating deeply 75 | -- nested records. Suppose you had the following nested Haskell data types: 76 | -- 77 | -- > data Atom = Atom { _element :: String, _point :: Point } 78 | -- > 79 | -- > data Point = Point { _x :: Double, _y :: Double } 80 | -- 81 | -- If you wanted to increase the @x@ coordinate of an `Atom` by one unit, you 82 | -- would have to write something like this in Haskell: 83 | -- 84 | -- > shiftAtomX :: Atom -> Atom 85 | -- > shiftAtomX (Atom e (Point x y)) = Atom e (Point (x + 1) y) 86 | -- 87 | -- This unpacking and repacking of data types grows increasingly difficult the 88 | -- more fields you add to each data type or the more deeply nested your data 89 | -- structures become. 90 | -- 91 | -- The @lens@ library solves this problem by letting you instead write: 92 | -- 93 | -- > -- atom.hs 94 | -- > 95 | -- > {-# LANGUAGE TemplateHaskell #-} 96 | -- > 97 | -- > import Control.Lens hiding (element) 98 | -- > import Control.Lens.TH 99 | -- > 100 | -- > data Atom = Atom { _element :: String, _point :: Point } deriving (Show) 101 | -- > 102 | -- > data Point = Point { _x :: Double, _y :: Double } deriving (Show) 103 | -- > 104 | -- > $(makeLenses ''Atom) 105 | -- > $(makeLenses ''Point) 106 | -- > 107 | -- > shiftAtomX :: Atom -> Atom 108 | -- > shiftAtomX = over (point . x) (+ 1) 109 | -- 110 | -- Let's convince ourselves that this works: 111 | -- 112 | -- >>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } 113 | -- >>> shiftAtomX atom 114 | -- Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}} 115 | -- 116 | -- The above solution does not change no matter how many fields we add to 117 | -- @Atom@ or @Point@. 118 | -- 119 | -- Now suppose that we added yet another data structure: 120 | -- 121 | -- > data Molecule = Molecule { _atoms :: [Atom] } deriving (Show) 122 | -- 123 | -- We could shift an entire @Molecule@ by writing: 124 | -- 125 | -- > $(makeLenses ''Molecule) 126 | -- > 127 | -- > shiftMoleculeX :: Molecule -> Molecule 128 | -- > shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) 129 | -- 130 | -- Again, this works the way we expect: 131 | -- 132 | -- >>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } 133 | -- >>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } } 134 | -- >>> let molecule = Molecule { _atoms = [atom1, atom2] } 135 | -- >>> shiftMoleculeX molecule -- Output formatted for clarity 136 | -- Molecule {_atoms = [Atom {_element = "C", _point = Point {_x = 2.0, _y = 2.0}},Atom {_element = "O", _point = Point {_x = 4.0, _y = 4.0}}]} 137 | -- 138 | -- ... or formatted for clarity: 139 | -- 140 | -- > Molecule 141 | -- > { _atoms = 142 | -- > [ Atom { _element = "C", _point = Point { _x = 2.0, _y = 2.0 } } 143 | -- > , Atom { _element = "O", _point = Point { _x = 4.0, _y = 4.0 } } 144 | -- > ] 145 | -- > } 146 | -- 147 | -- Many people stumble across lenses while trying to solve this common problem 148 | -- of working with data structures with a large number of fields or deeply 149 | -- nested values. These sorts of situations arise commonly in: 150 | -- 151 | -- * games with complex and deeply nested state 152 | -- 153 | -- * scientific data formats 154 | -- 155 | -- * sensor or instrument output 156 | -- 157 | -- * web APIs 158 | -- 159 | -- * XML and JSON 160 | -- 161 | -- * enterprise code where data structures can have tens, hundreds, or even 162 | -- thousands of fields (true story!) 163 | 164 | {- $lenses 165 | You might have some basic questions like: 166 | 167 | /Question:/ What is a lens? 168 | 169 | /Answer:/ A lens is a first class getter and setter 170 | 171 | We already saw how to use lenses to update values using `over`, but we can 172 | also use lenses to retrieve values using `view`: 173 | 174 | >>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } 175 | >>> view (point . x) atom 176 | 1.0 177 | 178 | In other words, lenses package both \"get\" and \"set\" functionality into 179 | a single value (the lens). You could pretend that a lens is a record 180 | with two fields: 181 | 182 | > data Lens a b = Lens 183 | > { view :: a -> b 184 | > , over :: (b -> b) -> (a -> a) 185 | > } 186 | 187 | That's not how lenses are actually implemented, but it's a useful 188 | starting intuition. 189 | 190 | /Question:/ What is the type of a lens? 191 | 192 | /Answer:/ We used two lenses in the above @Atom@ example, with these types: 193 | 194 | > point :: Lens' Atom Point 195 | > x :: Lens' Point Double 196 | 197 | The @point@ lens contains all the information we need to get or set the 198 | @_point@ field of the @Atom@ type (which is a `Point`). Similarly, the @x@ 199 | lens contains all the information we need to get or set the @_x@ field of 200 | the @Point@ data type (which is a `Double`). 201 | 202 | The convention for the `Lens'` type parameters is: 203 | 204 | > -- +-- Bigger type 205 | > -- | 206 | > -- v 207 | > Lens' bigger smaller 208 | > -- ^ 209 | > -- | 210 | > -- +-- Smaller type within the bigger type 211 | 212 | The actual definition of `Lens'` is: 213 | 214 | > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) 215 | 216 | You might wonder how you can fit both getter and setter functionality in 217 | a single value like this. The trick is that we get to pick what `Functor` 218 | we specialize @f@ to and depending on which `Functor` we pick we get 219 | different features. 220 | 221 | For example, if you pick @(f = `Identity`)@: 222 | 223 | > type ASetter' a b = (b -> Identity b) -> (a -> Identity a) 224 | > 225 | > -- ... equivalent to: (b -> b) -> (a -> a) 226 | 227 | ... you can build an `over`-like function. 228 | 229 | Similarly, if you pick @(f = `Const` b)@: 230 | 231 | > type Getting b a b = (b -> Const b b) -> (a -> Const b a) 232 | > 233 | > -- ... equivalent to: (b -> b ) -> (a -> b ) 234 | 235 | ... and if you apply a function of that type to `id` then you get a 236 | `view`-like function 237 | 238 | > -- (a -> b ) 239 | 240 | Those are not the only two `Functor`s we can pick. In fact, we can do a 241 | lot more with lenses than just get and set values, but those are the two 242 | most commonly used features. 243 | 244 | /Question:/ How do I create lenses? 245 | 246 | /Answer:/ You can either auto-generate them using Template Haskell or 247 | create them by hand 248 | 249 | In our @Atom@ example, we auto-generated the lenses using Template Haskell, 250 | like this: 251 | 252 | > makeLenses ''Atom 253 | > makeLenses ''Point 254 | 255 | This created four lenses of the following types: 256 | 257 | > element :: Lens' Atom String 258 | > point :: Lens' Atom Point 259 | > x :: Lens' Point Double 260 | > y :: Lens' Point Double 261 | 262 | For each field prefixed with an underscore, `makeLenses` creates one lens 263 | which has the same name as the corresponding field without the underscore. 264 | 265 | However, sometimes Template Haskell is not an option, so we can also use 266 | the `lens` utility function to build lenses. This utility has type: 267 | 268 | > lens :: (a -> b) -> (a -> b -> a) -> Lens' a b 269 | 270 | The first argument is a \"getter\" (a way to extract a @\'b\'@ from an 271 | @\'a\'@). The second argument is a \"setter\" (given a @b@, update an 272 | @a@). The result is a `Lens'` built from the getter and setter. You would 273 | use `lens` like this: 274 | 275 | > point :: Lens' Atom Point 276 | > point = lens _point (\atom newPoint -> atom { _point = newPoint }) 277 | 278 | You can even define lenses without incurring a dependency on the @lens@ 279 | library. Remember that lenses are just higher-order functions over 280 | `Functor`s, so we could instead write: 281 | 282 | > -- point :: Lens' Atom Point 283 | > point :: Functor f => (Point -> f Point) -> Atom -> f Atom 284 | > point k atom = fmap (\newPoint -> atom { _point = newPoint }) (k (_point atom)) 285 | 286 | This means that you can provide lenses for your library's types without 287 | depending on the @lens@ library. All you need is the `fmap` function, 288 | which is provided by the Haskell Prelude. 289 | 290 | /Question:/ How do I combine lenses? 291 | 292 | /Answer:/ You compose them, using function composition (Yes, really!) 293 | 294 | You can think of the function composition operator as having this type: 295 | 296 | > (.) :: Lens' a b -> Lens' b c -> Lens' a c 297 | 298 | We can compose lenses using function composition because `Lens'` is a 299 | type synonym for a higher-order function: 300 | 301 | > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) 302 | 303 | So under the hood we are composing two higher-order functions to get back a 304 | new higher-order function: 305 | 306 | > (.) :: Functor f 307 | > => ((b -> f b) -> (a -> f a)) 308 | > -> ((c -> f c) -> (b -> f b)) 309 | > -> ((c -> f c) -> (a -> f a)) 310 | 311 | In our original @Atom@ example, we composed the @point@ and @x@ lenses to 312 | create a new composite lens: 313 | 314 | > point :: Lens' Atom Point 315 | > x :: Lens' Point Double 316 | > 317 | > point . x :: Lens' Atom Double 318 | 319 | This composite lens lets us get or set the @x@ coordinate of an @Atom@. 320 | We can use `over` and `view` on the composite `Lens'` and they will behave 321 | exactly the way we expect: 322 | 323 | > view (point . x) :: Atom -> Double 324 | > 325 | > over (point . x) :: (Double -> Double) -> (Atom -> Atom) 326 | 327 | /Question:/ How do I consume lenses? 328 | 329 | /Answer:/ Using `view`, `set` or `over` 330 | 331 | Here are their types: 332 | 333 | > view :: Lens' a b -> a -> b 334 | > 335 | > over :: Lens' a b -> (b -> b) -> a -> a 336 | > 337 | > set :: Lens' a b -> b -> a -> a 338 | > set lens b = over lens (\_ -> b) 339 | 340 | `view` and `over` are the two fundamental functions on lenses. `set` is 341 | just a special case of `over`. 342 | 343 | `view` and `over` are fundamental because they distribute over lens 344 | composition: 345 | 346 | > view (lens1 . lens2) = (view lens2) . (view lens1) 347 | > 348 | > view id = id 349 | 350 | > over (lens1 . lens2) = (over lens1) . (over lens2) 351 | > 352 | > over id = id 353 | 354 | /Question:/ What else do I need to know? 355 | 356 | /Answer:/ That's pretty much it! 357 | 358 | For 90% of use cases, you just: 359 | 360 | * Create lenses (using `makeLens`, `lens` or plain-old `fmap`) 361 | 362 | * Compose them (using (`.`)) 363 | 364 | * Consume them (using `view`, `set`, and `over`) 365 | 366 | You could actually stop reading here if you are in a hurry since this 367 | covers the overwhelmingly common use case for the library. On the other 368 | hand, keep reading if you would like to learn additional tricks and 369 | features. 370 | -} 371 | 372 | {- $accessors 373 | You might be used to object-oriented languages where you could retrieve a 374 | nested field using: 375 | 376 | > atom.point.x 377 | 378 | You can do almost the exact same thing using the @lens@ library, except 379 | that the first dot will have a @^@ right before the dot: 380 | 381 | >>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } 382 | >>> atom^.point.x 383 | 1.0 384 | 385 | You can better understand why this works, by adding whitespace and 386 | explicit parentheses: 387 | 388 | > atom ^. (point . x) 389 | 390 | This trick uses (`^.`), which is an infix operator equivalent to `view`: 391 | 392 | > (^.) :: a -> Lens' a b -> b 393 | > x ^. l = view l x 394 | 395 | ... and you just keep adding dots after that for each lens you compose. 396 | This gives the appearance of object-oriented accessors if you omit the 397 | whitespace around the operators. 398 | -} 399 | 400 | {- $firstclass 401 | Lenses are \"first class\" values, meaning that you can manipulate them 402 | using ordinary functional programming techniques. You can take them as 403 | inputs, return them as outputs, or stick them in data structures. Anything 404 | goes! 405 | 406 | For example, suppose we don't want to define separate shift functions for 407 | @Atom@s and @Molecule@s: 408 | 409 | > shiftAtomX :: Atom -> Atom 410 | > shiftAtomX = over (point . x) (+ 1) 411 | 412 | > shiftMoleculeX :: Molecule -> Molecule 413 | > shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) 414 | 415 | We can instead unify them into a single function by parametrizing the 416 | shift function on the lens: 417 | 418 | > shift lens = over lens (+ 1) 419 | 420 | This lets us write: 421 | 422 | > shift (point . x) :: Atom -> Atom 423 | > 424 | > shift (atoms . traverse . point . x) :: Molecule -> Molecule 425 | 426 | Even better, we can define synonyms for our composite lenses: 427 | 428 | > atomX :: Lens' Atom Double 429 | > atomX = point . x 430 | > 431 | > -- We'll learn what `Traversal` means shortly 432 | > moleculeX :: Traversal' Molecule Double 433 | > moleculeX = atoms . traverse . point . x 434 | 435 | Now we can write code almost identical to the original code: 436 | 437 | > shift atomX :: Atom -> Atom 438 | > 439 | > shift moleculeX :: Molecule -> Molecule 440 | 441 | ... but we also get several other utilities for free: 442 | 443 | > set atomX :: Double -> Atom -> Atom 444 | > 445 | > set moleculeX :: Double -> Molecule -> Molecule 446 | > 447 | > view atomX :: Atom -> Double 448 | > 449 | > -- We can't use `view` for `Traversal'`s. Read on to find out why 450 | > toListOf moleculeX :: Molecule -> [Double] 451 | 452 | That's much more reusable, but you might wonder what this `Traversal'` and 453 | `toListOf` business is all about. 454 | -} 455 | 456 | -- $traversals 457 | -- /Question:/ What is a traversal? 458 | -- 459 | -- /Answer:/ A first class getter and setter for an arbitrary number of values 460 | -- 461 | -- A traversal lets you get all the values it points to as a list and it also 462 | -- lets you update or set all the values it points to. Think of a traversal 463 | -- as a record with two fields: 464 | -- 465 | -- > data Traversal' a b = Traversal' 466 | -- > { toListOf :: a -> [b] 467 | -- > , over :: (b -> b) -> (a -> a) 468 | -- > } 469 | -- 470 | -- That's not how traversals are actually implemented, but it's a useful 471 | -- starting intuition. 472 | -- 473 | -- We can still use `over` and `set` (a special case of `over`) with a 474 | -- traversal, but we use `toListOf` instead of `view`. 475 | -- 476 | -- /Question:/ What is the type of a traversal? 477 | -- 478 | -- /Answer:/ We used one traversal in the above @Molecule@ example: 479 | -- 480 | -- > moleculeX :: Traversal' Molecule Double 481 | -- 482 | -- This `Traversal'` lets us get or set an arbitrary number of x coordinates, 483 | -- each of which is a `Double`. There could be less than one x coordinate 484 | -- (i.e. 0 coordinates) or more than one x coordinate. Contrast this with a 485 | -- `Lens'` which can only get or set exactly one value. 486 | -- 487 | -- Like `Lens'`, `Traversal'` is a type synonym for a higher-order function: 488 | -- 489 | -- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) 490 | -- > 491 | -- > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) 492 | -- 493 | -- Notice that the only difference between a `Lens'` and a `Traversal'` is the 494 | -- type class constraint. A `Lens'` has a `Functor` constraint and 495 | -- `Traversal'` has an `Applicative` constraint. This means that any `Lens'` 496 | -- is automatically also a valid `Traversal'` (since `Functor` is a superclass 497 | -- of `Applicative`). 498 | -- 499 | -- Since every `Lens'` is a `Traversal'`, all of our example lenses also 500 | -- double as traversals: 501 | -- 502 | -- > atoms :: Traversal' Molecule [Atom] 503 | -- > element :: Traversal' Atom String 504 | -- > point :: Traversal' Atom Point 505 | -- > x :: Traversal' Point Double 506 | -- > y :: Traversal' Point Double 507 | -- 508 | -- We actually used yet another `Traversal'`, which was `traverse` (from 509 | -- "Data.Traversable"): 510 | -- 511 | -- > traverse :: Traversable t => Traversal' (t a) a 512 | -- 513 | -- This works because the `Traversal'` type synonym expands out to: 514 | -- 515 | -- > traverse :: (Applicative f, Traversable t) => (a -> f a) -> t a -> f (t a) 516 | -- 517 | -- ... which is exactly the traditional type signature of `traverse`. 518 | -- 519 | -- In our @Molecule@ example, we were using the special case where @t = []@: 520 | -- 521 | -- > traverse :: Traversal' [a] a 522 | -- 523 | -- In Haskell, you can derive `Functor`, `Data.Foldable.Foldable` and 524 | -- `Traversable` for many data types using the @DeriveFoldable@ and 525 | -- @DeriveTraversable@ extensions. This means that you can autogenerate a 526 | -- valid `traverse` for these data types: 527 | -- 528 | -- > {-# LANGUAGE DeriveFoldable #-} 529 | -- > {-# LANGUAGE DeriveFunctor #-} 530 | -- > {-# LANGUAGE DeriveTraversable #-} 531 | -- > 532 | -- > import Control.Lens 533 | -- > import Data.Foldable 534 | -- > 535 | -- > data Pair a = Pair a a deriving (Functor, Foldable, Traversable) 536 | -- 537 | -- We could then use `traverse` to navigate from `Pair` to its two children: 538 | -- 539 | -- > traverse :: Traversal' (Pair a) a 540 | -- > 541 | -- > over traverse :: (a -> a) -> (Pair a -> Pair a) 542 | -- > 543 | -- > over traverse (+ 1) (Pair 3 4) = Pair 4 5 544 | -- 545 | -- /Question:/ How do I create traversals? 546 | -- 547 | -- /Answer:/ There are three main ways to create primitive traversals: 548 | -- 549 | -- * `traverse` is a `Traversal'` that you get for any type that implements 550 | -- `Traversable` 551 | -- 552 | -- * Every `Lens'` will also type-check as a `Traversal'` 553 | -- 554 | -- * You can use Template Haskell to generate `Traversal'`s using `makePrisms` 555 | -- since every `Prism'` is also a `Traversal'` (not covered in this 556 | -- tutorial) 557 | -- 558 | -- /Question:/ How do I combine traversals? 559 | -- 560 | -- /Answer:/ You compose them, using function composition 561 | -- 562 | -- You can think of the function composition operator as having this type: 563 | -- 564 | -- > (.) :: Traversal' a b -> Traversal' b c -> Traversal' a c 565 | -- 566 | -- We can compose traversals using function composition because a 567 | -- `Traversal'` is a type synonym for a higher-order function: 568 | -- 569 | -- > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) 570 | -- 571 | -- So under the hood we are composing two functions to get back a new 572 | -- function: 573 | -- 574 | -- > (.) :: Applicative f 575 | -- > => ((b -> f b) -> (a -> f a)) 576 | -- > -> ((c -> f c) -> (b -> f b)) 577 | -- > -> ((c -> f c) -> (a -> f a)) 578 | -- 579 | -- In our original @Molecule@ example, we composed four `Traversal'`s 580 | -- together to create a new `Traversal'`: 581 | -- 582 | -- > -- Remember that `atoms`, `point`, and `x` are also `Traversal'`s 583 | -- > atoms :: Traversal' Molecule [Atom] 584 | -- > traverse :: Traversal' [Atom] Atom 585 | -- > point :: Traversal' Atom Point 586 | -- > x :: Traversal' Point Double 587 | -- > 588 | -- > -- Now compose them 589 | -- > atoms :: Traversal' Molecule [Atom] 590 | -- > atoms . traverse :: Traversal' Molecule Atom 591 | -- > atoms . traverse . point :: Traversal' Molecule Point 592 | -- > atoms . traverse . point . x :: Traversal' Molecule Double 593 | -- 594 | -- This composite traversal lets us get or set the @x@ coordinates of a 595 | -- @Molecule@. 596 | -- 597 | -- > over (atoms . traverse . point . x) 598 | -- > :: (Double -> Double) -> (Molecule -> Molecule) 599 | -- > 600 | -- > toListOf (atoms . traverse . point . x) 601 | -- > :: Molecule -> [Double] 602 | -- 603 | -- /Question:/ How do I consume traversals? 604 | -- 605 | -- /Answer:/ Using `toListOf`, `set` or `over` 606 | -- 607 | -- Here are their types: 608 | -- 609 | -- > toListOf :: Traversal' a b -> a -> [b] 610 | -- > 611 | -- > over :: Traversal' a b -> (b -> b) -> a -> a 612 | -- > 613 | -- > set :: Traversal' a b -> b -> a -> a 614 | -- > set traversal b = over traversal (\_ -> b) 615 | -- 616 | -- Note that `toListOf` distributes over traversal composition: 617 | -- 618 | -- > toListOf (traversal1 . traversal2) = (toListOf traversal1) >=> (toListOf traversal2) 619 | -- > 620 | -- > toListOf id = return 621 | -- 622 | -- If you prefer object-oriented syntax you can also use (`^..`), which is an 623 | -- infix operator equivalent to `toListOf`: 624 | -- 625 | -- >>> Pair 3 4 ^.. traverse 626 | -- [3,4] 627 | 628 | {- $types 629 | You might wonder why you can use `over` on both a `Lens'` and a 630 | `Traversal'` but you can only use `view` on a `Lens'`. We can see why by 631 | studying the (simplified) type and implementation of `over`: 632 | 633 | > over :: ((b -> Identity b) -> (a -> Identity a)) -> (b -> b) -> a -> a 634 | > over setter f x = runIdentity (setter (\y -> Identity (f y)) x) 635 | 636 | To follow the implementation, just step slowly through the types. Here 637 | are the types of the arguments to `over`: 638 | 639 | > setter :: (b -> Identity b) -> (a -> Identity a) 640 | > f :: b -> b 641 | > x :: a 642 | 643 | ... and here are the types of the sub-expressions on the right-hand side: 644 | 645 | > \y -> Identity (f y) :: b -> Identity b 646 | > setter (\y -> Identity (f y)) :: a -> Identity a 647 | > setter (\y -> Identity (f y)) x :: Identity a 648 | > runIdentity (setter (\y -> Identity (f y)) x) :: a 649 | 650 | We can replace @setter@ with @point@ and replace @x@ with @atom@ to see 651 | that this generates the correct code for updating an atom's point: 652 | 653 | > over point f atom 654 | > 655 | > -- Definition of `over` 656 | > = runIdentity (point (\y -> Identity (f y)) atom) 657 | > 658 | > -- Definition of `point` 659 | > = runIdentity (fmap (\newPoint -> atom { _point = newPoint }) (Identity (f (_point atom))) 660 | > 661 | > -- fmap g (Identity y) = Identity (g y) 662 | > = runIdentity (Identity (atom { _point = f (_point atom) })) 663 | > 664 | > -- runIdentity (Identity z) = z 665 | > = atom { _point = f (_point atom) } 666 | 667 | ... which is exactly what we would have written by hand without lenses. 668 | 669 | The reason `over` works for both `Lens'`es and `Traversal'`s is because 670 | `Identity` implements both `Functor` and `Applicative`: 671 | 672 | > instance Functor Identity where ... 673 | > instance Applicative Identity where ... 674 | 675 | So both the `Lens'` type and `Traversal'` type synonyms: 676 | 677 | > type Traversal' a b = forall f . Applicative f => (b -> f b) -> (a -> f a) 678 | > 679 | > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) 680 | 681 | ... can both be specialized to use `Identity` in place of @f@: 682 | 683 | > (b -> Identity b) -> (a -> Identity a) 684 | 685 | ... making them valid arguments to `over`. 686 | 687 | Now let's study the (simplified) type and implementation of `view`: 688 | 689 | > view :: ((b -> Const b b) -> (a -> Const b a)) -> a -> b 690 | > view getter x = getConst (getter Const x) 691 | 692 | Again, we can walk slowly through the types of the arguments: 693 | 694 | > getter :: (b -> Const b b) -> (a -> Const b a) 695 | > x :: a 696 | 697 | ... and the types of the sub-expressions on the right-hand side: 698 | 699 | > getter Const :: a -> Const b a 700 | > getter Const x :: Const b a 701 | > getConst (getter Const x) :: b 702 | 703 | Let's see how this plays out for the @point@ lens: 704 | 705 | > view point atom 706 | > 707 | > -- Definition of `view` 708 | > = getConst (point Const atom) 709 | > 710 | > -- Definition of `point` 711 | > = getConst (fmap (\newPoint -> atom { _point = newPoint }) (Const (_point atom))) 712 | > 713 | > -- fmap g (Const y) = Const y 714 | > = getConst (Const (_point atom)) 715 | > 716 | > -- getConst (Const z) = z 717 | > = _point atom 718 | 719 | ... which is exactly what we would have written by hand without lenses. 720 | 721 | `view` accepts `Lens'`es because `Const` implements `Functor`: 722 | 723 | > instance Functor (Const b) 724 | 725 | ... so the `Lens'` type synonym: 726 | 727 | 728 | > type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a) 729 | 730 | ... can be specialized to use @(`Const` b)@ in place of @f@: 731 | 732 | > (b -> Const b b) -> (a -> Const b a) 733 | 734 | 735 | ... making it a valid argument to `view`. 736 | 737 | Interestingly, `Const` implements also `Applicative`, but with a 738 | constraint: 739 | 740 | > instance Monoid b => Applicative (Const b) 741 | 742 | This implies that we *can* use `view` on a `Traversal'`, but only if the 743 | value that we extract is a `Monoid`. Let's try this out: 744 | 745 | >>> let atom1 = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } } 746 | >>> let atom2 = Atom { _element = "O", _point = Point { _x = 3.0, _y = 4.0 } } 747 | >>> let molecule = Molecule { _atoms = [atom1, atom2] } 748 | >>> view (atoms . traverse . element) molecule 749 | "CO" 750 | 751 | This works because our traversal's result is a `String`: 752 | 753 | > atoms . traverse . element :: Traversal' Molecule String 754 | 755 | ... and `String` implements the `Data.Monoid.Monoid` interface. When you 756 | try to extract multiple strings using `view` they get flattened together 757 | into a single `String` using `Data.Monoid.mappend`. 758 | 759 | If you try to extract the element from an empty molecule: 760 | 761 | >>> view (atoms . traverse . element) (Molecule { _atoms = [] }) 762 | "" 763 | 764 | You get the empty string (i.e. `Data.Monoid.mempty`). 765 | 766 | This is why the result of a `Traversal'` needs to be a `Data.Monoid.Monoid` 767 | when using `view`. If the `Traversal'` points to more than one value you 768 | need some way to combine them into a single value (using 769 | `Data.Monoid.mappend`) and if the `Traversal'` points to less than one 770 | value you need a default value to return (using `Data.Monoid.mempty`). 771 | 772 | If you try to `view` a `Traversal'` that doesn't point to a 773 | `Data.Monoid.Monoid`, you will get the following type error: 774 | 775 | > >>> view (atoms . traverse . point . x) molecule 776 | > No instance for (Data.Monoid.Monoid Double) 777 | > arising from a use of `traverse' 778 | > In the first argument of `(.)', namely `traverse' 779 | > In the second argument of `(.)', namely `traverse . point . x' 780 | > In the first argument of `view', namely 781 | > `(atoms . traverse . point . x)' 782 | 783 | The compiler complains that `Double` does not implement the 784 | `Data.Monoid.Monoid` type class, so there is no sensible way to merge all 785 | the x coordinates that our `Traversal'` points to. For these cases you 786 | should use `toListOf` instead. 787 | -} 788 | 789 | {- $drawbacks 790 | Lenses come with trade-offs, so you should use them wisely. 791 | 792 | For example, lenses do not produce the best error messages. Unless you 793 | understand how `Traversal'`s work you will probably not understand the 794 | above error message. 795 | 796 | Also, lenses increase the learning curve for new Haskell programmers, so 797 | you should consider avoiding them in tutorial code targeting novice 798 | Haskell programmers. 799 | 800 | Lenses also add a level of boilerplate to all data types to auto-generate 801 | lenses and increase compile times. So for small projects the overhead of 802 | adding lenses may dwarf the benefits. 803 | 804 | @lens@ is also a library with a large dependency tree, focused on being 805 | \"batteries included\" and covering a large cross-section of the Haskell 806 | ecosystem. Browsing the Hackage listing you will find support modules 807 | ranging from "System.FilePath.Lens" to "Control.Parallel.Strategies.Lens", 808 | and many more. If you need a more light-weight alternative you can use 809 | the @lens-simple@ or @microlens@ library, each of which provides a 810 | restricted subset of the @lens@ library with a much smaller dependency tree. 811 | 812 | The ideal use case for the @lens@ library is a medium-to-large project with 813 | rich and deeply nested types. In these large projects the benefits of using 814 | lenses outweigh the costs. 815 | -} 816 | 817 | {- $conclusion 818 | This tutorial covers an extremely small subset of this library. If you 819 | would like to learn more, you can begin by skimming the example code in the 820 | following modules: 821 | 822 | * "Control.Lens.Getter" 823 | 824 | * "Control.Lens.Setter" 825 | 826 | * "Control.Lens.Traversal" 827 | 828 | * "Control.Lens.Tuple" 829 | 830 | * "Control.Lens.Lens" 831 | 832 | * "Control.Lens.Review" 833 | 834 | * "Control.Lens.Prism" 835 | 836 | * "Control.Lens.Iso" 837 | 838 | The documentation for these modules includes several examples to get you 839 | started and help you build an intuition for more advanced tricks that were 840 | not covered in this tutorial. 841 | 842 | You can also study several long-form examples here: 843 | 844 | 845 | 846 | If you prefer light-weight @lens@-compatible libraries, then check out 847 | @lens-simple@ or @micro-lens@: 848 | 849 | * 850 | 851 | * 852 | 853 | If you would like a broader survey of lens features, then you can check 854 | out these tutorials: 855 | 856 | * - Introduces 857 | Prisms, Isos and JSON functionality 858 | 859 | * - Illustrates lens support for stateful code 860 | -} 861 | 862 | {- $exports 863 | These are the same types and lenses used throughout the tutorial, exported 864 | for your convenience. 865 | -} 866 | 867 | data Atom = Atom { _element :: String, _point :: Point } deriving (Show) 868 | 869 | data Point = Point { _x :: Double, _y :: Double } deriving (Show) 870 | 871 | data Molecule = Molecule { _atoms :: [Atom] } deriving (Show) 872 | 873 | data Pair a = Pair a a deriving (Functor, Foldable, Traversable) 874 | 875 | $(makeLenses ''Atom) 876 | $(makeLenses ''Point) 877 | $(makeLenses ''Molecule) 878 | 879 | -- These purely exist to ensure that the examples still type-check. I don't 880 | -- export them, though, so that they won't conflict with the user's code. 881 | shiftAtomX :: Atom -> Atom 882 | shiftAtomX = over (point . x) (+ 1) 883 | 884 | shiftMoleculeX :: Molecule -> Molecule 885 | shiftMoleculeX = over (atoms . traverse . point . x) (+ 1) 886 | 887 | shift :: ASetter' a Double -> a -> a 888 | shift lens = over lens (+ 1) 889 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-7.12 2 | -------------------------------------------------------------------------------- /test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Test.DocTest 4 | 5 | main :: IO () 6 | main = doctest ["src/Control/Lens/Tutorial.hs"] 7 | --------------------------------------------------------------------------------