Marc,
On 2020-04-01 16:50 +0200, Marc Nieper-Wißkirchen wrote:
>After the clarification above, could you say what still looks awkward to
>you?
>
>[*snip*]
>
>Already for
>that, having a Maybe monad in Scheme is a great thing. But for that, we
>really have to take care and work hard to get the right and the full
>semantics of a monad.
Thanks very much for the clarifications. It's clear and elegant,
although I wonder about concrete use-cases.
The
great thing about the Maybe monad is that it is a conceptually clear
way to model success and failure in a data-driven and not in a
control-flow kind of way. Procedures that may fail (but for which we
don't want to raise an error) yield Maybes. Procedures that can accept
failures take Maybes. If we want to turn any Scheme procedure that may
raise an error into a procedure yielding a Maybe instead, it will only
work uniformly if we have Maybes of multiple values.
Consider,
for example, the `truncate/' procedure in the R7RS. It is an error if
the second argument is zero. The natural lift of the procedure in the
world of the Maybe monad is
(define (maybe-truncate/ n1 n2)
(if (zero? n2) (nothing) (apply just truncate/ n1 n2)))
Of course, this will only work if `just' accepts more than one value.
A
Maybe will usually not be used to store values for long, it will be
used to pass values between procedures that may fail and the procedures
will be able to be combined with monadic combinators in some
forth-coming library. A general procedure takes more than one value and
can yield more than one value.
Another use case is the following, which converts a signaled error into a procedure yielding an Either:
(define (either-guard proc)
(lambda args (guard (c (else (left c))) (apply right (apply proc args))))
In
Haskell/category language, this turns a partially defined function a
-> b into a total function a -> Tb, where T is the Either monad.
To undo this, we need a combinator Ta -> a, namely
(define (either-raise either)
(either-ref either (lambda cs (apply raise cs)) values))
Actually,
I think such constructs are a valuable addition to this SRFI as it is
similar to the other conversion procedure that convert between different
"conventions". (My chosen names are very debatable.)
In any case, "either-guard" can only work with a general procedure if Maybes can hold multiple values.
I
have more concrete examples, say `mapping-find' from SRFI 146. It's
signature is `(mapping-find predicate mapping failure)'. In searches the
mapping for an association satisfying the predicate and yields two
values, the key and the associated value if it finds an association, and
tail calls failure if not. That's a paragon for the Maybe construct:
(define (maybe-mapping-find predicate mapping)
(call/cc
(lambda (k)
(apply just (mapping-find predicate mapping (lambda () (k (nothing)))))))
Here,
we turned control-driven programming into data-driven programming. (Of
course, we can wrap the result of mapping-find into a list or a cons if
Maybe supports only single values, but that would be rather ad-hoc,
less clear and more arbitrary.)
I found that adding multiple values to Maybe in the current
implementation required very little additional code; I doubt Either
would be much more work. There are some questions about the
conversion functions that would have to be resolved, but it’s easy
to modify the core functions.
A
few functions may not make sense if a Maybe contains less or more than
one values (especially some conversion functions), but that's fine if it
is due to the nature of these functions. (Similarly, in the
hypothetical world of a SRFI 158* where generators can yield multiple
values, some procedures like `generator->list' would still only make
sense for single-valued generators but that's in the nature of the
beast.)
So long,
Marc