[Haskell-cafe] Pythonic monads

Graham Klyne GK at ninebynine.org
Fri Feb 3 14:15:46 EST 2006

Constructing some code today in Python, using some functional-style coding
idioms, I found myself wondering if there would be any real benefit to using a
monad-based implementation (i.e. other than to demonstrate that it can be done).

The application that sparked this line of thought was a simple filter to trim
comments and whitespace out of an XML document.  I ended up with a simple
state-machine driven filter, thus:

# Strip comments from XML strings
def stripXmlComments(x):
    # State table for stripping XML comments
    stStripXmlComments = \
        { 0: match1('<',(1,stateFilterHold),(0,stateFilterPass))
        , 1: match1('!',(2,stateFilterHold),(0,stateFilterPass))
        , 2: match1('-',(3,stateFilterHold),(0,stateFilterPass))
        , 3: match1('-',(4,stateFilterDrop),(0,stateFilterPass))
        , 4: match1('-',(5,stateFilterDrop),(4,stateFilterDrop))
        , 5: match1('-',(6,stateFilterDrop),(4,stateFilterDrop))
        , 6: match1('>',(0,stateFilterDrop),(4,stateFilterDrop))
    return stateFilter(stStripXmlComments,x)

# Simple state machine driven filter
# The state table is a dictionary indexed by state values, where the
# initial state is 0, and each entry is a function that accepts a next
# symbol and returns a pair of (next state, action), where action is
# one of 'stateFilterPass', 'stateFilterDrop', 'stateFilterHold'.
# stateFilterHold means that the disposition will be determined later.
# The result is an iterator that returns elements from the filtered
# subsequence of the supplied sequence.
def stateFilter(stable,seq):
    queue = []
    state = 0
    for symbol in seq:
        (state,action) = stable[state](symbol)
        (queue,emit) = action(queue,symbol)
        for e in emit: yield e
def stateFilterPass(q,n):
    return ([],q+[n])
def stateFilterDrop(q,n):
    return ([],[])
def stateFilterHold(q,n):
    return (q+[n],[])

# State transition function to match the specified symbol and return
# 'eqval' if matched, otherwise 'neval'
def match1(sym,eqval,neval):
    def m(sym,eqval,neval,next):
        if next==sym:  return eqval
        return neval
    return curry(m,sym,eqval,neval)

def curry(func, *args):
    Curry multiple arguments:
    See: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/229472
    def curried(*args2):
        args2 = args + args2
        return func(*args2)
    return curried

and a test case:
    def testFilter02(self):
        fullstr = "<!-abc-> <!--def--> <!- -ghi-->"
        trimstr = list(stripXmlComments(fullstr))
        expstr  = list("<!-abc->  <!- -ghi-->")
        assert trimstr==expstr, \
               "stripSpaces, expected:\n"+expstr+"\nFound:\n"+trimstr

In thinking about this implementation, it seemed to me that this employed
patterns characteristic of a monadic type:  each entry in the state table (in
this case, an instance of match1, a curried function) is like a step in a
monadic computation, updating the monadic value and also returning some value.

What I can't quite visualize is if the code in Python would actually look any
better if it were implemented with a monadic type, as one might readily choose
for a Haskell implementation.  Or would there be no real benefit?

I have noticed that, while I like to use functional idioms in some of my Python
code, and the Python language is easily able to support these (even some lazy
evaluation, courtesy of generators), that the code doesn't always look as clean
as its Haskell equivalent.  In Haskell, composition and currying are fundamental
patterns and are directly supported by the syntax.  In Python, one has to work
harder to achieve these (e.g. the "curry" function above seems rather convoluted
to me, for such a fundamental notion).

Thoughts? Comments?


Graham Klyne
For email:

More information about the Haskell-Cafe mailing list