├── .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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------