## Title

Optional Arguments

## Author

Marc Nieper-Wißkirchen (spec and R6RS implementation)
Daphne Preston-Kendal (R7RS implementation)

## Status

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.

- Received: 2021-08-24
- Draft #1 published: 2021-08-25
- Draft #2 published: 2021-11-07
- Finalized: 2021-11-16

## Abstract

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*`.

## Rationale

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.

## Specification

### Examples

    (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 ())

### Syntax

`(opt-lambda opt-formals body)`

  Syntax: `Opt-formals` is either of the form `(variable1 … variablen binding1 … bindingm)` or `(variable1 … variablen binding1 … bindingm . variable)`, where each `bindingi` has the form `(variablen + 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)`syntax

  Similar 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)`syntax

  Syntax: `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)`syntax

  Similar 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 R6RS systems) and by the `(srfi 227 definition)` sublibrary (for R7RS systems).

`(define-optionals (identifier . opt-formals) body)`syntax

  Syntax: `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)`syntax

  Syntax: `Opt-formals` is as in a `opt-lambda` expression.

  Semantics: Operationally equivalent to `(define identifier (opt*-lambda opt-formals body))`

## Implementation

A portable implementation for R6RS 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 R7RS will be in the Git repository of this SRFI.

## Acknowledgements

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 `R7RS` implementation, and to Jakub T. Jankiewicz for bringing up this topic.

## Copyright

© 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.