The Free structure page on the Haskell wiki defines a function to convert a functor instance into a free monad:
inj :: Functor f => f a -> Free f a
inj fa = Roll $ fmap Return fa
Then, say inj [1,2,3], has type (Num t) => Free [] t. How do I define a function to return something like inj [1,2,3] back to [1,2,3]?
The first thing to observe is that a variation of inj makes Free into something that is almost a monad transformer.
I'll use Control.Monad.Free, from my free package on hackage, to avoid repeating everything here. This means that Roll becomes Free and Return is instead named Pure in the code below, relative to the version on the wiki.
import Control.Monad
import Control.Monad.Free -- from 'free'
instance MonadTrans Free where
lift = Free . liftM Pure
You cannot however go in the other direction for an arbitrary Functor. However, if you have an instance of Monad on m, you can undo lifting by flattening Free m down to a single layer of the underlying monad m!
retract :: Monad f => Free f a -> f a
retract (Pure a) = return a
retract (Free as) = as >>= retract
The name is chosen because this is a retraction of lift. So called because
retract . lift = id
holds as shown by
retract (lift as) = -- by definition of lift
retract (Free (liftM Pure as)) = -- by definition of retract
liftM Pure as >>= retract = -- by definition of liftM
as >>= \a -> return (Pure a) >>= retract = -- first monad law
as >>= \a -> retract (Pure a) -- by definition of retract
as >>= \a -> return a = -- eta reduction
as >>= return -- second monad law
as
so the function retract undoes the work of lift.
Since fmap = liftM, this holds for inj as well.
Note that lift . retract is not id. There simply isn't enough space to put everything in the intervening type -- the use of the monad smashes everything flat -- but lift . retract . lift . retract = lift . retract holds because lift . retract . lift . retract = lift . id . retract = lift . retract, so lift . retract is idempotent.
The problem with this 'lift' is that 'lift' is not a monad homomorphism, but is instead only a monad homomorphism 'up to retract', so this pushes the burden of preserving the monad transformer laws on the user of the lifted computations, so it makes sense to retain inj as a separate function name.
I'm actually going to go add retract to the free package right now. I needed it recently for an article I'm writing anyways.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With