Search code examples
listargumentsschemeracketvariadic-functions

How do I handle a variable number of arguments passed to a function in Racket?


I like creating functions which take an unlimited number of arguments, and being able to deal with them as a list. It's been useful to me when creating binary trees & I'm using it for a variation on the nearest-neighbor algorithm right now. My method, however, is really horrible: since I can't think of a way to iterate over an improper list (which may well be improper & degenerate), I tried using various list functions to force the improper list into list form.

This is my best attempt in a simple function to determine difference between map-nodes (works, just not sure why it works):

(define distance-between
  (lambda xs
    (let ([input-list (list* xs null)])
      (letrec 
          ([f (lambda (xs acc)
               (if (null? (cdr xs))
                   acc
                   (f (cdr xs) 
                      (+ (abs (- (map-node-x (car xs)) 
                                 (map-node-x (cadr xs))))
                         (abs (- (map-node-y (car xs)) 
                                 (map-node-y (cadr xs))))
                         acc))))])                   
       (f (car input-list) 0)))))

As you can see, it's an ugly solution and involves some of what seems like magic to me - why is the improper list coerced into list form when I include it in a list*? (note: this sentence is misleading, this does not occur).

I'd rather have a pretty solution and no magic. Can anyone help?

For example a typical input would be:

(distance-between (map-node 1 2) (map-node 2 3) (map-node 3 4))

with the expected result:

4

(a distance of 2 between map-node (a) and m-n (b), plus a distance of 2 between map-node (b) and map-node (c)).

Alternatively one might simply input:

(distance-between (map-node 1 2) (map-node 2 2))

and get an answer of:

1

If I attempted this on the raw input, without my (let ([input-list...])...) statement, it would cause an error as (? not actually sure why given response to this question).

The function works as expected.


Solution

  • There's nothing improper about the list received as a variadic argument list (meaning: variable number of arguments). For example:

    (define test-list
      (lambda xs
        (length xs))) ; xs is a normal list, use it like any other list
    
    (test-list 1 2 3 4)
    => 4
    

    In the above example, the xs parameter is a normal, plain, vanilla list, there's nothing improper about it. You can iterate over it as you would over any other list. There's no need to car it, it's already a list! Also, notice that the same function can be written like this:

    (define (test-list . xs)
      (length xs))   ; xs is a normal list, use it like any other list
    

    Just for reference: an improper list is one that does not end with the null list. For example: '(1 2 3 . 4). Again, that's not how a variadic argument list looks.