Another part of my musings, after the zero-values idea. This time it
is, as far as I'm aware, a novel mechanism, i.e. I've never heard of
this idea being considered to be or actually implemented in any language
(as opposed to zero values which are possible in Scheme and just need to
be specified for certain forms), and the couple times I mentioned it to
someone it's been met with "meh" reactions, but here goes anyway just
for the sake of sharing.
The usefulness of a procedure with optional parameters is widely
acknowledged and has many use-cases. But what about the counterpart,
that is providing arguments or values which are optional to handle by a
procedure or continuation?
Perhaps the most obvious use-case:
(table-lookup table key) => value, [found?]
It returns the value associated with 'key' in 'table' or #f when there
is none, and it returns the optional second value that is a Boolean
indicating whether a value was found. So if you care, you can use it to
distinguish between an #f that was found in the table, and an #f
signaling "not found." If you don't care, you can call the procedure in
a context expecting one value without getting an "excess values" error.
Perhaps the reason this has never caught on is that the above is, in
fact, the only use case I could think of so far. :-)
***
When a procedure with optional parameters is called also with optional
values, the behavior would be as follows:
The mandatory and optional parameters of the procedure are conceptually
appended to create a full parameter list. The mandatory and optional
values passed to the procedure are appended as well, creating a full
value list. Each value in the value list fills the corresponding slot
in the parameter list until either list is exhausted. It is an error if
there remain unfilled mandatory parameter slots, or unused mandatory
values.
(That's a mouthful to word, but pretty clean and intuitive once you've
grokked it, IMO.)
***
On a conceptual level, the combination of optional parameters and
optional arguments should be about as efficient to implement as an
efficient implementation of optional parameters only. When you have
optional parameters only, you need to compare the number of provided
values to the minimum (non-optional) and total (non-optional + optional)
number of arguments accepted by your procedure.
nargs >= nparams - nparams_opt && nargs <= nparams
When you also have optional arguments, you need to compare the minimum
number of arguments accepted to the total number of values provided, and
the total number of arguments accepted to the minimum number of values
that need to be handled.
nargs >= nparams - nparams_opt && nargs - nargs_opt <= nparams
That is conceptual only of course, I have no "real" implementation of
this, only <
http://taylanub.github.io/doc/scheme-values.scm.txt> which
is a bit incomplete and too high-level. Someone more knowledgeable than
me might say "nope, way too much overhead on every procedure call/return
once you implement that close to the metal."
*** *** ***
The above idea stands on it own, and I could have made an additional
post on the following, but it probably doesn't deserve it since it
starts getting a bit crazy from here on:
The idea can be carried over to keyword parameters/arguments as well.
While keyword parameters are intrinsically optional, keyword arguments
will normally make a procedure that didn't expect them to spew an error
(unless the implementation is so lenient it doesn't do the consistency
check at all, but I'd say that's bad). Providing keyword arguments that
are optional to handle might sound like an obscure use-case, but guess
in which code-base I've found a use-case for them. Once more, it's in
GNU Guix. :-)
The build mechanism is fairly sophisticated (necessarily so), and every
build phase procedure receives a ton of keyword arguments from the
framework, no matter which ones that precise phase cares about. Most
build phase procedures thus use the #:allow-other-keys token in their
parameter list, which disables the check that the procedure handles all
keyword arguments it's given. This is a bit annoying to type every
time, and it means that the framework can't pass any keyword values
which it deems mandatory to handle. Both of these are weak points, but
it's still something.
***
I've had some additional thoughts, especially about the interaction of
keyword parameters/arguments and "remaining arguments" parameters, over
here: <
http://taylanub.github.io/doc/scheme-values.html>.
(It's originally a plain-text file of "sketch" nature; I was able to
convert it to this format via the 'pandoc' MarkDown/HTML converter, and
10 lines of minimalist CSS I copied from somewhere. Don't expect much
comprehensibility from the content just because the CSS looks stylish.)
I won't touch on that much further here because the ideas get more and
more unusual, but the gist of it is 1. allow a rest-keyword-arguments
parameter like in Python, 2. allow a super-special "give me a values
object" parameter which instead of allocating you a list of the
positional arguments (like in '(lambda foo ...)'), allocates you an
object of a new type, "values," which encapsulates all information on
the values passed to the procedure: positional/mandatory,
positional/optional, keyword/mandatory, and keyword/optional. Objects
of this type would be accepted by 'apply' in addition to lists. Thus
you can continue to wrap arbitrary procedures, remaining fully agnostic
to their parameters, just passing on the values given to your wrapper to
the wrapped procedure.
I believe that yields a *comprehensive* semantics for value passing: by
position, by name, mandatory to provide, mandatory to accept, optional
to provide, optional to accept, gathering remaining positional arguments
in a list, gathering remaining keyword arguments in a table, or reifying
the whole shebang in an object of a specialized type.
The question would be whether it's useful.
Taylan