Towards monadic bidirectional serialization, a step back
Posted on October 13, 2016
This is written in Literate Haskell.
module Bidirectional.Serialization.Two where
import Control.Applicative
import Data.ProfunctorLast episode
I proposed to extend Codec as follows:
data Codec_ r w x a = Codec_
{ parse_ :: r a
, produce_ :: x -> w ()
, project_ :: x -> a
}And so on. It was becoming quite sophisticated.
A step back
Instead fix produce to carry the a type as well.
In a way, this merges produce_ and project_ in a single
field.
data Codec r w x a = Codec
{ parse :: r a
, produce :: x -> w a
}
instance (Functor r, Functor w)
=> Functor (Codec r w x) where
fmap f codec = Codec
{ parse = fmap f (parse codec)
, produce = fmap f . produce codec
}
instance (Applicative r, Applicative w)
=> Applicative (Codec r w x) where
pure a = Codec (pure a) (\_ -> pure a)
f <*> a = Codec
{ parse = parse f <*> parse a
, produce = \x -> produce f x <*> produce a x
}
-- We had lost this one for a moment
instance (Alternative r, Alternative w)
=> Alternative (Codec r w x) where
empty = Codec empty (\_ -> empty)
a <|> a' = Codec
{ parse = parse a <|> parse a'
, produce = \x -> produce a x <|> produce a' x
}
instance (Functor r, Functor w)
=> Profunctor (Codec r w) where
lmap from codec = codec
{ produce = produce codec . from
}
rmap = fmap
-- Hurray!
instance (Monad r, Monad w)
=> Monad (Codec r w x) where
a >>= f = Codec
{ parse = parse a >>= parse . f
, produce = \x ->
produce a x >>= \a -> produce (f a) x
}Note that constraints are now symmetrical, (Functor r, Functor w) and
(Monad r, Monad w), instead of Functor r and
(Monad r, Applicative w), respectively.
In hindsight, the fact that previously we only ever used an Applicative w
was suspicious.
We were actually relying on w () being a monoid.
We might as well turn w () into a single variable m.
-- Simplified Codec_
-- The same instances can be implemented with
-- a Monoid m constraint instead of Applicative w.
data CodecM r m x a = CodecM
{ parseM :: r a
, produceM :: x -> m
}Our Codec subsumes CodecM (and thus Codec_) via the following
encoding:
-- We have Monoid m => Applicative (Const m)
codecMToCodec :: CodecM r m x a -> Codec r (Const m) x a
codecMToCodec a = Codec
{ parse = parseM a
, produce = Const . produceM a
}
codecToCodecM :: Codec r (Const m) x a -> CodecM r m x a
codecToCodecM a = CodecM
{ parseM = parse a
, produceM = getConst . produce a
}