On this page:
gen:  monad
monad?
bind
mdo
:  =
29.1 Examples and Background
8.12

29 Monads🔗ℹ

 (require denxi/monad) package: denxi

denxi/monad implements a monomorphic bind operation and a notation for composition.

syntax

gen:monad

A generic interface for monad types.

Only includes bind to date.

Returns #t if the argument implements gen:monad.

procedure

(bind ma lift)  monad?

  ma : monad?
  lift : (-> any/c monad?)
Returns a monadic value that composes ma with the monadic value returned from lift.

Use to compose two functions that each deal with a monadic type.

syntax

(mdo step ...)

 
step = expr
  | id := expr
A “monadic do” form for composing a sequence of steps using bind operations.

In the simplest case, (void? (mdo)). This captures the sequence that does nothing.

For single-terms, (eq? (mdo V) V). Once more than one term is available, each term must be of the same monadic type. In other words, mdo is monomorphic.

:= is the monadic bind operator. Here’s an example in terms of subprograms:

(mdo number := (subprogram-unit 1)
     number)

syntax

:=

Binds an expression on the right hand side to an identifier on the left hand side in the context of mdo.

Raises an error when used outside of mdo. Since package outputs use implicit mdo forms, := may also appear within them.

29.1 Examples and Background🔗ℹ

Some subprograms in Denxi use an alternative notation within a output or mdo form.

(output "default"
        archive-input := (input-ref "default.tgz")
        path := (resolve-input archive-input)
        (extract path)
        (release-input archive-input))

:= is like let, but it isn’t exactly the same because this abbreviated program does not work.

(output "default"
        (release-input (extract (resolve-input (input-ref "default.tgz")))))

This program breaks because := does more than bind a value to an identifier. It also finds the value it needs to bind in the first place from a special context called a monad. There are many tutorials that explain monads poorly, and this is likely one of them. I encourage you to search online for programming exercises, but I’ll give you an abridged introduction to keep you moving in Denxi’s documentation.

Here are two functions.

(define (add5 v)
  (+ v 5))
 
(define (sub2 v)
  (- v 2))

You can compose them.

(sub2 (add5 4))

Now let’s change the functions so that they return another value.

(define (add5 v) (values (+ v 5) (format "Adding 5 to ~s" v)))
(define (sub2 v) (values (- v 2) (format "Subtracting 2 from ~s" v)))

Functional programmers might do this because everything returned from each function is expressed purely in terms of arguments, and there are no side-effects.

Problem is, (sub2 (add5 4)) no longer works, and we have not defined a way to separate and handle the first and second values where they each make sense. Monads takes care of all that, so function composition works again.

The mdo form knows how to perform composition in this way, and the := operator knows how to extract the actual value you want to use from the extra data. therefore, (extract (resolve-input (input-ref "default.tgz"))) doesn’t work because you passed the value you want plus extra stuff to extract.

If you look at the documentation for a function in Denxi and see that it returns an unfamiliar value like (subprogram/c string?) instead of just string?, it is probably meant for use as a monadic type, where := pulls out the string you want.