Named things in Haskell
For my GSoC project, I have to deal with named things, and I tried to generalized the traditional (String,a) object and I came with an interesting Named a data.
The problem
We often deal with named things, on which we want to perform some operations, without changing its name. For example, I want to benchmark libraries, so I will start with something like (String, Benchmark), the String being the library name, and come out to a (String, Double), with the Double being the time of taken by the function.
( For the sake of simplicity, lets imagine we have a function:
bench :: Benchmark -> IO Double)
The Named Data
I started to write the Named data:
data Named a = Named String aand one needed instance:
instance Show (Named a)
show (Named n _ ) = nAnd then, the main idea: applying a function f: a -> b to a Named a will produce a Named b without changing the name. My Haskell alarms ringed and detected a Functor instance:
instance Functor Named where
fmap f (Named n x) = Named n $ f xWell, so thought I was able to write a pretty function like:
benchN :: Named Benchmark -> IO (Named Double)But recall, bench is of type Benchmark -> IO Double, so, using only fmap will lead to:
uglyBenchN :: Named Benchmark -> Named (IO Double)
uglyBenchN = fmap benchSo…
The Traversable instance
The solution is to provide sequence :: Monad m => Named (m a) -> m (Named a), which is inside the Traversable class. As its type says, sequence will “strip-out” the monadic effect. So we have:
instance Foldable Named where
foldMap f (Named _ x) = f x
instance Traversable Named where
-- Applicative f => Named (f a) -> f (Named a)
sequenceA (Named n m) = Named n <$> mAnd poof, I am able to write:
notSoUglybenchN :: Named Benchmark -> IO (Named Double)
notSoUglybenchN = sequence . fmap benchBecause this is a common thing to do, we can use a classic function, traverse, and hence:
benchN :: Named Benchmark -> IO (Named Double)
benchN = traverse benchHooray ! But can we do better ? Named seems a pretty interesting object…
Is Named a Monad ? (Answer, no it is a CoMonad)
An interesting Functor is sometimes a Monad. So I started my checklist: to provide a Monad I have to provide the return and the bind function:
return :: a -> Named a
>>= :: Named a -> (a -> Named b) -> Named bI don’t have a standard way to name things, and neither to get a name out of two named things… But I can easily strip out the name, and conserve the name:
notReturn :: Named a -> a
notBind :: Named a -> (Named a -> b) -> Named bIt seems pretty much like something dual to a monad. And this is called a Comonad :)
The instance definition:
instance Comonad Named where
-- Named a -> a
extract (Named _ obj) = obj
-- Named a -> (Named a -> b) -> Named b
(=>>) named f = Named (show named) $ f namedWell we have a Comonad instance, we can redefine the Eq instance, given a little function:
liftExtract :: (Comonad w) => (a -> b -> c) -> w a -> w b -> c
liftExtract f a b = f (extract a) (extract b)
instance Eq a => Eq (Named a) where
(==) = liftExtract (==)liftExtract can in fact be used to define every lifted instances of transformers…
And can even redefine the Functor instance:
instance Functor Named where
fmap = liftW