[R6RS] Named optional parameter proposal
Marc Feeley
feeley
Tue May 24 21:50:32 EDT 2005
Scheme needs a standard convention for specifying named optional
parameters. Named optional parameters are needed for APIs with many
parameters which have default values if they are not specified
explicitly. Here are some concrete examples where named optional
parameters are useful:
1) Hash table constructor. In this example the named optional
parameters come after the positional optional parameters (here the
size of the hash table):
(make-hash-table 1000 test: equal? hash: my-hash weak-values: #t)
2) HTML code generator. Here "font" is a procedure taking named
optional parameters (the parameters of the <font> HTML tag). This
procedure returns a n-ary procedure wrapping its arguments in a
<font ...> ... </font>.
([font color: "red" size: "10"] "THIS TEXT IS IN A BIG RED FONT")
3) File opening procedure accepting various file opening settings
(direction, character encoding, end-of-line encoding, etc):
(open-file '(path: "foo.txt" char-encoding: utf8 direction: input))
Note that in this example the "parameters" of open-file are
received as a list of settings. This approach allows a more
consistent API with the with-input-from-file and
with-output-to-file procedures:
(with-input-from-file "foo.txt" read)
(with-input-from-file '(path: "foo.txt" char-encoding: utf8) read)
In other words, where a file name is expected a list of settings
can be specified instead.
4) Records with many fields. Here, named optional parameters
may be useful for the record definition form and for
record constructors:
(define-record graphic-context
(bgcolor default: "white")
(fgcolor default: "black")
(pattern default: 'solid)
(font default: "times")
... ; and many more
)
(make-graphic-context
pattern: 'stipple
font: "helvetica"
fgcolor: "red")
My main motivation for promoting the adoption of a standard convention
for specifying named optional parameters is that some of the additions
to Scheme we are considering (hash-tables, I/O, and records) can have
a cleaner API when named optional parameters are used.
I'm partial to the syntax used in my examples which uses a
keyword/value pair for each named parameter, where the keyword is a
new type of object with a syntax similar to symbols but ending with a
colon and which is self-evaluating. Note that several implementations
of Scheme support such keyword objects (Gambit, Kawa, Bigloo, Gauche,
STKlos, Jade, RScheme, Guile, Chicken, EdScheme), but the lexical
syntax is not consistent across all these implementations (some use a
colon prefix, some a colon suffix, some allow both, some allow
either). The lexical syntax I suggest and which most of the above
implementations support is the one specified in the DSSSL standard.
Note that the adoption of a standard convention for specifying named
optional parameters compatible with DSSSL (and the keyword type) does
not imply that we have to adopt the DSSSL syntax for extended lambda
expressions (with #!optional, #!key and #!rest parts) because named
optional parameters can be implemented on top of rest parameters.
Nevertheless, I believe that DSSSL's extended lambda is an acceptable
approach and it has the advantage of being used in the DSSSL community
and is supported by some implementations of Scheme (Gambit, Bigloo,
Kawa, Guile).
At the Boston meeting I will put these motions up for a vote:
1) To use named optional parameters in the APIs of standard R6RS
procedures and special forms where it is appropriate. [I
understand that this is a vague motion because appropriateness
is rather subjective...]
2) To add keyword objects. Keywords have the following operations:
(string->keyword "foo") => foo: (or whatever the lexical
syntax is)
(keyword->string 'foo:) => "foo"
(keyword? 'foo:) => #t
(symbol? 'foo:) => #f
3) To adopt the colon suffix lexical syntax of DSSSL. This implies that
the lexical syntax of symbols has to be changed so that symbols
cannot contain a trailing colon. Note that (string->symbol "foo:")
is
still valid, but the external representation of the resulting symbol
would have to use an escape notation, such as |foo:|. Note also that
(string->keyword "a,b") would also have to use an escape notation,
such as |a,b|: (note that the colon is after the closing vertical
bar).
4) To have keywords be self evaluating.
I may also put the following motion up for vote if the discussion
shows that we are ready to vote:
5) To add DSSSL's extended lambda whose specification is attached below.
Marc
DSSSL extended lambda:
(lambda LAMBDA-FORMALS BODY)
(define (VARIABLE DEFINE-FORMALS) BODY)
lambda-formals = `(' FORMAL-ARGUMENT-LIST `)' |
R5RS-LAMBDA-FORMALS
define-formals = FORMAL-ARGUMENT-LIST | R5RS-DEFINE-FORMALS
formal-argument-list = REQS OPTS REST KEYS
reqs = REQUIRED-FORMAL-ARGUMENT*
required-formal-argument = VARIABLE
opts = `#!optional' OPTIONAL-FORMAL-ARGUMENT* | EMPTY
optional-formal-argument = VARIABLE | `(' VARIABLE INITIALIZER
`)'
rest = `#!rest' REST-FORMAL-ARGUMENT | EMPTY
rest-formal-argument = VARIABLE
keys = `#!key' KEYWORD-FORMAL-ARGUMENT* | EMPTY
keyword-formal-argument = VARIABLE | `(' VARIABLE INITIALIZER `)'
initializer = EXPRESSION
r5rs-lambda-formals = `(' VARIABLE* `)'
| `(' VARIABLE+ `.' VARIABLE `)'
| VARIABLE
r5rs-define-formals = VARIABLE* | VARIABLE* `.' VARIABLE
When the procedure introduced by a `lambda' (or `define') is
applied to a list of actual arguments, the formal and actual
arguments are processed as specified in the R5RS if the
LAMBDA-FORMALS (or DEFINE-FORMALS) is a R5RS-LAMBDA-FORMALS (or
R5RS-DEFINE-FORMALS), otherwise they are processed as specified in
the DSSSL language standard:
a. VARIABLEs in REQUIRED-FORMAL-ARGUMENTs are bound to
successive actual arguments starting with the first actual
argument. It shall be an error if there are fewer actual
arguments than REQUIRED-FORMAL-ARGUMENTs.
b. Next VARIABLEs in OPTIONAL-FORMAL-ARGUMENTs are bound to
remaining actual arguments. If there are fewer remaining
actual arguments than OPTIONAL-FORMAL-ARGUMENTs, then the
variables are bound to the result of evaluating INITIALIZER,
if one was specified, and otherwise to `#f'. The INITIALIZER
is evaluated in an environment in which all previous formal
arguments have been bound.
c. If there is a REST-FORMAL-ARGUMENT, then it is bound to a
list of all remaining actual arguments. These remaining
actual arguments are also eligible to be bound to
KEYWORD-FORMAL-ARGUMENTs. If there is no
REST-FORMAL-ARGUMENT and there are no
KEYWORD-FORMAL-ARGUMENTs, then it shall be an error if there
are any remaining actual arguments.
d. If `#!key' was specified in the FORMAL-ARGUMENT-LIST, there
shall be an even number of remaining actual arguments. These
are interpreted as a series of pairs, where the first member
of each pair is a keyword specifying the argument name, and
the second is the corresponding value. It shall be an error
if the first member of a pair is not a keyword. It shall be
an error if the argument name is not the same as a variable
in a KEYWORD-FORMAL-ARGUMENT, unless there is a
REST-FORMAL-ARGUMENT. If the same argument name occurs more
than once in the list of actual arguments, then the first
value is used. If there is no actual argument for a
particular KEYWORD-FORMAL-ARGUMENT, then the variable is
bound to the result of evaluating INITIALIZER if one was
specified, and otherwise to `#f'. The INITIALIZER is
evaluated in an environment in which all previous formal
arguments have been bound.
It shall be an error for a VARIABLE to appear more than once in a
FORMAL-ARGUMENT-LIST.
It is unspecified whether variables receive their value by binding
or by assignment. This can lead to different semantics if
`call-with-current-continuation' is used in an INITIALIZER. Note
that this is irrelevant for DSSSL programs because
`call-with-current-continuation' does not exist in DSSSL.
For example:
> ((lambda (#!rest x) x) 1 2 3)
(1 2 3)
> (define (f a #!optional b) (list a b))
> (define (g a #!optional (b a) #!key (c (* a b))) (list a b c))
> (define (h a #!rest b #!key c) (list a b c))
> (f 1)
(1 #f)
> (f 1 2)
(1 2)
> (g 3)
(3 3 9)
> (g 3 4)
(3 4 12)
> (g 3 4 c: 5)
(3 4 5)
> (g 3 4 c: 5 c: 6)
(3 4 5)
> (h 7)
(7 () #f)
> (h 7 c: 8)
(7 (c: 8) 8)
> (h 7 c: 8 z: 9)
(7 (c: 8 z: 9) 8)
More information about the R6RS
mailing list