Search code examples
haskelltraversalhaskell-lens

Fusing traversals


The Traversable Paper gives an example on page 18-19 of fusing monoidal and monadic traversals which sounds really interesting but I'm confused by their LaTex.

cciBody :: Char -> Count a
wciBody :: Char -> (|M| (State Bool) DotInASquare Count) a

With the amazing result that:

(traverse cciBody) xInACircle (traverse wciBody)

Is the same as:

traverse (cciBody xInACircle wciBody)

I think the type of that result is:

Count XInASquare (|M| (State Bool) DotInASquare Count) [a]

But not 100% sure. Could someone who speaks Emoji tell me how it should look in Haskell?

Update

I think xInACircle might be an infix sequenceA. The types kind of match up. Or maybe it's just (,) which is an instance of Traversable. It's definitely not <*> even though the result looks a bit like t (x <*> y) = t x <*> t y but they don't use a Wingding for <*> in the paper.

Update 2

The type of xInACircle is (Functor m, Functor n) ⇒ (a → m b) → (a → n b) → (a → (m XInASquare n) b). Remind you of anything? Not me.


Solution

  • You're getting confused about operators used in type signatures. Basically, the same rules from Haskell syntax hold for these: first priority is parentheses, second priority is "application", last priority is "operators". So just as you might write:

    f 3 + g 4
    

    [which in mathematical terms we'd write as f(3) + g(4)], in Haskell there is a flag to enable infix type operators (-XTypeOperators) beginning with colons, so that you can write an expression like f :: a :* b -> b :* [a] in place of f :: Star a b -> Star b [a]. It's just an alternative syntax for a parametric type constructor with at least two parameters. (I guess since -> is already an infix type constructor this is hardly news.) We can also write these as Comp a b and Prod a b.

    The up-arrows and down-arrows are functions defined within the paper as part of a type class, but I don't like all of the pragmas that we need in order for Haskell to actually accept those functions, so I'm going to explicate them in this code. Here are all of the relevant definitions as a valid Haskell file using Comp and Prod instead of their operators:

    import Control.Applicative (Applicative, (<$>), (<*>), pure, WrappedMonad(..), Const(..))
    import Data.Traversable (traverse)
    import Control.Monad.State.Lazy (State, state, runState)
    import Data.Char (isSpace)
    import Data.Monoid (Monoid(..))
    
    instance Monoid Integer where
        mempty = 0
        mappend = (+)
    
    -- chained functors
    newtype Comp m n a = Comp {runComp :: m (n a)}
    instance (Functor m, Functor n) => Functor (Comp m n) where
        fmap f = Comp . fmap (fmap f) . runComp
    instance (Applicative m, Applicative n) => Applicative (Comp m n) where
        pure = Comp . pure . pure
        Comp mnf <*> Comp mnx = Comp ((<*>) <$> mnf <*> mnx)
    
    -- outer product of functors
    data Prod m n a = Prod {pfst :: m a, psnd :: n a}
    instance (Functor m, Functor n) => Functor (Prod m n) where
        fmap f (Prod ma na) = Prod (fmap f ma) (fmap f na)
    instance (Applicative m, Applicative n) => Applicative (Prod m n) where
        pure x = Prod (pure x) (pure x)
        Prod mf nf <*> Prod mx nx = Prod (mf <*> mx) (nf <*> nx)
    
    -- page 19,20
    
    type Count = Const Integer
    count :: a -> Count b
    count _ = Const 1
    
    cciBody :: Char -> Count a
    cciBody = count
    
    cci :: String -> Count [a]
    cci = traverse cciBody
    
    test :: Bool -> Integer
    test b = if b then 1 else 0
    
    lciBody :: Char -> Count a
    lciBody c = Const (test (c == '\n'))
    
    lci :: String -> Count [a]
    lci = traverse lciBody
    
    wciBody :: Char -> Comp (WrappedMonad (State Bool)) Count a
    wciBody c = Comp (fmap Const (WrapMonad $ state $ updateState c)) where
        updateState :: Char -> Bool -> (Integer, Bool)
        updateState c w = let s = not (isSpace c) in (test (not w && s), s)
    
    wci :: String -> Comp (WrappedMonad (State Bool)) Count [a]
    wci = traverse wciBody
    
    runWci :: String -> Integer
    runWci s = fst $ runState (fmap getConst $ unwrapMonad $ runComp $ wci s) False
    

    If you're not clear where any functions come from I've restricted the imports up-top, so look up there (or use Hoogle) to find their definitions.

    The operator you're calling xInACircle is an operator which takes an a -> m b and an a -> n b and produces a function a -> Prod m n b. This product type contains both results of both applicatives m and n. Once you understand the x-in-a-square type you'll understand the x-in-a-circle operator. Basically unlike the (Monoid m) => Applicative ((,) m) instance, which only fmaps and <*>s over its second argument, the Prod of two abstract-functors is a pair-functor which fmaps and <*>s over both its arguments. It is the pair (m a, n a) where both m and n are applicative.