osa1 github gitlab twitter cv rss

Ramblings on monads

April 9, 2012 - Tagged as: haskell, en.

I had written a short post about Haskell and monads to Nathan’s University forum as a first homework, and I wanted to add it to my blog too:


I know lots of people here have already given Haskell as an example, but I want to mention to a different point of Haskell. Monads and DSL capabilities.

Every monad in Haskell is potentially a DSL. You can define commands(ie. functions) in a syntax that looks almost like syntax in imperative languages even if you’re doing a purely functional computation(for imperative computations, see IO monad). When you write a monad and some functions working with this monad, you basically write operations of a kind of computations, and a way to combine this computations(with >>= function, read as bind).

This gives you two great advantages. First, monads give you an elegant way to separate combination and calculation logic, and second, it gives you an opportunity to create syntactic abstractions.

For example, you don’t have to pass some states around functions thank to monads. You can just create a monad with functions getting a state and returning some values and the new state. Then you can define your combination logic(bind functions) and with the help of do notation, you can write almost imperative looking code, passing states automatically. See example:

import Control.Monad.State
import Control.Monad
type AvgState = State (Int, Int) Int
state0 = (0, 0)
 
addAvg :: Int -> AvgState
addAvg x = do
    (count, total) <- get
    put (count+1, total+x)
    return $ (total+x) `div` (count+1)
 
test :: AvgState
test = do
    addAvg 10
    addAvg 20
    addAvg 30
 
main :: IO ()
main = do
    print $ evalState test state0

Here I’m calculating arithmetic average of some integers. type AvgState is my data type representing the sum of the numbers I give and the total count of numbers. Here I don’t write a new monad, instead I use Haskell’s State monad, contained in Control.Monad.State package.

addAvg functions is the main logic. If you look at it, it almost looks like an imperative program, I’m reading some values and changing them by adding them one, and returning a new value(note that I’m not returning any new states, it’s being handled my the monad itself), but still it’s purely functional.

Now how’s that a DSL? Look at test function and hopefully you’ll see :) .

I want to give another example about DSL-like monads: Parsec.

I’ve been working on a Websocket based chat protocol written in Haskell lately and this code is directly from my project:

chanName :: Parser ChanName
chanName = many1 (letter <|> oneOf "-" <|> digit)
 
msgCmd :: Parser Cmd
msgCmd = do
    string "msg"
    spaces
    chan <- chanName
    spaces
    msg <- many1 anyChar
    eof
    return $ MsgCmd chan msg

I’m using Parsec’s Parser monads with do notation and it looks almost like Backus–Naur Form. chanName mathes list of letters, ‘-’ character, or digits with at least one element. This is a parser. And then I’m using this parser in my msgCmd parser. It matches a string “msg”, then arbitrary number of spaces, then chanName, then spaces again, and at last any characters.