Konrad Hinsen

2008-12-11 12:15:18 UTC

A while ago I uploaded an implementation of monads in Clojure to the

group's file section. I have now replaced it by a more or less complete

rewrite. This rewrite was motivated by one serious problem with the

original implementation, plus a number of minor points that I was not

happy with. I have also integrated some ideas from Jim's alternative monad

implementation. The new implementation has replaced the original one at

http://clojure.googlegroups.com/web/monads.clj

In my original implementation, I had used keywords for the monad

operations. These keywords were replaced by their definitions inside a

given monad using a recursive symbol replacement in the form to be

evaluated. This approach has a major problem: it doesn't combine correctly

with macros. If a macro expands into a form containing monad operation

keywords, they are not replaced at all, because my symbol replacement code

sees only the original form.

The new implementation uses plain functions for the monad operations. The

with-monad form

expands into a straightforward let form that binds the operations to their

names. Generic monad operations such as lift are implemented as functions

that take the four basic monad definitions as additional parameters. These

functions are wrapped by macros that add these parameters to the argument

list. In the new implementation, all form manipulation is done by macros,

and there is much less of it: it no longer has a fundamental role, but

just provides nicer syntax.

To pursue my original idea of a replacement-based implementation, one

would need something like Common Lisp's symbol-macrolet, which is however

not trivial to implement. It requires either an integration with the

compiler's macro expansion mechanism, or a separate implementation of full

macro expansion, including subforms, of an arbitrary form.

With the new monad implementation, client modules can:

- define new monads (monad and defmonad)

- define new generic monad operations (defmonadfn)

- execute code using a monad (with-monad)

- define functions that internally use a specific monad (define the

function inside a with-monad block)

- use monad comprehension syntax (domonad)

Some minor changes:

- All monad operations have names starting with m-.

- There is a single m-lift that takes its arity as an argument.

- m-plus takes an arbitrary number of arguments and respects lazy evaluation.

Konrad.

group's file section. I have now replaced it by a more or less complete

rewrite. This rewrite was motivated by one serious problem with the

original implementation, plus a number of minor points that I was not

happy with. I have also integrated some ideas from Jim's alternative monad

implementation. The new implementation has replaced the original one at

http://clojure.googlegroups.com/web/monads.clj

In my original implementation, I had used keywords for the monad

operations. These keywords were replaced by their definitions inside a

given monad using a recursive symbol replacement in the form to be

evaluated. This approach has a major problem: it doesn't combine correctly

with macros. If a macro expands into a form containing monad operation

keywords, they are not replaced at all, because my symbol replacement code

sees only the original form.

The new implementation uses plain functions for the monad operations. The

with-monad form

expands into a straightforward let form that binds the operations to their

names. Generic monad operations such as lift are implemented as functions

that take the four basic monad definitions as additional parameters. These

functions are wrapped by macros that add these parameters to the argument

list. In the new implementation, all form manipulation is done by macros,

and there is much less of it: it no longer has a fundamental role, but

just provides nicer syntax.

To pursue my original idea of a replacement-based implementation, one

would need something like Common Lisp's symbol-macrolet, which is however

not trivial to implement. It requires either an integration with the

compiler's macro expansion mechanism, or a separate implementation of full

macro expansion, including subforms, of an arbitrary form.

With the new monad implementation, client modules can:

- define new monads (monad and defmonad)

- define new generic monad operations (defmonadfn)

- execute code using a monad (with-monad)

- define functions that internally use a specific monad (define the

function inside a with-monad block)

- use monad comprehension syntax (domonad)

Some minor changes:

- All monad operations have names starting with m-.

- There is a single m-lift that takes its arity as an argument.

- m-plus takes an arbitrary number of arguments and respects lazy evaluation.

Konrad.