by Marc Nieper-Wißkirchen (spec and R^{6}RS implementation) and Daphne Preston-Kendal (R^{7}RS implementation)
This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-227@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI specifies the opt-lambda
syntax, which
generalizes lambda
. An opt-lambda
expression
evaluates to a procedure that takes a number of required and a number of
optional (positional) arguments whose default values are determined by
evaluating corresponding expressions when the procedure is called.
This SRFI also specifies a variation opt*-lambda
, which is
to opt-lambda
as let*
is to let
and the related binding constructs let-optionals
and let-optionals*
.
Finally, for those who prefer less explicit procedure
definitions, a sublibrary provides define-optionals
and define-optionals*
.
Scheme procedures taking optional arguments can either be implemented
with a lambda
form taking a rest argument or
a case-lambda
form. Both approaches are not optimal. The
disadvantage of using lambda
is that the rest argument,
which is an implementation detail, has to be made explicit, that it has
to be destructured explicitly, and that there is some reliance on
compiler optimizations to eliminate heap allocation of the rest argument.
The disadvantage of using case-lambda
is that the size of
the source code becomes quadratic in the number of optional arguments.
In contrast, the opt-lambda
syntax specified in this SRFI
does not suffer from these issues but provides an idiomatic way to create
procedures with optional (positional) arguments.
(define f (opt-lambda (a b (c 1) (d 2) . r) (list a b c d r))) (f 1 2)(1 2 1 2 ()) (f 1 2 3)(1 2 3 2 ()) (f 1 2 3 4)(1 2 3 4 ()) (f 1 2 3 4 5)(1 2 3 4 (5)) (define n 1) (define g (opt-lambda (n (m (* n 2))) (list n m))) (g 2)(2 2) (g 2 3)(2 3) (set! n 2) (g 1)(1 4) (g 1 2)(1 2) (define g* (opt*-lambda (n (m (* n 2))) (list n m))) (set! n 1) (g* 2)(2 4) (g* 2 3)(2 3) (let-optionals '(1 2) (x . y) (list x y))(1 (2)) (let-optionals '(1) (x (y 2) (z 3)) (list x y z))(1 2 3) (let-optionals* '(1 3) (x (y 2) (z (+ x y))) (list x y z))(1 3 4) (define-optionals (f1 x (y 1)) (list x y)) (f1 0)(0 1) (define-optionals* (f2 x (y (* x x)) . z) (list x y z)) (f2 3)(3 9 ())
(opt-lambda opt-formals body)
Syntax:
Opt-formals
is either of the form
(variable_{1} … variable_{n}
binding_{1} …
binding_{m})
or
(variable_{1} … variable_{n}
binding_{1} …
binding_{m}
. variable)
, where
each binding_{i}
has the form (variable_{n
+ i} init)
,
where each init
is an
expression. It is a syntax violation if the same variable appears
more than once among
the variable
s.
Semantics: An opt-lambda
expression evaluates to
a procedure and is lexically scoped in the same manner as a procedure
resulting from a lambda
expression. When the procedure
is later called with actual arguments,
the variable
s are bound to
fresh locations, the values of the corresponding arguments are stored
in those locations, the body
is evaluated in the extended environment, and the results
of body
are returned as the
results of the procedure call.
A procedure created with the first syntax
of opt-formals
takes at
least n
arguments and at
most n +
m
arguments. A procedure created with the second
syntax of opt-formals
takes n
or more arguments. If the procedure
is called with fewer than n + m
(but at least n
arguments), the missing
actual arguments are substituted by the values resulting from
evaluating the corresponding init
s. The
corresponding init
s are evaluated in an
unspecified order in the lexical environment of
the opt-lambda
expression when the procedure is
called.
It is an assertion violation if the procedure created with the first
syntax of opt-formals
is
called with more than n + m
actual
arguments. The value stored in the binding
of variable
of a procedure
created with the second syntax
of opt-formals
will be a
newly allocated list of the actual arguments left over after all the
other actual arguments have been matched up against the other formal
arguments (or the empty list in case no actual arguments are left
over).
Note: Both n
and m
may be zero.
(opt*-lambda opt-formals body)
syntaxSimilar to opt-lambda
except that
the init
s corresponding to
missing actual arguments are evaluated sequentially from left to right,
and the region of the binding of
a variable
is that part of
the opt*-lambda
expression to the right of it or
its binding
.
(let-optionals expression opt-formals body)
syntaxSyntax:
Opt-formals
is as in
a opt-lambda
expression.
Semantics:
Operationally equivalent to
(apply (opt-lambda opt-formals body) expression)
(let-optionals* expression opt-formals body)
syntaxSimilar to let-optionals
except that
opt-lambda
is replaced with opt*-lambda
in
the operational definition.
The following definition syntax is exported by the (srfi
:227 opt-lambda definitions)
sublibrary (for R^{6}RS systems)
and by the (srfi 227 definition)
sublibrary (for
R^{7}RS systems).
(define-optionals (identifier . opt-formals) body)
syntaxSyntax:
Opt-formals
is as in
a opt-lambda
expression.
Semantics:
Operationally equivalent to
(define identifier
(opt-lambda opt-formals body))
(define-optionals* (identifier . opt-formals) body)
syntaxSyntax:
Opt-formals
is as in
a opt-lambda
expression.
Semantics:
Operationally equivalent to
(define identifier
(opt*-lambda opt-formals body))
A portable implementation for R^{6}RS is given by:
(library (srfi :227 opt-lambda) (export opt-lambda opt*-lambda let-optionals let-optionals*) (import (rnrs (6)) (srfi :227 opt-lambda meta)) (define-syntax opt-lambda (make-opt-lambda-transformer 'opt-lambda #f)) (define-syntax opt*-lambda (make-opt-lambda-transformer 'opt*-lambda #t)) (define-syntax let-optionals (syntax-rules () [(_ expr opt-formals body1 ... body2) (apply (opt-lambda opt-formals body1 ... body2) expr)])) (define-syntax let-optionals* (syntax-rules () [(_ expr opt-formals body1 ... body2) (apply (opt*-lambda opt-formals body1 ... body2) expr)])) (define-syntax define-optionals (syntax-rules () [(_ (name . opt-formals) body1 ... body2) (define name (opt-lambda opt-formals body1 ... body2))])) (define-syntax define-optionals* (syntax-rules () [(_ (name . opt-formals) body1 ... body2) (define name (opt*-lambda opt-formals body1 ... body2))])))
It uses the following helper library:
(library (srfi :227 opt-lambda meta) (export make-opt-lambda-transformer rec) (import (rnrs (6))) (define-syntax rec (lambda (stx) (syntax-case stx () [(_ f e) #'(letrec ([f e]) f)]))) (define make-opt-lambda-transformer (lambda (who sequential?) (lambda (stx) (syntax-case stx () [(_ (formal ... . rest) body1 ... body2) (let*-values ([(var1* bdg*) (let f ([formal* #'(formal ...)]) (syntax-case formal* () [() (values '() '())] [(var formal ...) (identifier? #'var) (let-values ([(var1* bdg*) (f #'(formal ...))]) (values (cons #'var var1*) bdg*))] [_ (values '() formal*)]))] [(var2* init*) (let f ([bdg* bdg*]) (syntax-case bdg* () [() (values '() '())] [([var init] bdg ...) (identifier? #'var) (let-values ([(var2* init*) (f #'(bdg ...))]) (values (cons #'var var2*) (cons #'init init*)))] [_ (syntax-violation who "invalid bindings" stx bdg*)]))] [(tmp1*) (if sequential? var1* (generate-temporaries var1*))] [(tmp2*) (if sequential? var2* (generate-temporaries var2*))]) #`(rec f (case-lambda #,@(let f ([tmp1* tmp1*] [var1* var1*] [tmp2* tmp2*] [var2* var2*] [init* init*]) (if (null? var2*) (list #`[(#,@var1* . rest) body1 ... body2]) (cons #`[(#,@tmp1*) (f #,@tmp1* #,(car init*))] (f (append tmp1* (list (car tmp2*))) (append var1* (list (car var2*))) (cdr tmp2*) (cdr var2*) (cdr init*))))))))])))))
A portable implementation for R^{7}RS will be in the Git repository of this SRFI.
Syntax for procedures with optional arguments are not a new invention.
In particular, the let-optionals
syntax has seen widespread
use before.
Thanks to all participants of the mailing list of this SRFI, especially
to Daphne Preston-Kendal for her review of optionals and keywords,
examples, helpful comments and contributing
the R^{7}RS
implementation, and to Jakub
T. Jankiewicz for bringing up this topic.
© 2021 Marc Nieper-Wißkirchen, Daphne Preston-Kendal.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.