Am So., 13. Juni 2021 um 00:18 Uhr schrieb Wolfgang Corcoran-Mathe <xxxxxx@sigwinch.xyz>:

What I mean here is, I think, what you refer to in your
"`if' vs. `whensoever'" example.  One might forget the procedural
language entirely and specify a number of macros for working with
fxmappings, much as an `if' form makes sense without an underlying
procedure.

This is certainly a viable approach. I am not claiming that it is the best. It likely becomes a good option if the procedural interface becomes inconvenient to use in the majority of the use cases (as would the "whensoever" procedure).
 
> A major goal should certainly be to be able to express algorithmic content
> as directly as possible. Some programming languages make it easier than
> others. For example, when coding an algorithm that returns two values, I
> can do this directly in Scheme, namely by, well, returning two values. In a
> programming language that does not have multiple values, I cannot express
> the algorithmic intent as directly because I have to wrap the values in
> some kind of struct, which I then return.

I think this comes down to how we express algorithms.  If we think of
a partial function (say `fxmapping-lookup') as "returning some values,
or ⊥", then Maybe is, I think, the most direct expression.  If we
rather talk in terms of delivering values to continuations, as
R[67]RS often does, then CPS is more expressive.  I argue that the
latter is generally more familiar and convenient, but I'm confident
that this is a matter of taste.

I still claim that it is the CPS approach that expresses the algorithm and nothing more. The point is, to even be able to formulate "partial functions" (BTW, the term may be a bit misleading) in terms of Maybes, you need coproducts in your domain of values. In the CPS approach, where you don't internalize the idea of two possible future code paths, you don't.

Of course, in Scheme you can form coproducts of values (union types), so practically it seemingly does not matter. But this is like proving Lagrange's theorem for finitely generated abelian groups using the fundamental theorem of finitely abelian groups. Doable, but one fails to see that the theorem holds for all groups (formulated in a way so that it makes sense also for infinite groups). Even if one is only interested in finitely generated abelian groups, the more specialized proof is certainly inferior because it only tells you half the truth.

To summarize, returning Maybes for what you call a partial function here is not the most direct expression because it involves internalizing an external notion.

What I don't get is your belief that Maybe is more familiar. This is certainly not true for Scheme programmers.
 
There is also the (justly unpopular) argument of popularity.
Maybe/Option is well-established in the functional language world,
outside of the Lisps.  Arguments for CPS over Maybe in the
ML-descended world seem to be few and far between; it seems to be a
familiar and expressive-enough way to describe partial functions for
those communities.

You raise a good point but I think it shows the opposite: In ML, you don't have multiple values (for input or output) but all functions take and return a single value. So everything is packed in a record (has to be internalized using my language from above) where in Scheme you just have multiple values. Then, unless I am mistaken, Standard ML does have no guarantee of having proper tail calls. So the CPS approach is ruled out as well.

This shows that ML and Scheme are quite different languages in this regard and that it is not a good idea to blindly transfer idioms from one of the languages to the other. (Besides, if I want to program in an ML-like language, I would program in ML, not in Scheme.)
 
> In view of this, it may arise an easier usable API when thin syntactic
> abstractions over some procedures are provided instead of the procedures
> themselves. Another possible advantage would be that they can render the
> question of CPS vs Maybe moot.

Do any useful syntactic abstractions for SRFI 224 operations come to
mind?  I can't find any in the related "dictionary type" SRFIs, and
I'm concerned about adding entirely new forms at this point.

I agree that it doesn't make sense to add them just to SRFI 224. It would probably be a good idea to rethink it when more SRFIs are taken into account. Or SRFI 224 adds some of them, but then we should add them to the other SRFIs later as well.
 
> PS Part of my problem is that I don't think I have seen a compelling
> example for why the CPS convention produces code that would be harder to
> understand.

I believe the specs of fxmapping-alter and fxmapping-search are
a good example.  (Although someone could probably express both more
concisely than I have.)

(fxmapping-alter fxmap k proc) → fxmapping

proc is a procedure of type maybe[exact-integer, *] → maybe[*].

Returns an fxmapping with the same associations as fxmap, except that
the association, or lack thereof, for k is updated as follows.  If the
association (k, v) exists in fxmap, then proc is called on Just k v;
if no such association exists, then proc is called on Nothing.  If the
result of this application is Nothing, the association is deleted (or
no new association is added); if the result is Just v′, the
association (k, v′) is added to the resulting fxmapping, replacing any
old association for k.

;;;; vs.

(fxmapping-search fxmap k failure success) → fxmapping

Returns a new fxmapping with the same associations as fxmap, except
that the association with key k (or the absence thereof) is updated as
follows.

* If fxmap contains an association (k, v), then success is invoked on v
and two procedure arguments, update and remove, and is expected to
tail-call one of them.

  + If update is invoked on an arbitrary value v′, then the resulting
    fxmapping contains the association (k, v′), which replaces the old
    association for k.

  + If remove is invoked on no arguments, then no association for k
    appears in the resulting fxmapping.

* If fxmap does not contain an association for k, then failure is
  invoked on two procedure arguments, insert and ignore, and is
  expected to tail-call one of them.

    + If insert is invoked on an arbitrary value v, then the resulting
      fxmapping contains the association (k, v).

    + If ignore is invoked on no arguments, then no association is
      added and the resulting fxmapping is identical to fxmap.

The result of calling any of the update, remove, insert, and ignore
procedures in a non-tail context is unspecified.

While the latter is longer, I don't think it is any harder to understand. But I am a bad judge because both protocols are not new to me.

It should be noted, though, that the latter version is extensible whereas the former breaks down in a more general context. The former works here because, by chance, you have at most two options for the in-direction (success/failure) and two options for each of the out-directions (update/remove, insert/ignore).

We could, actually, make the CPS protocol even more expressible and useful by removing the restriction that update/remove/insert/ignore have to be tail-called. Just let them return the updated fxmapping and let the continuation of the call to success/failure to be the continuation of fxmapping-search.

This way I can code something like:

;; Moves the value at KEY, if it exists, to 0.
(fxmapping-search fxmap key
  (lambda (insert ignore)
    (ignore))
  (lambda (v update remove)
    (fxmapping-adjoin (remove) 0 v))))

With this obvious extension (which removes the need for any error checking wrt tail-calling!), the CPS version becomes much more expressive than the Maybe version. Moreover, the old SRFI 146 version of the protocol needs the option to return an extra value (something that seems to be missing in SRFI 224 as well), but this we don't need here anymore because we can just use values to do it.

Just for illustration, with the syntactic abstraction I suggested a few posts ago, this would look like:

(fxmapping-search fxmap key
  [v
   (fxmapping-adjoin (remove) 0 v)]
  [else
   (ignore)])


I suggest that fxmapping-alter is more concise and probably clearer to
those unfamiliar with CPS.  There's also the fact that the procedure
object passed to fxmapping-alter is an operator on Maybes; it simply
has to return a value, and so might just be some arbitrary operator
you've written.  This is a major win for compositionality.  The
continuations passed to `search', on the other hand, must speak the
CPS protocol, and thus almost certainly can't be previously-written
functions (unless you're a huge CPS fan).

Do you have an example in mind where it cannot be remedied by just a thin lambda wrapper around it? And can't you turn the argument the other way round? If your building blocks do not speak the (alien-to-Scheme) Maybe protocol, you will have to add wrappers to them.

Marc