Let's see if I can clear up all of the new API, minus the parameters, with a
story. This will answer your last question about `let` vs `let*`.
To construct the monadic universe we need monadic lambda abstraction and
monadic lambda application. As we all know, everything else follows :)
I will add `monadic-lambda` to the exports. I won't give a formal definition now
but
(monadic-lambda (x y)
(foo x)
(bar y)
baz)
expands to
(lambda (x y)
(bind (foo x) (lambda (_) (bind (bar y) (lambda (_) baz)))))
where _ is a fresh variable each time.
There is also `monadic-apply` which is a generalization of `bind`. `bind` takes
a function f of type (a -> Mb) and a monadic value v of type Ma, it 'unwraps' v
to get a value of type a, which it then passes to f, resulting in a value of
type Mb.
`monadic-apply` takes a function of type (a1 a2 ... an -> Mb) and monadic values
of types Ma1, Ma2, ..., Man. It unwraps the values applies the function to
produce a value of type Mb. The signature is the same as the normal `apply`. So,
(monadic-apply f '(ma1 ma2))
is the same as
(bind ma1
(lambda (a1)
(bind ma2
(lambda (a2)
(f a1 a2)))))
All other `monadic-*` can be defined in terms of the above two primitives,
exactly as how it's done in the regular Scheme world. So,
(monadic-begin body ...)
is sugar for
((monadic-lambda () body ...))
; or equivalently
; (monadic-apply (monadic-lambda () body ...) '())
Also
(monadic-let ((name val) ...) body ...)
is sugar for
(monadic-apply (monadic-lambda (name ...) body ...)
'(val ...))
I leave the definition of `monadic-let*` and other things like `monadic-define`
as an exercise :). I hope that answers your question.
(For category theorists: what I'm doing here is taking the category of Scheme
types with procedures as morphisms and translating to the Kleisli category with
respect to some underlying monad.)
Onto your other points.
On Sun Sep 6, 2026 at 5:28 AM EEST, Peter McGoron wrote:
>> - default-monad is a R7RS parameter. When the [monad] argument is missing in
>> return, bind, join, and lift, (default-monad) is assumed.
>
> I would be a little concerned by this. Setting the monad in one part of
> the program could cause difficult to debug issues in another part of the
> program if you're not careful.
I agree, but it's the user's responsibility if they get confused due to side
effects if they choose to mutate the parameter, don't you think? The new API is
designed so you don't have to mutate directly if you don't want to. You can call
some `explicit-*` and inside its body everything can be implicit.
> Also, the common definition of `parameterize` does not tail-call its body,
> which means some iterative algorithms may blow the stack.
Okay, but there is always a way to wrap the call to `parameterize` outside the
iteration.
Perhaps it's worth making `explicit-monadic-*` not call `parameterize` and add
yet other versions `parameterize-explicit-monad-*` which do. At this point I
would export standard abbreviations like `m-begin`, `em-begin`, `pem-begin`. I
like this better.
> I don't know enough about the design of everything else to suggest an
> alternative. However, when I do something that is like "monads" (writing
> parser combinators[1] or similar[2]) I usually have as much as possible
> be higher order procedures that return higher-order procedures, and use
> an entry point to run the whole thing with given arguments. This is
> similar to the current draft, but the current draft doesn't have a
> procedure to run everything, which is implicit in the do/m macro (or
> equivalent). This might be a good way to compose fragments together
> without needing to rely on parameter objects.
>
> So if the library were designed around how I did [1] and [2],
>
> (monadic-let ((x foo) (y bar))
> (baz x y)
> (return buz))
>
> Would create some object that can be passed as "obj" to (monadic-run
> monad obj), obviating the need for the parameter object.
I don't like abstracting the monad away since in this example foo, bar, baz are
all things that would only make sense in the context of a particular monad. To
be more concrete, perhaps foo and bar read some values from a state monad, and
baz updates the state based on these values.
Hernán