kw.ss: Keyword Arguments
To load: (require (lib "kw.ss"))
The kw.ss library provides the lambda/kw
and define/kw
forms.
(
SYNTAX
lambda/kw
formals body-expr ···1
)
Like lambda
, but with optional and keyword-based argument
processing. This form is similar to an extended version of Common
Lisp procedure arguments. When used with plain variable
names, lambda/kw
expands to a plain lambda
, so
lambda/kw
is suitable for a language module that will use it to
replace lambda
. This form uses MzScheme keyword values (see
section 3.8 in PLT MzScheme: Language Manual) for its implementation.
In addition to lambda/kw
, this library provides a
define/kw
form that is similar to the built-in define
(see section 2.8.1 in PLT MzScheme: Language Manual), except that the formals
as as
in lambda/kw
. Like define
, this form can be used with
nested parenthesis for curried functions (the MIT-style generalization
of section 2.8.1 in PLT MzScheme: Language Manual).
The syntax of lambda/kw
is the same as lambda
, except for the
list of formal argument specifications. These specifications can hold (zero or
more) plain argument names, then a default section that begins after an
#:optional marker, then a keyword section that is marked by
#:keyword, and finally a section holding rest and ``rest-like''
arguments which are described below, together with argument processing
flag directives. Each section is optional, but the order of the
sections must be as listed.
More formally, the syntax is:
(lambda/kw kw-formals body ...) kw-formals is one of variable (variable ··· [#:optional optional-spec ···] [#:key key-spec ···] [rest/mode-spec ···]) (variable ··· . variable) optional-spec is one of variable (variable default-expr) key-spec is one of variable (variable default-expr) (variable keyword default-expr) rest/mode-spec is one of #:rest variable #:other-keys variable #:other-keys+body variable #:all-keys variable #:body kw-formals #:allow-other-keys #:forbid-other-keys #:allow-duplicate-keys #:forbid-duplicate-keys #:allow-body #:forbid-body #:allow-anything #:forbid-anything
Of course, all bound identifier
s must be unique. The following
section describes each part of a kw-formals
.
23.1 Required Arguments
Required arguments correspond to identifier
s that appear before
any keyword marker in the argument list. They determine the minimum
arity of the resulting procedure.
23.2 Optional Arguments
The optional-arguments section follows an #:optional marker
in the kw-formals
. Each optional argument can take the form of a
parenthesized variable and a default expression; the latter is used
if a value is not given at the call site. The default expression can
be omitted (along with the parentheses), in which case #f
is
the default.
The default expression's environment includes all previous arguments,
both required and optional names. With k optionals after n
required arguments, and with no keyword arguments or rest-like
arguments, the resulting procedure has an arity '(n + k
... n + 1 n)
. Adding keywords or rest-like arguments
makes the first arity (make-arity-at-least n + k)
.
The treatment of optionals is efficient, with an important
implication: default expressions appear multiple times in the
resulting case-lambda
. For example, the default expression
for the last optional argument appears k - 1 times (but no
expression is ever evaluated more than in a function call). This
expansion risks exponential blow-up is if lambda/kw
is used
in a default expression of a lambda/kw
, etc. The bottom
line, however, is that lambda/kw
is a sensible choice, due to
its enhanced efficiency, even when you need only optional arguments.
23.3 Keyword Arguments
A keyword argument section is marked by a #:key. If it is used with optional arguments, then the keyword specifications must follow the optional arguments (which mirrors the use in call sites; it is not possible to specify keyword arguments in a call without giving values for all optional arguments first).
Like optional arguments, each keyword argument is specified as a
parenthesized variable name and a default expression. The default
expression can be omitted (with the parentheses), in which
case #f
is the default value. The keyword used at a call
site for the corresponding variable has the same name as the
variable; a third form of keyword arguments has three parts -- a
variable name, a keyword, and a default expression -- to allow the
name of the locally bound variable to differ from the keyword used at
call sites.
When calling a function with keyword arguments, the required argument (and all optional arguments, if specified) must be followed by an even number of arguments, where the first argument is a keyword that determines which variable should get the following value, etc. If the same keyword appears multiple times (and if multiple instances of the keyword are allowed; see section 23.6), the value after the first occurrence is used for the variable:
((lambda/kw (#:key x [y 2] [z #:zz 3] #:allow-duplicate-keys) (list
x y z)) #:x 'x #:zz 'z #:x "foo") =>'(x 2 z)
Default expressions are evaluated only for keyword arguments that do not receive a value for a particular call. Like optional arguments, each default expression is evaluated in an environment that includes all previous bindings (required, optional, and keywords that were specified on its left).
See section 23.6 for information on when duplicate or unknown keywords are allowed at a call site.
23.4 Rest and Rest-like Arguments
The last kw-formals
section -- after the required, optional, and
keyword arguments -- may contain specifications for rest-like
arguments and/or mode keywords. Up to five rest-like arguments can
be declared, each with a variable
to bind:
#:rest -- the variable is bound to the list of ``rest'' arguments, which is the list of all values after the required and the optional values. This list includes all keyword-value pairs, exactly as they are specified at the call site.
Scheme's usual dot-notation is accepted in
kw-formals
only if no other meta-keywords are specified, since it is not clear whether it should specify the same binding as a #:rest or as a #:body. The dot notation is allowed without meta-keywords to make thelambda/kw
syntax compatible withlambda
.#:body -- the variable is bound to all arguments after keyword-value pairs. (This is different from Common Lisp's
&body
, which is a synonym for&rest
.) More generally, a #:body specification can be followed by anotherkw-formals
, not just a singlevariable
; see section 23.5 for more information.#:all-keys -- the variable is bound to the list of all keyword-values from the call site, which is always a proper prefix of a #:rest argument. (If no #:body arguments are declared, then #:all-keys binds the same as #:rest.) See also
keyword-get
in section 23.7.#:other-keys -- the variable is bound like an #:all-keys variable, except that all keywords specified in the
kw-formals
are removed from the list. When a keyword is used multiple times at a call cite (and this is allowed), only the first instances is removed for the #:other-keys binding.#:other-keys+body -- the variable is bound like a #:rest variable, except that all keywords specified in the
kw-formals
are removed from the list. When a keyword is used multiple times at a call site (and this is allowed), only the first instance us removed for the #:other-keys+body binding. (When no #:body variables are specified, then #:other-keys+body is the same as #:other-keys.)
In the following example, all rest-like arguments are used and have different bindings:
((lambda/kw (#:key x y
#:rest r
#:other-keys+body rk
#:all-keys ak
#:other-keys ok
#:body b)
(list
r rk b ak ok))
#:z 1 #:x 2 2 3 4)
=>
'((#:z 1 #:x 2 2 3 4)
(#:z 1 2 3 4)
(2 3 4)
(#:z 1 #:x 2)
(#:z 1))
Note that the following invariants always hold:
rest
=(append
all-keys
body
)other-keys+body
=(append
other-keys
body
)
To write a function that uses a few keyword argument values, and that
also calls another function with the same list of arguments
(including all keywords), use #:other-keys (or
#:other-keys+body). The Common Lisp approach is to specify
:allow-other-keys
, so that the second function call will not
cause an error due to unknown keywords, but the
:allow-other-keys
approach risks confusing the two layers of
keywords.
23.5 Body Argument
The most notable divergence from Common Lisp in lambda/kw
is
the #:body argument, and the fact that it is possible at a
call site to pass plain values after keyword-value pairs. The
#:body binding is useful for function calls that use
keyword-value pairs as sort of an attribute list before the actual
arguments to the function. For example, consider a function that
accepts any number of numeric arguments and will apply a function to
them, but the function can be specified as an optional keyword
argument. It is easily implemented with a #:body argument:
(define/kw (mathop #:key [op +] #:body b) (apply
op b)) (mathop 1 2 3) ; =>6
(mathop #:op max 1 2 3) ; =>3
(Note that the first body value cannot itself be a keyword.)
A #:body declaration works as an arbitrary kw-formals
,
not just a single variable like b
in the above example. For
example, to make the above mathop
work only on three
arguments that follow the keyword, use (x y z)
instead
of b
:
(define/kw (mathop #:key [op +] #:body (x y z)) (op x y z))
In general, #:body handling is compiled to a sub procedure
using lambda/kw
, so that a procedure can use more then one
level of keyword arguments. For example:
(define/kw (mathop #:key [op +] #:body (x y z #:key [convertvalues
])) (op (convert x) (convert y) (convert z))) (mathop #:op * 2 4 6 #:convertexact->inexact
) --> 48.0
Obviously, nested keyword arguments works only when non-keyword arguments separate the sets.
Run-time errors during such calls report a mismatch for a function
with a name that is based on the original name plus a ~body
suffix:
(mathop #:op * 2 4) => procedure mathop body: expects at least 3 arguments, given 2: 2 4
23.6 Mode Keywords
Finally, the argument list of a lambda/kw
can contain keywords that
serve as mode flags to control error reporting.
#:allow-other-keys -- the keyword-value sequence at the call site can include keywords that are not listed in the keyword part of the
lambda/kw
form.#:forbid-other-keys -- the keyword-value sequence at the call site cannot include keywords that are not listed in the keyword part of the
lambda/kw
form, otherwiseexn:fail:contract
exception is raised.#:allow-duplicate-keys -- the keyword-value list at the call site can include duplicate values associated with same keyword, the first one is used.
#:forbid-duplicate-keys -- the keyword-value list at the call site cannot include duplicate values for keywords, otherwise
exn:fail:contract
exception is raised. This restriction applies only to keywords that are listed in the keyword part of thelambda/kw
form -- if other keys are allowed, this restriction does not apply to them.#:allow-body -- body arguments can be specified at the call site after all keyword-value pairs.
#:forbid-body -- body arguments cannot be specified at the call site after all keyword-value pairs.
#:allow-anything -- allows all of the above, and treat a single keyword at the end of an argument list as a #:body, a situation that is usually an error. When this is used and no rest-like arguments are used except #:rest, an extra loop is saved and calling the functions is faster (around 20%).
#:forbid-anything -- forbids all of the above, ensuring that calls are as restricted as possible.
These mode markers are rarely needed, because the default modes are determined by the declared rest-like arguments:
The default is to allow other keys if a #:rest, #:other-keys+body, #:all-keys, or #:other-keys variable is declared (and an #:other-keys declaration requires allowing other keys).
The default is to allow duplicate keys if a #:rest or #:all-keys variable is declared;
The default is to allow body arguments if a #:rest, #:body, or #:other-keys+body variable is declared (and a #:body argument requires allowing them).
Here's an alternate specification, which maps rest-like arguments to the behavior that they imply:
#:rest: everything is allowed (a body, other keys, and duplicate keys);
#:other-keys+body: other keys and body are allowed, but duplicates are not;
#:all-keys: other keys and duplicate keys are allowed, but a body is not;
#:other-keys: other keys must be allowed (on by default, cannot use with #:forbid-other-keys), and duplicate keys and body are not allowed;
#:body: body must be allowed (on by default, cannot use with #:forbid-body) and other keys and duplicate keys and body are not allowed;
Except for the previous two ``must''s, defaults can be overridden by an explicit #:allow-... or a #:forbid-... mode.
23.7 Property Lists
(keyword-get
args keyword
[not-found-thunk
])
PROCEDURE
Searches a list of keyword arguments (a ``property list'' or ``plist''
in Lisp jargon) for the given keyword, and returns the associated
value. It is the facility that is used by lambda/kw
to
search for keyword values.
The args
list is scanned from left to right, if the keyword is
found, then the next value is returned. If the keyword
was not
found, then the not-found-thunk
value is used to produce a
value by applying it. If the keyword
was not found,
and not-found-thunk
is not given, #f
is returned.
(No exception is raised if the args
list is imbalanced, and the
search stops at a non-keyword value.)