Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A list whose "Nil" carries a value?

Does some standard Haskell library define a data type like this

data ListWithEnd e a = Cons a (ListWithEnd e a)
                     | End e

That is a list whose terminating element carries a value of a designated type?

So ListWithEnd () is isomorphic to [] and ListWithEnd Void is isomorphic to infinite streams. Or, viewed differently, ListWithEnd e a is very close to ConduitM () a Identity e..

like image 493
Petr Avatar asked Mar 30 '15 17:03

Petr


1 Answers

We can define ListWithEnd as follows:

import Control.Monad.Free

type LWE a e = Free ((,) a) e

We generally have an expectation that abstract or generic representations should reward us with an overall reduction of boilerplate. Let's see what this representation provides us.

In any case, we shall define a pattern synonym for the cons case:

{-# LANGUAGE PatternSynonyms #-}

pattern x :> xs = Free (x, xs)
infixr 5 :>

We can map, fold and traverse over the end element:

fmap (+1) (0 :> Pure 0) == (0 :> Pure 1)
traverse print (0 :> Pure 1) -- prints 1

The Applicative instance gives us very neat concatenation:

xs = 1 :> 2 :> Pure 10
ys = 3 :> 4 :> Pure 20

xs *> ys          == 1 :> 2 :> 3 :> 4 :> Pure 20 -- use right end
xs <* ys          == 1 :> 2 :> 3 :> 4 :> Pure 10 -- use left end
(+) <$> xs <*> ys == 1 :> 2 :> 3 :> 4 :> Pure 30 -- combine ends

We can map over the list elements, if a bit tortuously:

import Data.Bifunctor -- included in base-4.8!

hoistFree (first (+10)) xs == 11 :> 12 :> Pure 10

And we can make use of iter, of course.

iter (uncurry (+)) (0 <$ xs) == 3 -- sum list elements

It would be nice if LWE could be a Bitraversable (and Bifunctor and Bifoldable), because then we could access the list elements in a more generic and principled way. For this we definitely need a newtype:

newtype LWE a e = LWE (Free ((,) a) e) deriving (lots of things)

instance Bifunctor LWE where bimap = bimapDefault
instance Bifoldable LWE where bifoldMap = bifoldMapDefault
instance Bitraversable LWE where bitraverse = ...

But at this point we might think about just writing the plain ADT out and writing the Applicative, Monad and Bitraversable instances in a couple of lines of code. Alternatively, we could use lens and write a Traversal for the list elements:

import Control.Lens

elems :: Traversal (LWE a e) (LWE b e) a b
elems f (Pure e)  = pure (Pure e)
elems f (x :> xs) = (:>) <$> f x <*> elems f xs

Thinking further along this line, we should make a Lens for the end element. This is a bit of a bonus over the generic Free interface, since we know that every finite LWE must contain exactly one end element, and we can make this explicit by having a Lens for it (rather than a Traversal or Prism).

end :: Lens (LWE a e) (LWE a e') e e'
end f (Pure e)  = Pure <$> f e
end f (x :> xs) = (x :>) <$> end f xs
like image 128
András Kovács Avatar answered Nov 05 '22 00:11

András Kovács