What are simple definitions for Control.Lens.Traversal partsOf, Of and singular holes?

I am trying to learn more about the lens library. I already understand the lenses in the lens-family package and their output, and it also captures two versions of parameters like Store, Pretext and Bazaar, but it's hard for me to understand Control.Lens.Traversal partsOf , holesOf and singular , which are determined by complex types and many auxiliary functions. Could these features also be an easier way to learn?

+7
haskell lens
source share
1 answer

This is a rather big and thorny question. I argue that I myself do not quite understand how holesOf and partsOf , and I did not understand how singular worked up to two minutes ago, but I wanted to write down an answer that might help you.

I want to solve a more general problem: how to read the lens source code. Because if you remember a few simplifying assumptions, you can often simplify crazy definitions, like

 singular :: (Conjoined p, Functor f) => Traversing pfstaa -> Over pfstaa singular l = conjoined (\afb s -> let b = l sell s in case ins b of (w:ws) -> unsafeOuts b . (:ws) <$> afb w [] -> unsafeOuts b . return <$> afb (error "singular: empty traversal")) (\pafb s -> let b = l sell s in case pins b of (w:ws) -> unsafeOuts b . (:Prelude.map extract ws) <$> cosieve pafb w [] -> unsafeOuts b . return <$> cosieve pafb (error "singular: empty traversal")) unsafeOuts :: (Bizarre pw, Corepresentable p) => wabt -> [b] -> t unsafeOuts = evalState `rmap` bazaar (cotabulate (\_ -> state (unconsWithDefault fakeVal))) where fakeVal = error "unsafePartsOf': not enough elements were supplied" ins :: Bizarre (->) w => wabt -> [a] ins = toListOf (getting bazaar) unconsWithDefault :: a -> [a] -> (a,[a]) unconsWithDefault d [] = (d,[]) unconsWithDefault _ (x:xs) = (x,xs) 

But I'm rushing towards myself

These are the rules that I try to apply in my future when I read the lens source code:

Dumber optics

Optics usually follows the stab form throughout the library, which allows you to change the type of “target” (at best, an overloaded word). But many optics can only be implemented with s and a , and often it makes no sense to keep track of t and b when you are just trying to read the definition.

For example, when I tried to reverse engineer singular , I used these types in my file from scratch:

 {-# LANGUAGE RankNTypes #-} {-# LANGUAGE NoImplicitPrelude #-} import BasePrelude hiding (fold) type Lens big small = forall f. (Functor f) => (small -> f small) -> (big -> f big) type Traversal big small = forall ap. (Applicative ap) => (small -> ap small) -> (big -> ap big) makeLens :: (big -> small) -> (big -> small -> big) -> Lens big small makeLens getter setter = \liftSmall big -> setter big <$> liftSmall (getter big) 

And the combinators look like this:

 set :: ((small -> Identity small) -> big -> Identity big) -> small -> big -> big set setter new big = runIdentity (setter (\_ -> Identity new) big) view :: ((small -> Const small small) -> big -> Const small big) -> big -> small view getter big = getConst (getter Const big) 

Get out of here, indexes and prisms

Prisms and indexed optics are extremely useful as a lens consumer, but they are responsible for some of the most noticeable pieces of code. To unify prisms and indexed optics, lens developers use finances (e.g. Choice and Conjoined ) and auxiliary helper functions ( dimap , rmap ).

When reading the lens code, it’s very useful for me to always assume p ~ (->) (function type) whenever I see the profunctor variable. This allows me to use the Representable , Conjoined , Bizarre and Over tags from the signatures in the code snippet above.

Many type holes

With this and with GHC type holes, we can start trying to build our own singular on top of our simpler, more severe types.

 singular :: Traversal big small -> Lens big small singular = _ 

The general strategy, as briefly mentioned in this comonad.com blog post , must pass the big value to get a list of small [small] ) with Const , and then return them to where we got them from using State .

