Defining and using monadic structures on custom datatypes?

127 views Asked by At

I'm getting to grips with functors, applicatives and monads.

These examples are just to illustrate the basics:

data User a = Age a deriving (Show, Eq, Ord)

Functor (apply a non-context function to a single context datatype):

instance Functor User where
  fmap f (Age a) = Age (f a)

functorFunction :: Int -> Int
functorFunction e = e + 1

main = do
 print $ fmap functorFunction (Age 22)

Applicative (apply a simple function to multiple context datatypes):

instance Applicative User where
  pure a = Age a
  (<*>) (Age a) = fmap a

applicativeFunction :: Int -> Int -> Int
applicativeFunction e1 e2 = e1 + e2

main = do 
  print $ pure (applicativeFunction) <*> (Age 44) <*> (Age 65)  

I've going through learnyouahaskell and haven't been able to find a simple explanation of

1) how to define a monad structure for my 'User a' type, and 2) what functionality a monad provides versus, say, an applicative functor?

1

There are 1 answers

0
duplode On BEST ANSWER

1) how to define a monad structure for my 'User a' type

(Preliminary note: Learn You a Haskell predates Applicative becoming a superclass of Monad. For that reason, and unlike the book suggests, with recent versions of GHC there is no need of implementing return -- by default, it is the same as pure. That being so, I will jump directly to (>>=).)

One nice thing about methods with very general types such as (>>=)...

(>>=) :: Monad m => m a -> (a -> m b) -> m b

... is that they are so general that there are few sensible ways of implementing them -- often just one. If we specialise (>>=) for your type, we get:

(>>=) @User :: User a -> (a -> User b) -> User b

So (>>=) is supposed to take an User a and an a -> User b and use them to produce an User b. Given that the function takes an a value, the first thing to try is looking for an a value to pass to it. We do have such a value: the one wrapped by the Age constructor in the User a value:

instance Monad User where
    (Age a) >>= f = f a

To write a Monad instance in good conscience, we should check whether it follows the monad laws:

return a >>= f = f a

m >>= return = m

(m >>= f) >>= g = m >>= (\x -> f x >>= g)

That can be done with (your favourite equivalent of) pen and paper:

return a >>= f = f a
Age a >>= f = f a
f a = f a

m >>= return = m
Age a >>= return = Age a -- [*]
Age a >>= Age = Age a
Age a = Age a 

-- [*]: Both here and in the next law, we can replace 'm' by 'Age a'
--      because there is just one constructor. If there were more
--      (as in e.g. data Maybe a = Nothing | Just a), we would have
--      to check the law holds for all constructors.  

(m >>= f) >>= g = m >>= (\x -> f x >>= g)
(Age a >>= f) >>= g = Age a >>= (\x -> f x >>= g)
f a >>= g = (\x -> f x >>= g) a
f a >>= g = f a >>= g

2) what functionality a monad provides versus, say, an applicative functor?

Here I will leave it to the question arrowd suggested. I will only note that your example doesn't give you enough to really appreciate the difference, as the User context is too simple to let you do anything interesting with (>>=) (as Benjamin Hodgson points out, it is isomorphic to Identity, the dummy functor). Maybe, which is used by the accepted answer of the linked question, is quite a bit more illustrative.