Up till now all functions have had a specified number of named arguments. We will now introduce a syntax for defining variadic functions, which may take a fixed number of named arguments and a variable number of additional arguments which are collected into a named list.
The argument declarations for variadic functions are improper lists:
| λ-syntax | Combined DEFINE |
|
|---|---|---|
| 3 args | (LAMBDA (arg1 arg2 arg3) body...) |
(DEFINE (name arg1 arg2 arg3) body...) |
| ≥2 args | (LAMBDA (arg1 arg2 . rest) body...) |
(DEFINE (name arg1 arg2 . rest) body...) |
| ≥1 args | (LAMBDA (arg1 . rest) body...) |
(DEFINE (name arg1 . rest) body...) |
| ≥0 args | (LAMBDA args body...) |
(DEFINE (name . args) body...) |
In the definitions above, the parameters are bound as follows:
| Definition | (f 1 2 3) |
||
|---|---|---|---|
Value of a |
Value of b |
Value of c |
|
(DEFINE (f a b c) body...) |
1 |
2 |
3 |
(DEFINE (f a b . c) body...) |
1 |
2 |
(3) |
(DEFINE (f a . b) body...) |
1 |
(2 3) |
|
(DEFINE (f . a) body...) |
(1 2 3) |
||
All that is required is a small modification to
make_closure to accept the declaration:
// make_closure returns an Atom on the stack.
// a closure is a list that binds the environment and arguments.
// note that result may not be updated if there are errors.
func make_closure(env, args, body Atom, result *Atom) error {
// verify number and type of arguments
if !listp(body) {
return Error_Syntax
}
// verify arguments.
// if we find a symbol, stop checking.
// if we find something that is not a pair with a symbol in the cdr, return an error.
for p := args; !nilp(p) && p._type != AtomType_Symbol; p = cdr(p) {
if p._type != AtomType_Pair || car(p)._type != AtomType_Symbol {
return Error_Type
}
}
// bind the environment and arguments to the closure
*result = Atom{
_type: AtomType_Closure,
value: AtomValue{
pair: &Pair{
car: env,
cdr: cons(args, body),
},
},
}
return nil
}
And another to apply to bind the additional arguments
into a list:
// apply calls a native function with a list of arguments and updates the result.
// note that the result may not be updated if we find errors.
func apply(fn, args Atom, result *Atom) error {
.
.
.
// handle closure
if fn._type == AtomType_Closure {
// create a new environment for the closure
env := env_create(car(fn))
// bind the arguments
for arg_names := car(cdr(fn)); !nilp(arg_names); arg_names = cdr(arg_names) {
// if arg name is a symbol, apply it as rest of arguments
if arg_names._type == AtomType_Symbol {
_ = env_set(env, arg_names, args)
args = _nil
break
}
.
.
.
}
.
.
.
}
.
.
.
}
A boring example:
| ID | Input | Output |
|---|---|---|
| 1 | ((lambda (a . b) a) 1 2 3) | 1 |
| 2 | ((lambda (a . b) b) 1 2 3) | (2 3) |
| 3 | ((lambda args args) 1 2 3) | (1 2 3) |
We can also create a variadic adder:
| ID | Input | Output |
|---|---|---|
| 4 | (define (sum-list xs)(if xs(+ (car xs) (sum-list (cdr xs)))0)) | SUM-LIST |
| 5 | (sum-list '(1 2 3)) | 6 |
| 6 | (define (add . xs) (sum-list xs)) | ADD |
| 7 | (add 1 2 3) | 6 |
| 8 | (add 1 (- 4 2) (/ 9 3)) | 6 |
Since you can always pass a list to a regular function, this is really just another kind of syntactic sugar.
func TestChapter10(t *testing.T) {
env := env_create_default()
for _, tc := range []struct {
id int
input string
expect string
err error
}{
{id: 1, input: "((lambda (a . b) a) 1 2 3)", expect: "1"},
{id: 2, input: "((lambda (a . b) b) 1 2 3)", expect: "(2 3)"},
{id: 3, input: "((lambda args args) 1 2 3)", expect: "(1 2 3)"},
{id: 4, input: "(define (sum-list xs) (if xs (+ (car xs) (sum-list (cdr xs))) 0))", expect: "SUM-LIST"},
{id: 5, input: "(sum-list '(1 2 3))", expect: "6"},
{id: 6, input: "(define (add . xs) (sum-list xs))", expect: "ADD"},
{id: 7, input: "(add 1 2 3)", expect: "6"},
{id: 8, input: "(add 1 (- 4 2) (/ 9 3))", expect: "6"},
} {
var expr Atom
_, err := read_expr([]byte(tc.input), &expr)
if err != nil {
t.Errorf("%d: read error: want nil: got %v\n", tc.id, err)
continue
}
var result Atom
err = eval_expr(expr, env, &result)
if tc.err == nil && err == nil {
// yay
} else if tc.err == nil && err != nil {
t.Errorf("%d: error: want nil: got %v\n", tc.id, err)
} else if !errors.Is(err, tc.err) {
t.Errorf("%d: error: want %v: got %v\n", tc.id, tc.err, err)
}
if got := result.String(); tc.expect != got {
t.Errorf("%d: eval: want %q: got %q\n", tc.id, tc.expect, got)
}
}
}