Lambda Expressions and Formal Parameters

A lambda expression evaluates to a procedure. The environment in effect when the lambda expression was evaluated is remembered as part of the procedure. When the procedure is later called with some actual arguments, the environment in which the lambda expression was evaluated will be extended by binding the variables in the formal argument list to fresh locations, and the corresponding actual argument values will be stored in those locations. (A fresh location is one that is distinct from every previously existing location.) Next, the expressions in the body of the lambda expression will be evaluated sequentially in the extended environment. The results of the last expression in the body will be returned as the results of the procedure call.

(lambda (x) (+ x x))   ⇒ a procedure
((lambda (x) (+ x x)) 4)  ⇒ 8

(define reverse-subtract
  (lambda (x y) (- y x)))
(reverse-subtract 7 10) ⇒ 3

(define add4
  (let ((x 4))
   (lambda (y) (+ x y))))
(add4 6) ⇒ 10

The formal arguments list of a lambda expression has some extensions over standard Scheme: Kawa borrows the extended formal argument list of DSSSL, and allows you to declare the type of the parameter. More generally, you can use patterns.

lambda-expression ::= (lambda formals option-pair* opt-return-type body)
opt-return-type ::= [:: type]
formals ::= (formal-arguments) | rest-arg

An opt-return-type specifies the return type of the procedure: The result of evaluating the body is coerced to the specified type.

Deprecated: If the first form of the function body is an unbound identifier of the form <TYPE> (that is the first character is ‘<’ and the last is ‘>’), then that is another way to specify the function’s return type.

See properties for how to set and use an option-pair.

The define form has a short-hand that combines a lambda definition with binding the lambda to a variable:

(define (name formal-arguments) opt-return-type body)

formal-arguments ::= required-or-guard* [#!optional optional-arg ...] rest-key-args
rest-key-args ::= [#!rest rest-arg] [#!key key-arg ...] [guard]
  | [#!key key-arg ...] [rest-parameter] [guard]
  | . rest-arg

When the procedure is applied to an argument list, the latter is matched against formal parameters. This may involve some complex rules and pattern matching.

Required parameters

required-or-guard ::= required-arg | guard
required-arg ::= pattern
  | ( pattern :: type)

The required-args are matched against the actual (pre-keyword) arguments in order, starting with the first actual argument. It is an error if there are fewer pre-keyword arguments then there are required-args. While a pattern is most commonly an identifier, more complicated patterns are possible, thus more (or fewer) variables may be bound than there are arguments.

Note a pattern may include an opt-type-specifier. For example:

(define (isquare x::integer)
  (* x x))

In this case the actual argument is coerced to an integer and then the result matched against the pattern x. This is how parameter types are specified.

The pattern may be enclosed in parentheses for clarify (just like for optional parameters), but in that case the type specifier is required to avoid ambiguity.

Optional parameters

optional-arg ::= variable opt-type-specifier
  | ( pattern opt-type-specifier [initializer [supplied-var]])
supplied-var ::= variable

Next the optional-args are bound to remaining pre-keyword arguments. If there are fewer remaining pre-keyword arguments than there are optional-args, then the remaining variables are bound to the corresponding initializer. If no initializer was specified, it defaults to #f. (TODO: If a type is specified the default for initializer is the default value of the type.) The initializer is evaluated in an environment in which all the previous formal parameters have been bound. If a supplied-var is specified, it has type boolean, and is set to true if there was an actual corresponding argument, and false if the initializer was evaluated.

Keyword parameters

key-arg ::= variable opt-type-specifier
    | ( variable opt-type-specifier [initializer [supplied-var]] )

Keyword parameters follow #!key. For each variable if there is an actual keyword parameter whose keyword matches variable, then variable is bound to the corresponding value. If there is no matching artual argument, then the initializer is evaluated and bound to the argument. If initializer is not specified, it defaults to #f. The initializer is evaluated in an environment in which all the previous formal parameters have been bound.

(define (fun x #!key (foo 1) (bar 2) (baz 3))
  (list x foo bar baz))
(fun 9 baz: 10 foo: 11) ⇒ (9 11 2 10)

The following cause a match failure, unless there is a rest parameter:

It is not recommended to use both keyword parameters and a rest parameter that can match keyword arguments. Currently, the rest parameter will include any arguments that match the explicit keyword parameters, as well any that don’t, though this may change.

On the other hand, it is fine to have both keyword parameters and a rest parameter does not accept keywords. In that case the rest parameter will match any “postfix” arguments:

#|kawa:8|# (define (fun x #!key k1 k2 #!rest r)
  (format "x:~w k1:~w k2:~w r:~w" x k1 k2 r))
(fun 3 k2: 12 100 101) ⇒ x:3 k1:#f k2:12 r:(100 101)

The supplied-var argument is as for optional arguments.

Performance note: Keyword parameters are implemented very efficiently and compactly when explicit in the code. The parameters are sorted by the compiler, and the actual keyword arguemnts at the call state are also sorted at compile-time. So keyword matching just requires a fast linear scan comparing the two sorted lists. This implementation is also very compact, compared to say a hash table.

If a type is specified, the corresponding actual argument (or the initializer default value) is coerced to the specified type. In the function body, the parameter has the specified type.

Rest parameters

A “rest parameter” matches any arguments not matched by other parameters. You can write it using any of the following ways:

rest-parameter ::=
  #!rest rest-arg [:: type]
  | @rest-arg
  | @:rest-arg
rest-arg ::= variable

In addition, if formals is just a rest-arg identifier, or a formal-arguments ends with . rest-arg (i.e. is a dotted list) that is equivalent to using #!rest.

These forms are similar but differ in the type of the rest-arg and whether keywords are allowed (as part of the rest-arg):

Guards (conditional expressions)

A guard is evaluated when it appears in the formal parameter list. If it evaluates to false, then matching fails. Guards can appears before or after required arguments, or at the very end, after all other formal parameters.