Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define a new monad in Haskell?

I would like to create my own monad in Haskell, and have Haskell treat it just like any other built in monad. For instance, here is code for creating a monad that updates some global state variable each time it is called, along with an evaluator that uses it to compute the number of times the quot function is called:

-- define the monad type
type M a = State -> (a, State)
type State = Int

-- define the return and bind operators for this monad
return a x = (a, x)
(>>=) :: M a -> (a -> M b) -> M b
m >>= k = \x -> let (a,y) = m x in
                let (b,z) = k a y in
                (b,z)

-- define the tick monad, which increments the state by one
tick :: M ()
tick x = ((), x+1)

data Term = Con Int | Div Term Term
-- define the evaluator that computes the number of times 'quot' is called as a side effect
eval :: Term -> M Int
eval (Con a)   = Main.return a
eval (Div t u) = eval t Main.>>= \a -> eval u Main.>>= \b -> (tick Main.>>= \()->Main.return(quot a b))

answer :: Term
answer = (Div (Div (Con 1972)(Con 2))(Con 23))

(result, state) = eval answer 0

main = putStrLn ((show result) ++ ", " ++ (show state))

As implemented now, return and >>= belong in the namespace Main, and I have to distinguish them from Prelude.return and Prelude.>>=. If I wanted Haskell to treat M like any other type of monad, and properly overload the monad operators in Prelude, how would I go about that?

like image 430
David Pfau Avatar asked Dec 11 '22 03:12

David Pfau


1 Answers

To make your new monad work with all the existing Haskell machinery--do notation, for instance--all you need to do is declare your type an instance of the Monad typeclass. Then the Prelude functions >>=, return, etc. will work with your new type just as they do with all other Monad types.

There's a limitation, though, that will require some changes in your examples. Type synonyms (declared with type) cannot be made class instances. (Your M a is exactly the same as Int -> (a, Int).) You'll need to use data or newtype instead. (The distinction between those two is not relevant here.)

Both of those keywords create a genuinely new type; in particular, they create a new data constructor. You should read up on this in any fundamental Haskell text. Briefly, newtype X a = Y (...) creates a new type X a; you can create values of that type using the constructor Y (which can, and often does, have the same name as the type constructor X); and you can consume values by pattern matching on Y. If you choose not to export the data constructor Y, only functions in your module will be able to manipulate the values directly.

(There's a GHC extension TypeSynonymInstances but it won't help you here, because of a separate issue: type synonyms cannot be partially applied; for any type X a = {- ... -} you can only write X a or X Int or whatnot, never just X. You can't write instance Monad M because M is partially applied.)

After that, all you need to do is move your definitions of return and >>= into an instance Monad declaration:

newtype M a = M (State -> (a, State))

instance Monad M where
  return a = M $ \x -> (a, x)
  m >>= k = {- ... -}

Note that the implementation of (>>=) is slightly verbose because you need to unwrap and rewrap the newtype using its data constructor M. Look at the implementation of StateT in transformers, which uses a record accessor to make it easier. (You can manually write a function runM :: M -> State -> (a, State) equivalent to the record syntax that transformers and many other packages use.)

like image 196
Christian Conkle Avatar answered Jan 07 '23 23:01

Christian Conkle