Am Do., 2. Apr. 2020 um 21:38 Uhr schrieb Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de>:

Am Do., 2. Apr. 2020 um 20:55 Uhr schrieb Wolfgang Corcoran-Mathe <xxxxxx@sigwinch.xyz>:
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)))

That is, of course, stupid code.

The corrected version is:

(define (maybe-truncate/ n1 n2)
  (if (zero? n2) (call-with-values (lambda () (truncate/ n1 n2)) just))
 

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))))

Same problem here. Correction:

(define (either-guard proc)
  (lambda args (guard (c (else (left c))) (call-with-values (lambda () (apply proc args)) right))))

 

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)))))))

(define (maybe-mapping-find predicate mapping)
  (call/cc
    (lambda (k)
      (call-with-values (mapping-find predicate mapping (lambda () (k (nothing)))) just))))
 

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

Am Do., 2. Apr. 2020 um 20:55 Uhr schrieb Wolfgang Corcoran-Mathe <xxxxxx@sigwinch.xyz>:
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.

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.

Regards,

--
Wolfgang Corcoran-Mathe  <xxxxxx@sigwinch.xyz>

"Invent and fit; have fits and reinvent!" --Alan J. Perlis