├── .gitignore ├── DontFearTheProfunctorOptics.hs ├── Optics.md ├── ProfunctorOptics.md ├── Profunctors.md ├── README.md └── diagram ├── adapter.svg ├── affine.svg ├── cartesian-law1.svg ├── cartesian-law2.svg ├── cartesian.svg ├── cocartesian.svg ├── composition.svg ├── lens.svg ├── monoidal.svg ├── prism.svg ├── profunctor-law1.svg ├── profunctor-law2.svg ├── profunctor.svg └── traversal.svg /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | *.dyn_o 9 | *.dyn_hi 10 | .hpc 11 | .hsenv 12 | .cabal-sandbox/ 13 | cabal.sandbox.config 14 | *.prof 15 | *.aux 16 | *.hp 17 | *.eventlog 18 | .stack-work/ 19 | cabal.project.local 20 | .HTF/ 21 | -------------------------------------------------------------------------------- /DontFearTheProfunctorOptics.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE TupleSections #-} 3 | {-# LANGUAGE TypeSynonymInstances #-} 4 | 5 | module DontFearTheProfunctorOptics where 6 | 7 | import Data.Functor 8 | import Data.Functor.Constant 9 | import Data.List 10 | 11 | -------------------------------- 12 | -- Part I: Optics, Concretely -- 13 | -------------------------------- 14 | 15 | -- Adapter 16 | 17 | data Adapter s t a b = Adapter { from :: s -> a 18 | , to :: b -> t } 19 | 20 | fromTo :: Eq s => Adapter s s a a -> s -> Bool 21 | fromTo (Adapter f t) s = (t . f) s == s 22 | 23 | toFrom :: Eq a => Adapter s s a a -> a -> Bool 24 | toFrom (Adapter f t) a = (f . t) a == a 25 | 26 | shift :: Adapter ((a, b), c) ((a', b'), c') (a, (b, c)) (a', (b', c')) 27 | shift = Adapter f t where 28 | f ((a, b), c) = (a, (b, c)) 29 | t (a', (b', c')) = ((a', b'), c') 30 | 31 | -- Lens 32 | 33 | data Lens s t a b = Lens { view :: s -> a 34 | , update :: (b, s) -> t } 35 | 36 | viewUpdate :: Eq s => Lens s s a a -> s -> Bool 37 | viewUpdate (Lens v u) s = u ((v s), s) == s 38 | 39 | updateView :: Eq a => Lens s s a a -> a -> s -> Bool 40 | updateView (Lens v u) a s = v (u (a, s)) == a 41 | 42 | updateUpdate :: Eq s => Lens s s a a -> a -> a -> s -> Bool 43 | updateUpdate (Lens v u) a1 a2 s = u (a2, (u (a1, s))) == u (a2, s) 44 | 45 | pi1 :: Lens (a, c) (b, c) a b 46 | pi1 = Lens v u where 47 | v = fst 48 | u (b, (_, c)) = (b, c) 49 | 50 | (|.|) :: Lens s t a b -> Lens a b c d -> Lens s t c d 51 | (Lens v1 u1) |.| (Lens v2 u2) = Lens v u where 52 | v = v2 . v1 53 | u (d, s) = u1 ((u2 (d, (v1 s))), s) 54 | 55 | -- Prism 56 | 57 | data Prism s t a b = Prism { match :: s -> Either a t 58 | , build :: b -> t } 59 | 60 | matchBuild :: Eq s => Prism s s a a -> s -> Bool 61 | matchBuild (Prism m b) s = either b id (m s) == s 62 | 63 | buildMatch :: (Eq a, Eq s) => Prism s s a a -> a -> Bool 64 | buildMatch (Prism m b) a = m (b a) == Left a 65 | 66 | the :: Prism (Maybe a) (Maybe b) a b 67 | the = Prism m b where 68 | m = maybe (Right Nothing) Left 69 | b = Just 70 | 71 | -- Affine 72 | 73 | data Affine s t a b = Affine { preview :: s -> Either a t 74 | , set :: (b, s) -> t } 75 | 76 | previewSet :: Eq s => Affine s s a a -> s -> Bool 77 | previewSet (Affine p st) s = either (\a -> st (a, s)) id (p s) == s 78 | 79 | setPreview :: (Eq a, Eq s) => Affine s s a a -> a -> s -> Bool 80 | setPreview (Affine p st) a s = p (st (a, s)) == either (Left . const a) Right (p s) 81 | 82 | setSet :: Eq s => Affine s s a a -> a -> a -> s -> Bool 83 | setSet (Affine p st) a1 a2 s = st (a2, (st (a1, s))) == st (a2, s) 84 | 85 | maybeFirst :: Affine (Maybe a, c) (Maybe b, c) a b 86 | maybeFirst = Affine p st where 87 | p (ma, c) = maybe (Right (Nothing, c)) Left ma 88 | st (b, (ma, c)) = (ma $> b, c) 89 | 90 | -- Traversal 91 | 92 | data Traversal s t a b = Traversal { contents :: s -> [a] 93 | , fill :: ([b], s) -> t } 94 | 95 | firstNSecond :: Traversal (a, a, c) (b, b, c) a b 96 | firstNSecond = Traversal c f where 97 | c (a1, a2, _) = [a1, a2] 98 | f (bs, (_, _, x)) = (head bs, (head . tail) bs, x) 99 | 100 | -- Right Traversal 101 | 102 | data FunList a b t = Done t | More a (FunList a b (b -> t)) 103 | 104 | newtype Traversal' s t a b = Traversal' { extract :: s -> FunList a b t } 105 | 106 | firstNSecond'' :: Traversal' (a, a, c) (b, b, c) a b 107 | firstNSecond'' = Traversal' (\(a1, a2, c) -> More a1 (More a2 (Done (,,c)))) 108 | 109 | --------------------------------------------------- 110 | -- Part II: Profunctors as Generalized Functions -- 111 | --------------------------------------------------- 112 | 113 | -- Functor 114 | 115 | -- class Functor f where 116 | -- fmap :: (a -> b) -> f a -> f b 117 | 118 | -- instance Functor ((->) r) where 119 | -- fmap f g = g . f 120 | 121 | -- Contravariant 122 | 123 | class Contravariant f where 124 | cmap :: (b -> a) -> f a -> f b 125 | 126 | newtype CReader r a = CReader (a -> r) 127 | 128 | instance Contravariant (CReader r) where 129 | cmap f (CReader g) = CReader (g . f) 130 | 131 | -- Profunctor 132 | 133 | class Profunctor p where 134 | dimap :: (a' -> a) -> (b -> b') -> p a b -> p a' b' 135 | 136 | lmap :: (a' -> a) -> p a b -> p a' b 137 | lmap f = dimap f id 138 | rmap :: (b -> b') -> p a b -> p a b' 139 | rmap f = dimap id f 140 | 141 | instance Profunctor (->) where 142 | dimap f g h = g . h . f 143 | 144 | -- Cartesian 145 | 146 | class Profunctor p => Cartesian p where 147 | first :: p a b -> p (a, c) (b, c) 148 | second :: p a b -> p (c, a) (c, b) 149 | 150 | instance Cartesian (->) where 151 | first f (a, c) = (f a, c) 152 | second f (c, a) = (c, f a) 153 | 154 | -- Cocartesian 155 | 156 | class Profunctor p => Cocartesian p where 157 | left :: p a b -> p (Either a c) (Either b c) 158 | right :: p a b -> p (Either c a) (Either c b) 159 | 160 | instance Cocartesian (->) where 161 | left f = either (Left . f) Right 162 | right f = either Left (Right . f) 163 | 164 | -- Monoidal 165 | 166 | class Profunctor p => Monoidal p where 167 | par :: p a b -> p c d -> p (a, c) (b, d) 168 | empty :: p () () 169 | 170 | instance Monoidal (->) where 171 | par f g (a, c) = (f a, g c) 172 | empty = id 173 | 174 | -- Beyond Functions 175 | 176 | newtype UpStar f a b = UpStar { runUpStar :: a -> f b } 177 | 178 | instance Functor f => Profunctor (UpStar f) where 179 | dimap f g (UpStar h) = UpStar (fmap g . h . f) 180 | 181 | instance Functor f => Cartesian (UpStar f) where 182 | first (UpStar f) = UpStar (\(a, c) -> fmap (,c) (f a)) 183 | second (UpStar f) = UpStar (\(c, a) -> fmap (c,) (f a)) 184 | 185 | instance Applicative f => Cocartesian (UpStar f) where 186 | left (UpStar f) = UpStar (either (fmap Left . f) (fmap Right . pure)) 187 | right (UpStar f) = UpStar (either (fmap Left . pure) (fmap Right . f)) 188 | 189 | instance Applicative f => Monoidal (UpStar f) where 190 | par (UpStar f) (UpStar g) = UpStar (\(a, b) -> (,) <$> f a <*> g b) 191 | empty = UpStar pure 192 | 193 | newtype Tagged a b = Tagged { unTagged :: b } 194 | 195 | instance Profunctor Tagged where 196 | dimap _ g (Tagged b) = Tagged (g b) 197 | 198 | instance Cocartesian Tagged where 199 | left (Tagged b) = Tagged (Left b) 200 | right (Tagged b) = Tagged (Right b) 201 | 202 | instance Monoidal Tagged where 203 | par (Tagged b) (Tagged d) = Tagged (b, d) 204 | empty = Tagged () 205 | 206 | --------------------------------- 207 | -- Part III: Profunctor Optics -- 208 | --------------------------------- 209 | 210 | type Optic p s t a b = p a b -> p s t 211 | 212 | -- Profunctor Adapter 213 | 214 | type AdapterP s t a b = forall p . Profunctor p => Optic p s t a b 215 | 216 | adapterC2P :: Adapter s t a b -> AdapterP s t a b 217 | adapterC2P (Adapter f t) = dimap f t 218 | 219 | from' :: AdapterP s t a b -> s -> a 220 | from' ad = getConstant . runUpStar (ad (UpStar Constant)) 221 | 222 | to' :: AdapterP s t a b -> b -> t 223 | to' ad = unTagged . ad . Tagged 224 | 225 | shift' :: AdapterP ((a, b), c) ((a', b'), c') (a, (b, c)) (a', (b', c')) 226 | shift' = dimap assoc assoc' where 227 | assoc ((x, y), z) = (x, (y, z)) 228 | assoc' (x, (y, z)) = ((x, y), z) 229 | 230 | -- Profunctor Lens 231 | 232 | type LensP s t a b = forall p . Cartesian p => Optic p s t a b 233 | 234 | lensC2P :: Lens s t a b -> LensP s t a b 235 | lensC2P (Lens v u) = dimap dup u . first . lmap v where 236 | dup a = (a, a) 237 | 238 | view' :: LensP s t a b -> s -> a 239 | view' ln = getConstant . runUpStar (ln (UpStar Constant)) 240 | 241 | update' :: LensP s t a b -> (b, s) -> t 242 | update' ln (b, s) = ln (const b) s 243 | 244 | pi1' :: LensP (a, c) (b, c) a b 245 | pi1' = first 246 | 247 | -- Profunctor Prism 248 | 249 | type PrismP s t a b = forall p . Cocartesian p => Optic p s t a b 250 | 251 | prismC2P :: Prism s t a b -> PrismP s t a b 252 | prismC2P (Prism m b) = dimap m (either id id) . left . rmap b 253 | 254 | match' :: PrismP s t a b -> s -> Either a t 255 | match' pr = runUpStar (pr (UpStar Left)) 256 | 257 | build' :: PrismP s t a b -> b -> t 258 | build' pr = unTagged . pr . Tagged 259 | 260 | the' :: PrismP (Maybe a) (Maybe b) a b 261 | the' = dimap (maybe (Right Nothing) Left) (either Just id) . left 262 | 263 | -- Profunctor Affine 264 | 265 | type AffineP s t a b = forall p . (Cartesian p, Cocartesian p) => Optic p s t a b 266 | 267 | affineC2P :: Affine s t a b -> AffineP s t a b 268 | affineC2P (Affine p st) = dimap preview' merge . left . rmap st . first where 269 | preview' s = either (\a -> Left (a, s)) Right (p s) 270 | merge = either id id 271 | 272 | preview' :: AffineP s t a b -> s -> Either a t 273 | preview' af = runUpStar (af (UpStar Left)) 274 | 275 | set' :: AffineP s t a b -> (b, s) -> t 276 | set' af (b, s) = af (const b) s 277 | 278 | maybeFirst' :: AffineP (Maybe a, c) (Maybe b, c) a b 279 | maybeFirst' = first . dimap (maybe (Right Nothing) Left) (either Just id) . left 280 | 281 | maybeFirst'' :: AffineP (Maybe a, c) (Maybe b, c) a b 282 | maybeFirst'' = pi1' . the' 283 | 284 | -- Profunctor Traversal 285 | 286 | type TraversalP s t a b = forall p . (Cartesian p, Cocartesian p, Monoidal p) => Optic p s t a b 287 | 288 | traversalC2P :: Traversal s t a b -> TraversalP s t a b 289 | traversalC2P (Traversal c f) = dimap dup f . first . lmap c . ylw where 290 | ylw h = dimap (maybe (Right []) Left . uncons) merge $ left $ rmap cons $ par h (ylw h) 291 | cons = uncurry (:) 292 | dup a = (a, a) 293 | merge = either id id 294 | 295 | contents' :: TraversalP s t a b -> s -> [a] 296 | contents' tr = getConstant . runUpStar (tr (UpStar (\a -> Constant [a]))) 297 | 298 | firstNSecond' :: TraversalP (a, a, c) (b, b, c) a b 299 | firstNSecond' pab = dimap group group' (first (pab `par` pab)) where 300 | group (x, y, z) = ((x, y), z) 301 | group' ((x, y), z) = (x, y, z) 302 | -------------------------------------------------------------------------------- /Optics.md: -------------------------------------------------------------------------------- 1 | # Don't Fear the Profunctor Optics (Part 1/3) 2 | 3 | It is said that [profunctors are so easy](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/profunctors). What about [profunctor optics](http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/poptics.pdf)? They should be easy too, right? However, it might be a little bit scary and confusing to face a definition such as 4 | 5 | ```haskell 6 | type LensP s t a b = forall p . Cartesian p => p a b -> p s t 7 | ``` 8 | 9 | for the very first time. In this series, we'll try to introduce all the required concepts that you need to understand this alias (and other ones). To do so, we'll provide visual diagrams where profunctors are seen as boxes which take inputs and produce outputs and profunctor optics are just functions that transform those boxes into more complex ones. Prior to that, a brief introduction to optics will be supplied. We hope these resources help you to never ever fear the profunctor optics. 10 | 11 | ## Optics, Concretely 12 | 13 | Optics are essentially abstractions to update immutable data structures in an elegant way. Besides profunctor, there are [many other optic representations](https://www.twanvl.nl/files/lenses-talk-2011-05-17.pdf). The original one, also known as *concrete*, is perhaps the most accessible for a newcomer. Thereby, we'll introduce optics using this representation and later, the same examples will be translated into profunctor optics. Although not the simplest one, *Lens* has become the most famous optic, so we'll start by describing it. 14 | 15 | ### Lens 16 | 17 | Informally, lenses are useful to access a certain *focus* value which is contextualized in a bigger *whole* value. What do we mean by "access" here? At least, we'd like to be able to view and update the focus, given an original whole value. Translating this requirements into code, we get our initial lens definition where `s` is the whole and `a` is the focus: 18 | 19 | ```haskell 20 | data Lens s a = Lens { view :: s -> a 21 | , update :: (a, s) -> s } 22 | ``` 23 | 24 | The typical lens example is `π1`, which access the first component (the focus) of a 2-tuple (the whole): 25 | 26 | ```haskell 27 | π1 :: Lens (a, b) a 28 | π1 = Lens v u where 29 | v = fst 30 | u (a', (_, b)) = (a', b) 31 | ``` 32 | 33 | We provide an usage example as well: 34 | 35 | ```haskell 36 | λ> view π1 (1, 'a') 37 | 1 38 | λ> update π1 (2, (1, 'a')) 39 | (2,'a') 40 | ``` 41 | 42 | This is nice, but we could do better. What if we teach our lenses to morph the type of the focus (and consequently, the type of the whole)? I mean, what if we want to replace `1` with `"hi"` in the previous example? To do so, we need to slightly modify our former lens definition, in order to support [this kind of polymorphism](http://r6.ca/blog/20120623T104901Z.html): 43 | 44 | ```haskell 45 | data Lens s t a b = Lens { view :: s -> a 46 | , update :: (b, s) -> t } 47 | ``` 48 | 49 | As a result, `π1` turns into: 50 | 51 | ```haskell 52 | π1 :: Lens (a, c) (b, c) a b 53 | π1 = Lens v u where 54 | v = fst 55 | u (b, (_, c)) = (b, c) 56 | ``` 57 | 58 | Surprisingly, both versions share the same implementation, but notice the change in the signature. Now, we can use our new lens in a polymorphic way: 59 | 60 | ```haskell 61 | λ> update π1 ("hi", (1, 'a')) 62 | ("hi",'a') 63 | ``` 64 | 65 | It is worth mentioning that lenses should hold a few laws: 66 | 67 | ```haskell 68 | viewUpdate :: Eq s => Lens s s a a -> s -> Bool 69 | viewUpdate (Lens v u) s = u ((v s), s) == s 70 | 71 | updateView :: Eq a => Lens s s a a -> a -> s -> Bool 72 | updateView (Lens v u) a s = v (u (a, s)) == a 73 | 74 | updateUpdate :: Eq s => Lens s s a a -> a -> a -> s -> Bool 75 | updateUpdate (Lens v u) a1 a2 s = u (a2, (u (a1, s))) == u (a2, s) 76 | ``` 77 | 78 | Informally, these laws check that `update` is exclusively modifying the focus and that `view` extracts that focus value as is. 79 | 80 | You might be thinking that lenses are not necessary to achieve such a simple task. Why should we care about them? The thing is that they have become very handy to deal with nested immutable data structures. In fact, this is a direct consequence of one of the major features of optics: they compose! As an example, we could compose lenses to update the first component of a 2-tuple which is surrounded by additional 2-tuple layers: 81 | 82 | ```haskell 83 | λ> update (π1 |.| π1 |.| π1) ("hi", (((1, 'a'), 2.0), True)) 84 | ((("hi",'a'),2.0),True) 85 | ``` 86 | 87 | The composition method is implemented as follows: 88 | 89 | ```haskell 90 | (|.|) :: Lens s t a b -> Lens a b c d -> Lens s t c d 91 | (Lens v1 u1) |.| (Lens v2 u2) = Lens v u where 92 | v = v2 . v1 93 | u (d, s) = u1 ((u2 (d, (v1 s))), s) 94 | ``` 95 | 96 | Nevertheless, this way of composing optics is clumsy. This is evidenced when we try to compose lenses with other kinds of optics, where we require a different method for each kind we want to compose with. Consequently, libraries that use concrete representation become more verbose, and programmers that use this libraries require a deep knowledge on the composition interface. As we'll see in further sections, profunctor optics make composition trivial. For now, let's forget about composition and focus on getting comfortable with concrete optic operations. 97 | 98 | ### Adapter 99 | 100 | The next optic that will be covered is *Adapter*. As its name suggests, this optic is able to adapt values. Particularly, it adapts the whole value to the focus value, and viceversa. In fact, this optic manifests that both whole and focus values contain the same information. The polymorphic representation for adapters is implemented this way: 101 | 102 | ```haskell 103 | data Adapter s t a b = Adapter { from :: s -> a 104 | , to :: b -> t } 105 | ``` 106 | 107 | Adapters should obey some rules as well: 108 | 109 | ```haskell 110 | fromTo :: Eq s => Adapter s s a a -> s -> Bool 111 | fromTo (Adapter f t) s = (t . f) s == s 112 | 113 | toFrom :: Eq a => Adapter s s a a -> a -> Bool 114 | toFrom (Adapter f t) a = (f . t) a == a 115 | ``` 116 | 117 | Basically, they're telling us that this optic should behave as an isomorphism. 118 | 119 | As an adapter example, we supply `shift`, which evidences that associativity changes in tuples aren't lossy: 120 | 121 | ```haskell 122 | shift :: Adapter ((a, b), c) ((a', b'), c') (a, (b, c)) (a', (b', c')) 123 | shift = Adapter f t where 124 | f ((a, b), c) = (a, (b, c)) 125 | t (a', (b', c')) = ((a', b'), c') 126 | ``` 127 | 128 | We show a simple usage scenario for `shift` in the next gist: 129 | 130 | ```haskell 131 | λ> from shift ((1, "hi"), True) 132 | (1,("hi",True)) 133 | λ> to shift (True, ("hi", 1)) 134 | ((True,"hi"),1) 135 | ``` 136 | 137 | ### Prism 138 | 139 | `Prism` emerges when there's the possibility that the focus value isn't available, though we can always reassemble the whole value when given the focus. If you're familiar with algebraic datatypes, lenses deal with product types while prisms deal with sum types. Prisms are represented as follows: 140 | 141 | ```haskell 142 | data Prism s t a b = Prism { match :: s -> Either a t 143 | , build :: b -> t } 144 | ``` 145 | 146 | They have two operations: `match` and `build`. The first one tries to extract the focus value from the whole one, but if it's not possible, it provides the final value for `t`. On the other hand, `build` is always able to construct the whole value, given the focus one. As expected, this optic should hold the following properties: 147 | 148 | ```haskell 149 | matchBuild :: Eq s => Prism s s a a -> s -> Bool 150 | matchBuild (Prism m b) s = either b id (m s) == s 151 | 152 | buildMatch :: (Eq a, Eq s) => Prism s s a a -> a -> Bool 153 | buildMatch (Prism m b) a = m (b a) == Left a 154 | ``` 155 | 156 | They manifest the consistency between `match` and `build`: if we are able to view an existing focus, building it will return the original structure; if we build a whole from any focus, that whole must contain a focus. 157 | 158 | A common prism is `the` that focuses on the value which is hidden behind a `Maybe` type, if any: 159 | 160 | ```haskell 161 | the :: Prism (Maybe a) (Maybe b) a b 162 | the = Prism (maybe (Right Nothing) Left) Just 163 | ``` 164 | 165 | Now, we can appreciate that it's not always possible to get the focus from a `Maybe` value, though we can build a whole `Maybe` if we have the focus, simply using `Just`: 166 | 167 | ```haskell 168 | λ> match the (Just 1) 169 | Left 1 170 | λ> match the Nothing 171 | Right Nothing 172 | λ> build the 1 173 | Just 1 174 | λ> build the "hi" 175 | Just "hi" 176 | ``` 177 | 178 | ### Affine 179 | 180 | There's a little-known optic which is a hybrid between lenses and prisms. It's known as [*Affine*](http://oleg.fi/gists/posts/2017-03-20-affine-traversal.html) or [Optional](http://julien-truffaut.github.io/Monocle/optics/optional.html). Like prisms, this optic expresses that the focus value might not exist. Like lenses, this optic expresses that, given the focus value, we should be able to build the whole value, but unlike prisms, we'd need context information to do so. In Haskell, this optic is encoded as follows: 181 | 182 | ```haskell 183 | data Affine s t a b = Affine { preview :: s -> Either a t 184 | , set :: (b, s) -> t } 185 | ``` 186 | 187 | We can see two methods here: `preview` and `set`. As you might have noticed, this optic has borrowed prism's `match` and lens' `update`. The intuition behind these methods is the same that the one behind their counterparts. These are affine's laws: 188 | 189 | ```haskell 190 | previewSet :: Eq s => Affine s s a a -> s -> Bool 191 | previewSet (Affine p st) s = either (\a -> st (a, s)) id (p s) == s 192 | 193 | setPreview :: (Eq a, Eq s) => Affine s s a a -> a -> s -> Bool 194 | setPreview (Affine p st) a s = p (st (a, s)) == either (Left . const a) Right (p s) 195 | 196 | setSet :: Eq s => Affine s s a a -> a -> a -> s -> Bool 197 | setSet (Affine p st) a1 a2 s = st (a2, (st (a1, s))) == st (a2, s) 198 | ``` 199 | 200 | The intuition is pretty similar to the one behind prisms, but there's an important difference: when setting a value, it doesn't necessarily mean that `preview` will return it, but in case it does, the value will be exactly the one which was set. In fact, `set` only updates the whole structure if it does contain a focus value. 201 | 202 | Here's an example of affine, which tries to access `a` in `(Maybe a, c)`: 203 | 204 | ```haskell 205 | maybeFirst :: Affine (Maybe a, c) (Maybe b, c) a b 206 | maybeFirst = Affine p st where 207 | p (ma, c) = maybe (Right (Nothing, c)) Left ma 208 | st (b, (ma, c)) = (ma $> b, c) 209 | ``` 210 | 211 | There's not always an `a` hidden behind this data structure. On the other hand, we can't build a whole `(Maybe b, c)` simply from a `b`. In fact, we need a `c` to do so. In addition, the affine laws make it impossible to update the focus if it didn't exist in the whole. Therefore, we need the complete `(Maybe a, c)` as contextual information. Next, we show a scenario where we run this optic: 212 | 213 | ```haskell 214 | λ> preview maybeFirst (Just 1, "hi") 215 | Left 1 216 | λ> preview maybeFirst (Nothing, "hi") 217 | Right (Nothing,"hi") 218 | λ> set maybeFirst ('a', (Just 1, "hi")) 219 | (Just 'a',"hi") 220 | λ> set maybeFirst ('a', (Nothing, "hi")) 221 | (Nothing,"hi") 222 | ``` 223 | 224 | Although we implemented `maybeFirst` in a monolithical way for pedagogical reasons, you have probably noticed that this example combines somehow `π1` with `the`. This intuition is nice and we'll come back to it in further parts, when we cover optic composition in detail. 225 | 226 | ### Traversal 227 | 228 | Finally, *Traversal* will be introduced. This optic is very useful when the whole value contains a sequence of focus values of the same type. This includes the possibility of having zero, just one, or more than one focus values. Naively, we could try to represent a traversal as follows: 229 | 230 | ```haskell 231 | data Traversal s t a b = Traversal { contents :: s -> [a] 232 | , fill :: ([b], s) -> t } 233 | ``` 234 | 235 | Here, `contents` is responsible for getting all the focus values, while `fill` updates them. However, this way of representing traversals is wrong, since the number of focus values should be determined by `s` and must be consistent among `contents` and `fill`. For that reason, traversals are concretely represented with a [nested list](https://twanvl.nl/blog/haskell/non-regular1) coalgebra or a [store free applicative](https://bartoszmilewski.com/2015/07/13/from-lenses-to-yoneda-embedding/) coalgebra, where the aforementioned conditions are preserved. However, these definitions are beyond the scope of this post. From now on, we'll be using our fake traversal, since it provides a nice introductory intuition. Similarly, traversal laws won't be covered. As usual, we provide a traversal example: 236 | 237 | ```haskell 238 | firstNSecond :: Traversal (a, a, c) (b, b, c) a b 239 | firstNSecond = Traversal c f where 240 | c (a1, a2, _) = [a1, a2] 241 | f (bs, (_, _, x)) = (head bs, (head . tail) bs, x) 242 | ``` 243 | 244 | This traversal includes the first and second components of a 3-tuple as focus values. Notice that the impure `fill` implementation is just a consequence of using our fake representation. Think of what would happen if we provided an empty list while filling. Anyway, here's how we use `firstNSecond`: 245 | 246 | ```haskell 247 | λ> contents firstNSecond (1, 2, "hi") 248 | [1,2] 249 | λ> fill firstNSecond (['a', 'b'], (1, 2, "hi")) 250 | ('a','b',"hi") 251 | ``` 252 | 253 | ### Conclusions 254 | 255 | Today, we've introduced some of the most representative optics (along with their associated laws) in their concrete representation. They all hold the notion of focus and whole, which will be very useful when facing profunctor optics. On the other hand, we could appreciate that traversals are quite tricky. If you feel curious about the right way of representing them, you can read [this great article](https://arxiv.org/pdf/1103.2841.pdf) by Russell O'Connor, where they were firstly introduced. The next day, we'll cover profunctors, which suppose an intermediate step of preparation before dealing with profunctor optics. 256 | 257 | NEXT: [Profunctors as Generalized Functions](Profunctors.md) 258 | -------------------------------------------------------------------------------- /ProfunctorOptics.md: -------------------------------------------------------------------------------- 1 | # Don't Fear the Profunctor Optics (Part 3/3) 2 | 3 | PREVIOUSLY: [Profunctors as Generalized Functions](Profunctors.md) 4 | 5 | ## Profunctor Optics 6 | 7 | Profunctor, as concrete, is just another representation for optics. The general 8 | structure for profunctor optics is the next one: 9 | 10 | ```haskell 11 | type Optic s t a b = forall p . (C0 p, ..., CN p) => p a b -> p s t 12 | ``` 13 | 14 | So, every optic defined using this representation should know how to turn a `p a 15 | b` into a `p s t`, for any type `p` (notice the universal quantification 16 | `forall`), as long as it satisfies certain constraints (`C0`, `CN`, etc.), which 17 | will vary depending on the particular optic we want to represent. What does this 18 | mean? Previously, we said that we can see this profunctors as generalizations of 19 | functions, and we represented them as boxes. Besides, we could appreciate that 20 | optics in general, are abstractions that deal with polymorphic focus and whole 21 | values. Having said so, the alias we have just shown tells us that in order to 22 | fulfill an optic, we must determine how to take any generalized function on the 23 | focus to its counterpart on the whole. 24 | 25 | For each optic kind, we'll show how to expand a focus box into a whole box, 26 | using our diagram notation and the concrete representation. That will determine 27 | the minimal constraints that are needed to conform the particular optic. Then, 28 | we'll follow the opposite direction, bringing the concrete representation from 29 | the profunctor one. Finally, the examples which were shown in the first 30 | installment (`π1`, `the`, etc.) of this post series will be redefined with the 31 | new representation. 32 | 33 | ### Profunctor Adapter 34 | 35 | We'll start by `Adapter`, given its simple nature. Recall that we'll be facing 36 | the same problem for every optic kind: we need to turn a `p a b` into a `p s t`, 37 | given any `p` that satisfies the particular constraints. Undoubtedly, the 38 | extension process will be different for each case. Particularly, we saw that 39 | adapters are represented concretely by means of `from :: s -> a` and `to :: b -> 40 | t`. How could we get a `p s t` given `p a b` (for any type constructor `p`) and 41 | this pair of functions? We show it in the next picture: 42 | 43 | ![adapter](diagram/adapter.svg) 44 | 45 | Thereby, the only feature that we require to extend `h :: p a b` into `p s t` is 46 | `Profunctor`'s `dimap`. That's why profunctor adapters are represented as 47 | follows: 48 | 49 | ```haskell 50 | type AdapterP s t a b = forall p . Profunctor p => p a b -> p s t 51 | ``` 52 | 53 | In fact, we could translate the diagram above into Haskell this way: 54 | 55 | ```haskell 56 | adapterC2P :: Adapter s t a b -> AdapterP s t a b 57 | adapterC2P (Adapter f t) = dimap f t 58 | ``` 59 | 60 | Conversely, how do we recover the concrete representation from the profunctor 61 | one? To do so, we need to use a specific profunctor instance for each operator 62 | of the concrete representation (`from` & `to`). For instance, we require `UpStar 63 | Constant` and `Tagged` to recover `from` and `to`, respectively: 64 | 65 | ```haskell 66 | from' :: AdapterP s t a b -> s -> a 67 | from' ad = getConstant . runUpStar (ad (UpStar Constant)) 68 | 69 | to' :: AdapterP s t a b -> b -> t 70 | to' ad = unTagged . ad . Tagged 71 | ``` 72 | 73 | These definitions, though simple, are not straightforward at all. By now, we're 74 | more than happy if you feel comfortable with the diagrams. 75 | 76 | Finally, we'll redefine the original `shift` example, that we show again as a 77 | reminder: 78 | 79 | ```haskell 80 | shift :: Adapter ((a, b), c) ((a', b'), c') (a, (b, c)) (a', (b', c')) 81 | shift = Adapter f t where 82 | f ((a, b), c) = (a, (b, c)) 83 | t (a', (b', c')) = ((a', b'), c') 84 | ``` 85 | 86 | Using the new profunctor representation for adapters we get: 87 | 88 | ```haskell 89 | shift' :: AdapterP ((a, b), c) ((a', b'), c') (a, (b, c)) (a', (b', c')) 90 | shift' = dimap assoc assoc' where 91 | assoc ((x, y), z) = (x, (y, z)) 92 | assoc' (x, (y, z)) = ((x, y), z) 93 | ``` 94 | 95 | ### Profunctor Lens 96 | 97 | Next, we'll try to define lenses. Its concrete optic is a little bit more 98 | complex, containing `view :: s -> a` and `update :: (b, s) -> t`. It seems 99 | trivial to extend `p a b` in the left with `view`, to get a `p s b`. However, we 100 | can't use `update` in the right, since it requires not only a `b` but also a 101 | `s`. If we review our toolbox, we know that it's possible to have the original 102 | `s` passing through, living along with the original box using cartesian. This is 103 | how we build a lens diagram from `p a b`: 104 | 105 | ![lens](diagram/lens.svg) 106 | 107 | There's a new component which simply replicates the input, to make it 108 | interoperable with a multi-input box. Since we only require `Profunctor` and 109 | `Cartesian`, our profunctor lens is represented as follows: 110 | 111 | ```haskell 112 | type LensP s t a b = forall p . Cartesian p => p a b -> p s t 113 | ``` 114 | 115 | And this is how we encode the previous diagram: 116 | 117 | ```haskell 118 | lensC2P :: Lens s t a b -> LensP s t a b 119 | lensC2P (Lens v u) = dimap dup u . first . lmap v where 120 | dup a = (a, a) 121 | ``` 122 | 123 | On the other hand, we could recover the concrete lens from a profunctor lens by 124 | using `UpStar Constant` and `->` instances: 125 | 126 | ```haskell 127 | view' :: LensP s t a b -> s -> a 128 | view' ln = getConstant . runUpStar (ln (UpStar Constant)) 129 | 130 | update' :: LensP s t a b -> (b, s) -> t 131 | update' ln (b, s) = ln (const b) s 132 | ``` 133 | 134 | Now it's turn to redefine `π1`. It was originally defined as follows:: 135 | 136 | ```haskell 137 | π1 :: Lens (a, c) (b, c) a b 138 | π1 = Lens v u where 139 | v = fst 140 | u (b, (_, c)) = (b, c) 141 | ``` 142 | 143 | You might be surprised with the profunctor representation: 144 | 145 | ```haskell 146 | π1' :: LensP (a, c) (b, c) a b 147 | π1' = first 148 | ``` 149 | 150 | Indeed, `first` provides all we need to access the first component of a tuple! 151 | Similarly, `second` could serve us to access the corresponding second component. 152 | 153 | ### Profunctor Prism 154 | 155 | Now, it's the turn for profunctor prisms. Recall that the concrete definition 156 | contains `match :: s -> a + t` and `build :: b -> t`. Again, if we want to 157 | extend our `p a b` into a `p s t` we're gonna need some help. The resulting 158 | picture for a prism circuit is represented in the next picture: 159 | 160 | ![prism](diagram/prism.svg) 161 | 162 | There, `p a b` is extended with `build` on the right. Then, it's required to 163 | include a lower exclusive path for non-existing focus. Choosing between one path 164 | or another will be determined by the switch input, which is in turn determined 165 | by `match`. Finally, a tiny adaptation on the right is applied, to turn a `t + 166 | t` into a `t`. From this diagram, we can infer that a prism depends on 167 | `Cocartesian`: 168 | 169 | ```haskell 170 | type PrismP s t a b = forall p . Cocartesian p => p a b -> p s t 171 | ``` 172 | 173 | As usual, here it is the textual version of the diagram above: 174 | 175 | ```haskell 176 | prismC2P :: Prism s t a b -> PrismP s t a b 177 | prismC2P (Prism m b) = dimap m (either id id) . left . rmap b 178 | ``` 179 | 180 | The instances that should be fed to a profunctor prism in order to recover a 181 | concrete prism are `UpStar (Either a)` and `Tagged`: 182 | 183 | ```haskell 184 | match' :: PrismP s t a b -> s -> Either a t 185 | match' pr = runUpStar (pr (UpStar Left)) 186 | 187 | build' :: PrismP s t a b -> b -> t 188 | build' pr = unTagged . pr . Tagged 189 | ``` 190 | 191 | Remember concrete `the`? It focus on the `a` hidden behind a `Maybe a`: 192 | 193 | ```haskell 194 | the :: Prism (Maybe a) (Maybe b) a b 195 | the = Prism (maybe (Right Nothing) Left) Just 196 | ``` 197 | 198 | We can redefine it with our brand new profunctor prism: 199 | 200 | ```haskell 201 | the' :: PrismP (Maybe a) (Maybe b) a b 202 | the' = dimap (maybe (Right Nothing) Left) (either Just id) . left 203 | ``` 204 | 205 | ### Profunctor Affine 206 | 207 | Previously, we saw that `preview :: s -> a + t` and `set :: (b, s) -> t` are the 208 | primitives that conform concrete affines. This time, turning `h :: p a b` into 209 | `p s t` will require several features. This is what we need to achieve it: 210 | 211 | ![affine](diagram/affine.svg) 212 | 213 | Thereby, we apply the original generalized function only if the focus exists. In 214 | that case, we still need the original whole value to be able to apply `set`. 215 | Finally, if our focus wasn't there, we can select the lower path directly. Since 216 | we used cartesian and cocartesian features, this leads to this alias for affine: 217 | 218 | ```haskell 219 | type AffineP s t a b = forall p . (Cartesian p, Cocartesian p) => p a b -> p s t 220 | ``` 221 | 222 | Our diagram is translated into Haskell this way: 223 | 224 | ```haskell 225 | affineC2P :: Affine s t a b -> AffineP s t a b 226 | affineC2P (Affine p st) = dimap preview' merge . left . rmap st . first where 227 | preview' s = either (\a -> Left (a, s)) Right (p s) 228 | merge = either id id 229 | ``` 230 | 231 | As usual, we can go back to concrete affine as well: 232 | 233 | ```haskell 234 | preview' :: AffineP s t a b -> s -> Either a t 235 | preview' af = runUpStar (af (UpStar Left)) 236 | 237 | set' :: AffineP s t a b -> (b, s) -> t 238 | set' af (b, s) = af (const b) s 239 | ``` 240 | 241 | Finally, we're going to adapt `maybeFirst` to this new setting: 242 | 243 | ```haskell 244 | maybeFirst' :: AffineP (Maybe a, c) (Maybe b, c) a b 245 | maybeFirst' = first . dimap (maybe (Right Nothing) Left) (either Just id) . left 246 | ``` 247 | 248 | This expression is quite familiar to us, isn't it? It combines somehow the 249 | implementations of `π1'` and `the'`. In fact, this snippet compiles nicely: 250 | 251 | ```haskell 252 | maybeFirst'' :: AffineP (Maybe a, c) (Maybe b, c) a b 253 | maybeFirst'' = π1' . the' 254 | ``` 255 | 256 | We're composing different optic kinds with `.`! What has just happened?!?! We'll 257 | come back to composition later, but you know what? You have been composing 258 | optics for all this time! Indeed, `first`, `left`, `dimap f g`... are methods 259 | that turn generalized functions on a focus into generalized functions on a 260 | whole. As you can tell, we've been extensively composing them by means of `.` to 261 | conform our diagrams. 262 | 263 | ### Profunctor Traversal 264 | 265 | Recall that we defined our fake traversal in terms of `contents :: s -> [a]` and 266 | `fill :: ([b], s) -> t`. We should be able to pass every focus value through our 267 | original `h :: p a b` and collect the results. Here it's the corresponding 268 | diagram: 269 | 270 | ![traversal](diagram/traversal.svg) 271 | 272 | This definition is quite complex, huh? It even requires recursion! (Notice that 273 | the inner yellow box corresponds with the outer yellow one) Broadly, we are 274 | extracting the list of focus values and passing them through our original 275 | generalized function. However, since this list could be empty, we need to 276 | consider an alternative path, which is used as the recursion base case. We need 277 | monoidal to make both recursive box and original `h` coexist. Since traversals 278 | require contextual information when updating, cartesian is also necessary. As 279 | new elements, there is `/` which turns a `Cons` into a head-tail tuple and `:` 280 | which does exactly the inverse operation. The rest of the diagram should be 281 | straightforward. We represent profunctor traversals as follows: 282 | 283 | ```haskell 284 | type TraversalP s t a b = forall p . (Cartesian p, Cocartesian p, Monoidal p) => p a b -> p s t 285 | ``` 286 | 287 | Here's the code associated to the diagram: 288 | 289 | ```haskell 290 | traversalC2P :: Traversal s t a b -> TraversalP s t a b 291 | traversalC2P (Traversal c f) = dimap dup f . first . lmap c . ylw where 292 | ylw h = dimap (maybe (Right []) Left . uncons) merge $ left $ rmap cons $ par h (ylw h) 293 | cons = uncurry (:) 294 | dup a = (a, a) 295 | merge = either id id 296 | ``` 297 | 298 | We'll show now how to recover `contents`, since `fill` is kind of broken: 299 | 300 | ```haskell 301 | contents' :: TraversalP s t a b -> s -> [a] 302 | contents' tr = getConstant . runUpStar (tr (UpStar (\a -> Constant [a]))) 303 | ``` 304 | 305 | Finally, the unsafe concrete `firstNSecond` example: 306 | 307 | ```haskell 308 | firstNSecond :: Traversal (a, a, c) (b, b, c) a b 309 | firstNSecond = Traversal c f where 310 | c (a1, a2, _) = [a1, a2] 311 | f (bs, (_, _, x)) = (head bs, (head . tail) bs, x) 312 | ``` 313 | 314 | could be adapted to a profunctor traversal as follows: 315 | 316 | ```haskell 317 | firstNSecond' :: TraversalP (a, a, c) (b, b, c) a b 318 | firstNSecond' pab = dimap group group' (first (pab `par` pab)) where 319 | group (x, y, z) = ((x, y), z) 320 | group' ((x, y), z) = (x, y, z) 321 | ``` 322 | 323 | ## Optic Composition is Function Composition 324 | 325 | Undoubtedly, it's easier to read a concrete optic definition than a profunctor 326 | optic one. Concrete optics are just a bunch of simple functions that every 327 | programmer is comfortable with, while profunctor optics require grasping 328 | profunctors and contextualizing them in the problem of updating immutable data 329 | structures. Why is this representation so trendy? The thing is that profunctor 330 | optics take composability to the next level. 331 | 332 | Profunctor optics are essentially functions, and functions enable the most 333 | natural way of composition in functional programming. We can compose functions, 334 | and therefore profunctor optics, by using `.`. Given this situation, there's no 335 | need to implement a specific combinator for each pair of optics. In fact, `first . 336 | first` or `second . left . the` are perfectly valid examples of optic 337 | composition. Notice that we can even compose optics heterogeneously, as it's 338 | evidenced in the last expression, where a lens, a prism and an affine are 339 | composed together. But hold a second, which optic results of composing two 340 | arbitrary optics? Haskell's elegance helps a lot to answer this question. 341 | 342 | When Haskell composes two functions, it merges the constraints imposed for each 343 | of them, and set them as constraints for the resulting function. Therefore, if 344 | we compose a lens (that depends on cartesian) and a prism (that depends on 345 | cocartesian) we end up with an optic that depends on both cartesian and 346 | cocartesian. Is this output familiar to you? Of course, it's exactly the 347 | definition of `AffineP`, which is the result of combining a lens with a prism. 348 | According to this view, we can see that a traversal, which is the most 349 | restrictive optic we've seen in this article, is able to represent the rest of 350 | them, though won't be using its full potential when doing so. You can find 351 | [here](http://julien-truffaut.github.io/Monocle/optics.html) a graph that 352 | shows this hierarchy. 353 | 354 | Now, let's play with composition: 355 | 356 | ```haskell 357 | λ> let tr' = π1' . the' . firstNSecond' 358 | λ> contents' tr' (Just ("profunctor", "optics", 'a'), 0) 359 | ["profunctor","optics"] 360 | λ> tr' length (Just ("profunctor", "optics", 'a'), 0) 361 | (Just (10,6,'a'),0) 362 | ``` 363 | 364 | First of all we compose different optics to generate a traversal. It focuses on 365 | the `a`s which are nested in a whole `(Maybe (a, a, c), d)`. Then, we can use 366 | `contents'` to collect them or even feed another profunctor instance. For 367 | example, if we use `(->)` we should get a `modify`. Therefore, passing `length` 368 | as argument applies the very same function to each focus. This elegance is 369 | simply awesome. 370 | 371 | On the other hand, we can use our computation diagrams to show a different 372 | perspective on profunctor optics composition. This is what happens when we 373 | compose a lens with an adpater: 374 | 375 | ![composition](diagram/composition.svg) 376 | 377 | Our lens requires a `p a b` to produce a `p s t`. When we embed (or compose) the 378 | adapter, we're being more specific about that gap. We still want to produce a `p 379 | s t`, but we don't need a full `p a b` to do so. We can build it from a smaller 380 | `j :: p c d` computation instead. The resulting diagram uses only `Profunctor` 381 | and `Cartesian` utilities to be built. Those are exactly the constraints 382 | required by lens, so we can determine that composing a lens with an adapter 383 | results in another lens, as expected. 384 | 385 | ## Discussion 386 | 387 | In this series, we've introduced optics, profunctors and finally profunctor 388 | optics. Particularly, we've been toying around with adapters, lenses, prisms, 389 | affines and traversals, but you should take into account that there are [many 390 | others](http://oleg.fi/gists/images/optics-hierarchy.svg) out there. The 391 | contents have been heavily inspired by [this 392 | paper](http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/poptics.pdf) by 393 | Pickering et al. As a consequence, we've tried to remain in line with the 394 | conventions adopted in it. In fact, our major contribution relies on providing 395 | several diagrams to make profunctors and profunctor optics more approachable. 396 | They are mainly based on [Hughes' arrows](https://www.haskell.org/arrows/) ones. 397 | 398 | In general, we've priorized diagram simplicity over code conciseness (since we 399 | pursued to emphasize the concrete operators above all). This is evidenced in the 400 | Haskell encodings of the profunctor optic diagrams, where the original paper 401 | provides nicer implementations. Following with our particular implementation, 402 | you might have noticed that some functions that recover concrete operations from 403 | profunctor optics are exactly the same, for instance `update'` and `set'`. In 404 | fact, profunctor optic libraries such as *mezzolens* don't supply particular 405 | interfaces for every optic. Instead, they provide [operations for particular 406 | profunctors](https://hackage.haskell.org/package/mezzolens-0.0.0/docs/Mezzolens.html) 407 | that could be used by arbitrary optics (as long as their constraints allow it). 408 | 409 | Instancing profunctor optics from scratch is not straightforward at all. In 410 | addition, different instances turn out to follow similar patterns. Therefore we 411 | suggest to create the concrete optic manually and then translate it to its 412 | profunctor version. In our experience, profunctor optics generated this way 413 | might not be the most direct ones, but they are good enough for most of cases. 414 | 415 | We've only covered two optic representations, that differ greatly from each 416 | other. However, you should know that there are other intermediate 417 | representations that we've been avoiding on purpose. The most widespread is 418 | [*Van Laarhoven*](https://www.twanvl.nl/blog/haskell/cps-functional-references), 419 | which is deployed in [Kmett's awesome optic 420 | library](https://github.com/ekmett/lens/). For instance, Van Laarhoven lenses 421 | look as follows: 422 | 423 | ```haskell 424 | type LensVL s t a b = Functor f => (a -> f b) -> (s -> f t) 425 | ``` 426 | 427 | You can immediately realize that there are many similarities to the profunctor 428 | formulation. Try to implement an isomorphism between `LensVL` and `LensP` as an 429 | exercise. If you're interested on the foundations of this representation, 430 | there's an [epic 431 | post](https://bartoszmilewski.com/2015/07/13/from-lenses-to-yoneda-embedding/) 432 | on the categorical view of Van Laarhoven lenses. There's an [analogous 433 | post](https://bartoszmilewski.com/2017/07/07/profunctor-optics-the-categorical-view/) 434 | for the categorical view of profunctor optics, which I haven't analysed in 435 | detail yet. 436 | 437 | Finally, we must say that profunctor optics are trendy. Particularly, they're 438 | becoming [quite relevant](https://www.youtube.com/watch?v=OJtGECfksds) in 439 | PureScript. We don't know if they will become mainstream in other functional 440 | languages, but at least I hope you don't fear them anymore. 441 | -------------------------------------------------------------------------------- /Profunctors.md: -------------------------------------------------------------------------------- 1 | # Don't Fear the Profunctor Optics (Part 2/3) 2 | 3 | PREVIOUSLY: [Optics, Concretely](Optics.md) 4 | 5 | Today it's the turn of profunctors. We'll start with a very brief introduction 6 | to covariant and contravariant functors. Then, we'll see the most relevant 7 | members of the profunctor family. Particularly, we'll show their representation 8 | as typeclasses (along with their associated laws and several instances) and a 9 | visual representation where profunctors are seen as boxes that take inputs and 10 | produce outputs. Here we go! 11 | 12 | ## Profunctors as Generalized Functions 13 | 14 | (Covariant) Functors arise everywhere! When we put our programmer's glasses on, 15 | we can view them [as 16 | containers](https://bartoszmilewski.com/2014/01/14/functors-are-containers/) 17 | that can be mapped over with `fmap`: 18 | 19 | ```haskell 20 | class Functor f where 21 | fmap :: (a -> b) -> f a -> f b 22 | ``` 23 | 24 | Not surprisingly, functions are functors, when they're parameterized on their 25 | result type: 26 | 27 | ```haskell 28 | instance Functor ((->) r) where 29 | fmap f g = f . g 30 | ``` 31 | 32 | Contravariant functors are pretty much like functors, but they require a 33 | reversed arrow to map the container in the expected direction: 34 | 35 | ```haskell 36 | class Contravariant f where 37 | cmap :: (b -> a) -> f a -> f b 38 | ``` 39 | 40 | Functions are also contravariant, this time on their first type parameter: 41 | 42 | ```haskell 43 | newtype CReader r a = CReader (a -> r) 44 | 45 | instance Contravariant (CReader r) where 46 | cmap f (CReader g) = CReader (g . f) 47 | ``` 48 | 49 | Now, we should be ready to approach profunctors. For each member of the 50 | profunctor family, we'll show its typeclass and associated laws, along with a `(->)` 51 | instance. Then, we'll provide an informal explanation which is supported by our 52 | profunctor diagrams. 53 | 54 | ### Profunctor 55 | 56 | Profunctors are sort of functors which are contravariant on their first argument 57 | and covariant on their second one. Thereby, its abstract method `dimap` takes 58 | two functions as input: 59 | 60 | ```haskell 61 | class Profunctor p where 62 | dimap :: (a' -> a) -> (b -> b') -> p a b -> p a' b' 63 | 64 | lmap :: (a' -> a) -> p a b -> p a' b 65 | lmap f = dimap f id 66 | rmap :: (b -> b') -> p a b -> p a b' 67 | rmap f = dimap id f 68 | ``` 69 | 70 | `lmap` and `rmap` are utilities to map either the covariant or the contravariant 71 | type parameter. Therefore, they correspond with contravariant's `cmap` and 72 | functor's `fmap`, respectively. As every interesting typeclass, profunctors do 73 | obey some laws: 74 | 75 | ```haskell 76 | dimap id id = id 77 | dimap (f' . f) (g . g') = dimap f g · dimap f' g' 78 | ``` 79 | 80 | As expected, functions do fit perfectly as instances of this typeclass: 81 | 82 | ```haskell 83 | instance Profunctor (->) where 84 | dimap f g h = g . h . f 85 | ``` 86 | 87 | #### Explanation 88 | 89 | Profunctors are best understood as generalizations of functions. As a 90 | consequence, we can see them as boxes that take an input and provide an output. 91 | Thereby, `dimap` just adapts inputs and outputs to conform new boxes. Given this 92 | intuition, I noticed that drawing diagrams could be helpful to understand the 93 | profunctor family. Soon, I realized that these new diagrams were quite similar 94 | to the ones provided by [Hughes for arrows](https://www.haskell.org/arrows/). In 95 | fact, there are [strong 96 | connections](https://stackoverflow.com/questions/38169453/whats-the-relationship-between-profunctors-and-arrows) 97 | among them. From now on, we'll be using them to provide a visual perspective of 98 | profunctors: 99 | 100 | ![profunctor](diagram/profunctor.svg) 101 | 102 | Here, `h :: p a b` is our original profunctor box. For being a profunctor we 103 | know that it can be extended with `dimap`, using plain functions `f :: a' -> a` 104 | and `g :: b -> b'`, represented as circles. Once we apply `dimap` a surrounding 105 | profunctor box is generated, which is able to take `a'`s as input and produce 106 | `b'`s as output. 107 | 108 | These diagrams also help to better understand profunctor laws. This picture 109 | represent the first of them `dimap id id = id`: 110 | 111 | ![profunctor-law1](diagram/profunctor-law1.svg) 112 | 113 | Basically, it says that we can replace an `id` function with a raw cable. Then, 114 | the surrounding profunctor box would become redundant. On the other hand, the 115 | second law `dimap (f' . f) (g . g') = dimap f g · dimap f' g'` is represented as 116 | follows: 117 | 118 | ![profunctor-law2](diagram/profunctor-law2.svg) 119 | 120 | It tells us that we can split a `dimap` which takes composed functions into a 121 | composition of `dimap`s where each takes the corresponding uncomposed functions. 122 | 123 | ### Cartesian 124 | 125 | Following down the profunctor family, we find `Cartesian`: 126 | 127 | ```haskell 128 | class Profunctor p => Cartesian p where 129 | first :: p a b -> p (a, c) (b, c) 130 | second :: p a b -> p (c, a) (c, b) 131 | ``` 132 | 133 | These are the laws associated to this typeclass (skipping the counterparts for 134 | `second`): 135 | 136 | ```haskell 137 | dimap runit runit' h = first h 138 | dimap assoc assoc' (first (first h)) = first h 139 | -- where 140 | runit :: (a, ()) -> a 141 | runit' :: a -> (a, ()) 142 | assoc :: (a, (b, c)) -> ((a, b), c) 143 | assoc' :: ((a, b), c) -> (a, (b, c)) 144 | ``` 145 | 146 | The function instance for cartesian is straightforward: 147 | 148 | ```haskell 149 | instance Cartesian (->) where 150 | first f (a, c) = (f a, c) 151 | second f (c, a) = ( c, f a) 152 | ``` 153 | 154 | #### Explanation 155 | 156 | Again, this typeclass contains methods that turn certain box `p a b` into 157 | new ones. Particularly, these methods make our original box coexist with 158 | an additional input `c`, either in the first or second position. Using our 159 | diagram representation, we get: 160 | 161 | ![cartesian](diagram/cartesian.svg) 162 | 163 | As usual, we start from a simple `h :: p a b`. Once `first` is applied we can 164 | see how `c` input passes through, while `a` is turned into a `b`, using the 165 | original box `h` for the task. The most characteristic feature of the resulting 166 | box is that it takes two inputs and produce two outputs. 167 | 168 | When we draw the first cartesian law `dimap runit runit' h = first h`, we get 169 | the next diagram: 170 | 171 | ![cartesian-law1](diagram/cartesian-law1.svg) 172 | 173 | This law is claiming that `first` should pass first input through `h` while 174 | second input should be preserved in the output as is. That intuition turns out 175 | to be implicit in our diagram representation. The second law `dimap assoc assoc' 176 | (first (first h)) = first h` is shown graphically in this image: 177 | 178 | ![cartesian-law2](diagram/cartesian-law2.svg) 179 | 180 | This representation seems more complex, but in essence, it states that nesting 181 | `first`s doesn't break previous law. This is implicit in the diagram as well, 182 | where `b` and `c` simply pass-through, regardless of the nested boxes in the 183 | upper case. 184 | 185 | ### Cocartesian 186 | 187 | Next, there is `Cocartesian`, whose primitives are shown in the following 188 | typeclass: 189 | 190 | ```haskell 191 | class Profunctor p => Cocartesian p where 192 | left :: p a b -> p (Either a c) (Either b c) 193 | right :: p a b -> p (Either c a) (Either c b) 194 | ``` 195 | 196 | Its laws are these ones (ignoring `right` counterparts): 197 | 198 | ```haskell 199 | dimap rzero rzero' h = left h 200 | dimap coassoc' coassoc (left (left h)) = left h 201 | --where 202 | rzero :: Either a Void -> a 203 | rzero' :: a -> Either a Void 204 | coassoc :: Either a (Either b c) -> Either (Either a b) c 205 | coassoc' :: Either (Either a b) c -> Either a (Either b c) 206 | ``` 207 | 208 | And here it's the function instance: 209 | 210 | ```haskell 211 | instance Cocartesian (->) where 212 | left f = either (Left . f) Right 213 | right f = either Left (Right . f) 214 | ``` 215 | 216 | #### Explanation 217 | 218 | Indeed, this typeclass is very similar to `Cartesian`, but the resulting box 219 | deals with sum types (`Either`) instead of product types. What does it mean from 220 | our diagram perspective? It means that inputs are exclusive and only one of them 221 | will be active in a particular time. The input itself determines which path 222 | should be active. The corresponding diagram is shown in the next picture: 223 | 224 | ![cocartesian](diagram/cocartesian.svg) 225 | 226 | As one could see, `left` turns the original `h :: p a b` into a box typed `p 227 | (Either a c) (Either b c)`. Internally, when an `a` matches, [the 228 | switch](http://lightwiring.co.uk/wp-content/uploads/2013/03/2-way-switching-2-wire-control-schematic-diagram.jpg) 229 | takes the upper path, where `h` delivers a `b` as output. Otherwise, when `c` 230 | matches, the lower path will be activated, and the value passes through as is. 231 | Notice that switches in both extremes must be coordinated to make things work, 232 | either activating the upper or the lower path. On the other hand, `right` is 233 | like `left` where paths are swapped. We left diagram representation of laws as 234 | an exercise for the reader. 235 | 236 | ### Monoidal 237 | 238 | Finally, the last profunctor that will be covered is `Monoidal`. As usual, 239 | here's the associated typeclass: 240 | 241 | ```haskell 242 | class Profunctor p => Monoidal p where 243 | par :: p a b -> p c d -> p (a, c) (b, d) 244 | empty :: p () () 245 | ``` 246 | 247 | Monoidal laws are encoded as follows: 248 | 249 | ```haskell 250 | dimap assoc assoc' (par (par h j) k) = par h (par j k) 251 | dimap runit runit' h = par h empty 252 | dimap lunit lunit' h = par empty h 253 | -- where 254 | lunit :: ((), a) -> a 255 | lunit' :: a -> ((), a) 256 | ``` 257 | 258 | Instantiating this typeclass for function is trivial: 259 | 260 | ```haskell 261 | instance Monoidal (->) where 262 | par f g (a, c) = (f a, g c) 263 | empty = id 264 | ``` 265 | 266 | #### Explanation 267 | 268 | Now, we'll focus on `par`. It receives a pair of boxes, `p a b` and `p c d`, and 269 | it builds a new box typed `p (a, c) (b, d)`. Given this signature, it's easy to 270 | figure out what's going on in the shadows. It's shown in the next diagram: 271 | 272 | ![monoidal](diagram/monoidal.svg) 273 | 274 | The resulting box make both arguments (`h` and `j`) coexist, by connecting them 275 | in parallel (therefore the name `par`). 276 | 277 | ### Beyond Functions 278 | 279 | So far, we've been instantiating profunctor typeclasses with plain functions. 280 | However, as we said before, profunctors do *generalize* functions. What other 281 | kind of things (beyond functions) can be represented with them? 282 | 283 | Well, there is `UpStar`, which is pretty similar to a function, though returning 284 | a [fancy 285 | type](http://blog.sigfpe.com/2006/06/monads-kleisli-arrows-comonads-and.html): 286 | 287 | ```haskell 288 | newtype UpStar f a b = UpStar { runUpStar :: a -> f b } 289 | ``` 290 | 291 | We can provide instances for many profunctor typeclasses: 292 | 293 | ```haskell 294 | instance Functor f => Profunctor (UpStar f) where 295 | dimap f g (UpStar h) = UpStar (fmap g . h . f) 296 | 297 | instance Functor f => Cartesian (UpStar f) where 298 | first (UpStar f) = UpStar (\(a, c) -> fmap (,c) (f a)) 299 | second (UpStar f) = UpStar (\(c, a) -> fmap (c,) (f a)) 300 | 301 | -- XXX: `Pointed` should be good enough, but it lives in external lib 302 | instance Applicative f => Cocartesian (UpStar f) where 303 | left (UpStar f) = UpStar (either (fmap Left . f) (fmap Right . pure)) 304 | right (UpStar f) = UpStar (either (fmap Left . pure) (fmap Right . f)) 305 | 306 | instance Applicative f => Monoidal (UpStar f) where 307 | par (UpStar f) (UpStar g) = UpStar (\(a, b) -> (,) <$> f a <*> g b) 308 | empty = UpStar pure 309 | ``` 310 | 311 | Notice that we need some evidences on `f` to be able to implement such 312 | instances, particularly `Functor` or `Applicative`. We encourage the reader to 313 | instantiate these instances also for `DownStar` (where the fanciness is in the 314 | parameter type) as an exercise: 315 | 316 | ```haskell 317 | newtype DownStar f a b = DownStar { runDownStar :: f a -> b } 318 | ``` 319 | 320 | (You may not able to instantiate all three classes for `DownStar`. If so, try to 321 | reason about why certain class cannot be instantiated.) 322 | 323 | Take [`Tagged`](http://oleg.fi/gists/posts/2017-04-18-glassery.html) as another 324 | example: 325 | 326 | ```haskell 327 | newtype Tagged a b = Tagged { unTagged :: b } 328 | ``` 329 | 330 | This is just a wrapper for `b`s, having `a` as a phantom input type. In fact, it 331 | represents somehow a constant function. You can provide instances for many 332 | profunctor typeclasses with it: 333 | 334 | ```haskell 335 | instance Profunctor Tagged where 336 | dimap f g (Tagged b) = Tagged (g b) 337 | 338 | instance Cocartesian Tagged where 339 | left (Tagged b) = Tagged (Left b) 340 | right (Tagged b) = Tagged (Right b) 341 | 342 | instance Monoidal Tagged where 343 | par (Tagged b) (Tagged d) = Tagged (b, d) 344 | empty = Tagged () 345 | ``` 346 | 347 | Try to reason about the impossibility of determining an instance for `Cartesian` 348 | as an exercise. 349 | 350 | We've chosen `DownStar` and `Tagged` because we'll use them in the next part of 351 | this series. However, you should know that there are other awesome instances for 352 | profunctors [out 353 | there](https://ocharles.org.uk/blog/guest-posts/2013-12-22-24-days-of-hackage-profunctors.html). 354 | As you see, profunctors also arise everywhere! 355 | 356 | NEXT: [Profunctor Optics](ProfunctorOptics.md) 357 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Don't Fear the Profunctor Optics! 2 | 3 | This is a series of posts on Profunctor Optics. They're heavily inspired on 4 | [this awesome 5 | article](http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/poptics.pdf) 6 | by Pickering *et al*. Our contribution relies on providing box diagrams to make 7 | profunctor and profunctor optics easier to approach. 8 | 9 | * Part I: [Optics, Concretely](Optics.md) 10 | * Part II: [Profunctors](Profunctors.md) 11 | * Part III: [Profunctor Optics](ProfunctorOptics.md) 12 | 13 | Any feedback is welcome! 14 | -------------------------------------------------------------------------------- /diagram/cartesian-law1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /diagram/cartesian.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /diagram/monoidal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /diagram/profunctor-law1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /diagram/profunctor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | --------------------------------------------------------------------------------