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 a
and one needed instance:
instance Show (Named a)
show (Named n _ ) = n
And 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 x
Well, 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 bench
So…
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 <$> m
And poof, I am able to write:
notSoUglybenchN :: Named Benchmark -> IO (Named Double)
notSoUglybenchN = sequence . fmap bench
Because this is a common thing to do, we can use a classic function, traverse
, and hence:
benchN :: Named Benchmark -> IO (Named Double)
benchN = traverse bench
Hooray ! 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 b
I 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 b
It 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 named
Well 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