Moving to get a list can be done with our reimplementation of toListOf :

 toListOf :: Traversal big small -> big -> [small] toListOf traversal = foldrOf traversal (:) [] -- | foldMapOf with mappend/mzero inlined foldrOf :: Traversal big small -> (small -> r -> r) -> r -> big -> r foldrOf traversal fold zero = \big -> appEndo (foldMapOf traversal (Endo . fold) big) zero -- | Traverses a value of type big, accumulating the result in monoid mon foldMapOf :: Monoid mon => Traversal big small -> (small -> mon) -> big -> mon foldMapOf traversal fold = getConst . traversal (Const . fold) 

Nested monoid doll here: listings from Endo from Const s.

Now we have:

 singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do case toListOf traversal big of (x:xs) -> _ [] -> _ 

Returning values ​​back a little bend the brain. There is such a crazy function that we avoided talking about:

 unsafeOuts :: (Bizarre pw, Corepresentable p) => wabt -> [b] -> t unsafeOuts = evalState `rmap` bazaar (cotabulate (\_ -> state (unconsWithDefault fakeVal))) where fakeVal = error "unsafePartsOf': not enough elements were supplied" 

What in our simplified world is becoming

 newtype Bazaar' small small' big = Bazaar { unBazaar :: forall ap. Applicative ap => (small -> ap small') -> ap big } deriving Functor instance Applicative (Bazaar' small small') where pure big = Bazaar (\_ -> pure big) Bazaar lhs <*> Bazaar rhs = Bazaar (\liftSmall -> lhs liftSmall <*> rhs liftSmall) type Bazaar small big = Bazaar' small small big gobble :: StateT Identity [a] a gobble = state (unconsWithDefault (error "empty!")) unsafeOuts :: Bazaar small big -> [small] -> big unsafeOuts (Bazaar bazaar) smalls = evalState (bazaar (\_ -> gobble)) smalls 

Here we have rmap = (.) And cotabulate f = f . Identity cotabulate f = f . Identity , and we were able to do this because we assumed p ~ (->) .

A halfhearted attempt to surprise bazaars

The bazaars are strange, and it seems that little has been written about them. The lens documentation mentions that it looks like a workaround that has already been applied to the structure. Indeed, the bazaar is what you get if you take the Traversal type and apply it to the big value that you already have.

It's also a bit of a laid-back application , but I don't know if that helps.

In a recent blog comment on a seemingly unrelated FunList type , Zemyla develops an equivalence between

 data FunList abt = Done t | More a (FunList ab (b -> t)) instance Functor (FunList ab) where ... instance Applicative (FunList ab) where ... instance Profunctor (FunList a) where ... -- example values: -- * Done (x :: t) -- * More (a1 :: a) (Done (x :: a -> t)) -- * More (a1 :: a) (More (a2 :: a) (Done (x :: a -> a -> t)) 

and bazaar lens . I believe that this view will be a little more useful in an intuitive understanding of what is happening.

Dat state monad

The pearl here is a gobble that pushes the head of the list out of state every time it starts. Our bazaar is able to update the value of gobble :: StateT Identity [small] small to bazaar (\_ -> gobble) :: StateT Identity [small] big . It is very similar to a workaround that we can take to act effectively on parts of a small value and update it to an action that acts on all value. All this happens very quickly and, apparently, not enough code; it somehow makes my head spin.

(Something that might be useful is to play with bazaars in GHCi using this helper function:

 bazaarOf :: Traversal big small -> big -> Bazaar small big bazaarOf traversal = traversal (\small -> Bazaar (\liftSmall -> liftSmall small)) -- See below for `ix`. λ> unBazaar (bazaarOf (ix 3) [1,2,3,4]) Right Right [1,2,3,4] λ> unBazaar (bazaarOf (ix 3) [1,2,3,4]) (\_ -> Right 10) Right [1,2,3,100] λ> unBazaar (bazaarOf (ix 1) [1,2,3,4]) Left Left 2 

In simple cases, this roughly corresponds to the "delayed" version of traverse .)

Anyway

unsafeOuts gives us a way to get the second big value, given the list of small values ​​and the bazaar built from the first big value. Now we need to build a bazaar from the initial round we went through:

 singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> _ [] -> _ 

Here we do two things:

  • First we take Bazaar small small . Since we plan to go through big , we can take each x :: small value we get and build a Bazaar (\f -> fx) :: Bazaar small small . It's enough!

  • Then the bypass type smoothly updates our Bazaar small small to bazaar :: Bazaar small big .

The original lens code does this with b = traversal sell big , using sell from Sellable (->) (Bazaar (->)) instace. If you enter this definition, you should get the same result.

In x:xs case x is the value we want to act on. This is the first value that the traversal we have made is aimed at, which now becomes the first value aimed at the lens that we are returning. We call liftSmall x to obtain a f small for some functor f ; then add xs inside the functor to get a f [small] ; then we will call unsafeOuts bazaar inside the functor to return f [small] back to f big :

 singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> fmap (\y -> unsafeOuts bazaar (y:xs)) <$> liftSmall x [] -> _ 

In the case of an empty list, we act the same, except that we use the lower value in:

 singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> fmap (\y -> unsafeOuts bazaar (y:xs)) <$> liftSmall x [] -> fmap (\y -> unsafeOuts bazaar [y]) <$> liftSmall (error "singularity") 

We define some basic optics so that we can play with our definition:

 -- | Constructs a Traversal that targets zero or one makePrism :: (small -> big) -> (big -> Either big small) -> Traversal big small makePrism constructor getter = \liftSmall big -> case (fmap liftSmall . getter) big of Left big' -> pure big' Right fsmall -> fmap constructor fsmall _Cons :: Traversal [a] (a, [a]) _Cons = makePrism (uncurry (:)) (\case (x:xs) -> Right (x, xs); [] -> Left []) _1 :: Lens (a, b) a _1 = makeLens fst (\(_, b) a' -> (a', b)) _head :: Traversal [a] a _head = _Cons . _1 ix :: Int -> Traversal [a] a ix k liftSmall big = if k < 0 then pure big else go big k where go [] _ = pure [] go (x:xs) 0 = (:xs) <$> liftSmall x go (x:xs) i = (x:) <$> go xs (i - 1) 

They are all stolen from the lens library.

As expected, this helps us avoid the annoying Monoid class:

 λ> :t view _head view _head :: Monoid a => [a] -> a λ> :t view (singular _head) view (singular _head) :: [small] -> small λ> view _head [1,2,3,4] [snip] • Ambiguous type variable 'a0' arising from a use of 'print' prevents the constraint '(Show a0)' from being solved. [snip] λ> view (singular _head) [1,2,3,4] 1 

And it does nothing, as expected, with the setters (since the bypasses are already configured):

 λ> set (ix 100) 50 [1,2,3] [1,2,3] λ> set (singular (ix 100)) 50 [1,2,3] [1,2,3] λ> set _head 50 [1,2,3,4] [50,2,3,4] λ> set (singular _head) 50 [1,2,3,4] [50,2,3,4] 

partsOf and holesOf

 -- | A type-restricted version of 'partsOf' that can only be used with a 'Traversal'. partsOf' :: ATraversal staa -> Lens st [a] [a] partsOf' lfs = outs b <$> f (ins b) where b = l sell s 

The pure hypothesis follows: as far as I can tell, partsOf extremely similar to singular in that it first builds bazaar b , calls f (ins b) on the bazaar, and then "returns values ​​back to where it found it."

 holesOf :: forall pst a. Conjoined p => Over p (Bazaar paa) staa -> s -> [Pretext paat] holesOf ls = unTagged ( conjoined (Tagged $ let f [] _ = [] f (x:xs) g = Pretext (\xfy -> g . (:xs) <$> xfy x) : f xs (g . (x:)) in f (ins b) (unsafeOuts b)) (Tagged $ let f [] _ = [] f (wx:xs) g = Pretext (\wxfy -> g . (:Prelude.map extract xs) <$> cosieve wxfy wx) : f xs (g . (extract wx:)) in f (pins b) (unsafeOuts b)) :: Tagged (pab) [Pretext paat] ) where b = l sell s 

holesOf also makes a bazaar ( l sell s the third time!)) and again suffers from conjunctivitis: assuming p ~ (->) , you can remove the second branch of Conjoined . But then you are left with a bunch of Pretext and comonads, and I'm not quite sure how it all hangs together. This requires further study!

Here is the gist of all the code that I had in my screenshot file when I click Submit on this wall of text.

+4
source share

All Articles