------------------------------------------------------------------------------
MC logo
Variable-Length Parameter Lists
[^] Tom's Lisp
------------------------------------------------------------------------------
[Basic Input Format] [Lists, Pairs and Related Operations] [Conditional Evaluation] [Basic Function Definition] [Definitions and Scope] [Functions Which Take Functions] [String Functions] [Exception Handling] [Quoting And Evaluation] [Variable-Length Parameter Lists] [Macro Definitions] [Printing] [The Tomslsp command and its switches] [Index of Standard Functions]

When a function is defined, the parameter list specifies the number of arguments it requires. Tom's Lisp enforces this rule.

lsp>(define (fred x y) (* x (+ 2 y)))
fred
lsp>(fred 4 5)
28
lsp>(fred 3)
**** Error 4: Missing argument. ****
   (fred 3)
lsp>(fred 4 5 7)
**** Error 6: Too many arguments ****
   (fred 4 5 7)

Occasionally, however, the ability to create functions which can take any number of arguments is useful. Tom's Lisp does this when the argument list is given as a single identifier instead of a list of identifiers. When the function is called, any number of arguments may be sent, and the list of them is bound to the given identifier. For instance, here is a function which takes the maximum of any number of arguments:

; Return the maximum item in the argument list.
(setq max (lambda nums
    ; Is there a single item in the argument list?
    (if (null? (cdr nums))
        ; A single item is the max.
        (car nums)

        ; Compute the max of a larger list.
        (let
            ; Find the max of the args after the first.
            ((themax (eval (cons 'max (cdr nums)))))

            ; Return the larger of the first argument and the 
            ; max of the rest.
            (if (> (car nums) themax) (car nums) themax)
        )
    )
))
Notice that you cannot create variable-argument functions with the usual define syntax; you must use lambda and bind the results with setq (or the setq-equivalent form of define). The function does work:
lsp>(max 7 5 9 11)
11
lsp>(max 2 9 8 46 3 34 12)
46
lsp>(max 3)
3
The function is complicated by the difficulty of making the recursive call. The arguments are delivered in a single list, but must be sent as separate arguments in the call. A plain (max (cdr nums)) will not work, since that sends a list of numbers, not a series of numbers as separate arguments. We use (eval (cons max (cdr nums))') to accomplish this.

Here's another way to do it:

; Return the maximum item in the argument list.
(setq max 
    ; Enter a scope for creating a helper function.
    (scope
        ; Find the maximum value of a plain list.
        (define (rmax lis)
            ; Check for singleton list.
            (if (null? (cdr lis)) 
                ; A single item is the max.
                (car lis)

                ; Compute the max of a larger list.
                (let 
                    ; Find the max of the args after the first.
                    ((themax (rmax (cdr lis))))

                    ; Return the larger of the first argument and the 
                    ; max of the rest.
                    (if (> (car lis) themax) (car lis) themax)
                )
            )
        )

        ; Create the variable-parm function that simply sends
        ; its argument list to the ordinary max function.
        (lambda parms (rmax parms))
    )
)
This uses a scope to create a private helper function function, which accepts a list and does a normal recursion. The main function simply sends the parameter list to this ordinary one-parameter function.
lsp>(max 3 1 99 8)
99
lsp>(max 3 1)
3
lsp>(max 7 6 2 4)
7

There's one other thing: this version still dies badly when called with no arguments:

lsp>(max) 
**** Error 5: cdr: Bad arg type. ****
   (max)
   (rmax nil)
   (if (null? (cdr lis)) (car lis) (let ((themax (rmax (cdr lis)))) (if (> (car lis) themax) (car lis) themax)))
   (cond ((null? (cdr lis)) (car lis)) (#t (let ((themax (rmax (cdr lis)))) (if (> (car lis) themax) (car lis) themax))))
   (null? (cdr lis))
   (cdr nil)
This function dies when basis case test, trying to identify the singleton list, takes the car of the empty list. We can at least improve the error message:
; Return the maximum item in the argument list.
(setq max 
    ; Enter a scope for creating a helper function.
    (scope
        ; Find the maximum value of a plain list.
        (define (rmax lis)
            ; Check for singleton list.
            (if (null? (cdr lis)) 
                ; A single item is the max.
                (car lis)

                ; Compute the max of a larger list.
                (let 
                    ; Find the max of the args after the first.
                    ((themax (rmax (cdr lis))))

                    ; Return the larger of the first argument and the 
                    ; max of the rest.
                    (if (> (car lis) themax) (car lis) themax)
                )
            )
        )

        ; Create the variable-parm function that simply sends
        ; its argument list to the ordinary max function.
        (lambda parms 
            (if (null? parms)
                (error ERR_SHORTARG "Max called with zero args.")
                (rmax parms)
            )
        )
    )
)
For which:
lsp>(max 3 1)
3
lsp>(max)
**** Error 4: Max called with zero args. ****
   (max)
   (if (null? parms) (error ERR_SHORTARG "Max called with zero args.") (rmax parms))
   (cond ((null? parms) (error ERR_SHORTARG "Max called with zero args.")) (#t (rmax parms)))
   (error 4 "Max called with zero args.")
Here we use the code for too few parameters, but give the more specific message.