[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