Uses for monad-embed

To make monadic code cleaner

monad-embed makes writing code that uses monads as easy as writing pure code. It makes do-notation unnecessary in many cases. It makes sequence, liftM, and some other functions unnecessary.

To automatically generate monadic variants of higher-order functions

Because functions in monad-embed are parameterized on an arbitrary monad by default, functions like mapM and filterM are unnecessary. Haskell offers monadic versions only of common functions such as map, filter, and foldl; when a programmer needs a monadic version of another function, they must write one for themself. More often, they write a manual solution. This reduces code reuse.

For example, consider the following code fragment from a hypothetical compiler:

import Data.Map

processFuns :: Map String Fun -> Map String Fun2
processFuns = Data.Map.updateWithKey processFun

processFun :: String -> Fun -> Maybe Fun2
processFun = ...

This is all well and good until the programmer wants to use the Either monad to report errors that occur in processFun:

processFun :: String -> Fun -> Either ErrorMessage (Maybe Fun2)

Unfortunately, Data.Map does not define updateWithKeyM. The programmer must define it for themself:

updateWithKeyM f m = liftM (fromList . concat) $ sequence [do
        mv <- f k v
        case mv of
            Nothing -> return []
            Just v' -> return [(k, v')]
        | (k, v) <- toList m];

This is inconvenient. monad-embed solves this problem by making every function work with any monad by default. If Data.Map were ported to monad-embed, the functions it defines would work with any monad without any extra effort on the part of the programmer.

To encourage programmers to use monads in new ways

A final reason to use monad-embed is that parameterizing an entire program on an underlying monad opens up new ways of using monads.

For example, a programmer trying to debug a complicated library might temporarily instantiate their entire library in the IO monad to allow them to write debugging output to a file, much as programmers in imperative languages temporary add print calls when trying to track down bugs. This would work without using unsafePerformIO or Debug.Trace to write debugging information.

For example, consider this implementation of quicksort:

sort :: (f :: * -> *, a :: *) => {f} (a -> a -> Ordering) -> List a -> List a;
sort = \ cmp list -> case list of {
    Nil -> Nil;
    Cons x xs -> do {
        greaterThanX = y -> cmp x y `equalOrdering` GT
        } then sort cmp (filter greaterThanX xs) `cat`
            (x `Cons` Nil) `cat`
            sort cmp (filter (not `compose` greaterThanX) xs);
    };
This is equivalent to the normal Haskell definition of quicksort, but parameterized on a comparison function. It is verbose because of monad-embed's poor standard library and parser.

We can instrument this to print a message every time we compare two numbers without changing the definition of sort at all; we only need to define our own comparision function:

printAndCompare :: {IO} Number -> Number -> Ordering;
printAndCompare = \ x y -> do {
    output ("Comparing " `strCat` numToStr x
        `strCat` " to " `strCat` numToStr y);
    } then compareNumbers x y;

exampleList = 4 `Cons` 333 `Cons` -2 `Cons` 84 `Cons` -2 `Cons` Nil;
main = do {
    output "Sorting numbers...";
    nums <- sort printAndCompare exampleList;
    output "Sorted numbers are:";
    } then outputNumbers nums;

The output of this program is:

Output: Sorting numbers...
Output: Comparing 4.0 to 333.0
Output: Comparing 4.0 to -2.0
Output: Comparing 4.0 to 84.0
Output: Comparing 4.0 to -2.0
Output: Comparing -2.0 to -2.0
Output: Comparing -2.0 to -2.0
Output: Comparing 4.0 to 333.0
Output: Comparing 4.0 to -2.0
Output: Comparing 4.0 to 84.0
Output: Comparing 4.0 to -2.0
Output: Comparing 333.0 to 84.0
Output: Comparing 333.0 to 84.0
Output: Sorted numbers are:
Output: -2.0
Output: -2.0
Output: 4.0
Output: 84.0
Output: 333.0