227: Optional Arguments

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

Status

This SRFI is currently in draft 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.

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

Issues

None at present.

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)

Syntax

(opt-lambda opt-formals body)

Syntax: Opt-formals is either of the form (variable1variablen binding1bindingm) or (variable1variablen binding1bindingm . 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 variables.

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 variables 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 inits. The corresponding inits 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 inits 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.

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

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

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


Editor: Arthur A. Gleckler