I thought I'd briefly describe how one can define Script-fu procedures which accept a variable number of arguments and how those arguments are handled.
There are two basic ways of defining a new procedure in Scheme:
(define (somefunction x y z) ... )
(define somefunc (lambda (x y z) ... ) )
The two forms are effectively equivalent; in both cases a lambda closure is created and then bound to a symbol. In fact the former is generally considered merely syntactic sugar for the latter. Nonetheless, there are potential advantages to the second ("lambda") form in certain situations.
One advantage obtains from the fact that operations can be inserted before the lambda is created. For example, one might check to see if a desired dependency is available.
(define somefunc (let ((align-down-missing? (zero? (car (gimp-procedural-db-proc-exists "script-fu-sg-align-down" ))))) (lambda (x y z) ... ) ) )
The test for the existence of the Align Down script only has to be performed once when the function is first defined. Only the lambda part will be executed when 'somefunc' is invoked, yet the variable representing the missing status will be retained forever. In Scheme terminology, the 'align-down-missing?' binding exists in the environment captured by the lambda (the environment which existed at the time the lambda was created), but not in the global environment. Of course, one could assign 'align-down-missing?' as a variable in the global environment (by definining it outside the 'somefunc' definition), but global variables tend to clutter things up and can introduce potential problems with namespace conflicts. There are other interesting aspects of this "capturing" of environments by a lambda binding, but I will leave those for a later discussion.
The main point I wish to cover is passing a variable number of arguments to procedures. This is implemented in Scheme by using a notation rather similar to that of dotted pairs. For example, if we have the definition
(define (somefunc . z) ... )
then within the definition, the variable 'z' will contain a list of all the arguments that were passed to the procedure. For example, if you invoked the above using "(somefunc 1 2 3 4)" then the variable 'z' would be assigned the list '(1 2 3 4). You can access individual elements of that list using the traditional 'car' and 'cdr' functions.
If you included parameters before the dot, for example,
(define (somefunc x y . z) ... )
These "explicit" parameters would be assigned their passed values and the rest of the arguments would be assigned (as a list) to 'z'. If you only provide the explicit arguments 'x' and 'y' then the list 'z' will be empty. Note that when calling your procedure, you must provide all explicit arguments.
It should be noted that while the first example above works fine for defining a procedure with no explicit arguments, this notation does not work in Scheme when the lambda form is employed. In other words, the following code will NOT work:
(define somefunc (lambda ( . z) ... ) )
Instead, to employ the lambda form with no explicit arguments, omit the parentheses and period altogether, leaving only the identifier to which the list of arguments is assigned.
(define somefunc (lambda z ... ) )
Variable arguments can provide a nice way of expanding the functionality of a procedure without increasing the complexity of the default behavior. Consider the following procedure whose main purpose is to duplicate an object -- whether that object be a number, a string, a character, or a list. If a second argument is supplied, that argument should be the number of copies that result (if omitted, this is assumed to be "2"). If a third argument is supplied (and has a non-false value), it indicates that cloning of characters or strings should result in a single, concatenated string (not a list).
; Make list comprising N copies of the item (default N = 2) ; ex: (clone 9) ==> (9 9) ; (clone 9 4) ==> (9 9 9 9) ; (clone "abc" 3) ==> ("abc" "abc" "abc") ; chars and strings accept a third arg which if not false, ; will result in a string instead of a list. ; ex: (clone #\a 4) ==> (#\a #\a #\a #\a) ; (clone #\a 4 #t) ==> "aaaa" ; (clone "ab" 3 #t) ==> "ababab" (define (clone x . args) (let ((count (if (null? args) 2 (car args) ) )) (if (and (pair? args) (pair? (cdr args)) (cadr args) ) (let loop ((result "") (count count)) (if (zero? count) result (if (char? x) (loop (string-append result (make-string 1 x)) (- count 1)) (loop (string-append result x) (- count 1)) ))) (let loop ((result '()) (count count) ) (if (zero? count) (if (null? result) '() (reverse result) ) (loop (cons x result) (- count 1)) )))))
Combining the functionality of variable arguments with Script-fu's dynamic typing of variables, it is possible to write some rather elegant procedures. For example,
(define (paint-xy drawable x y . args) (let ((color (cond ((null? args) ;; color not specified, use FG (list->vector (car (gimp-context-get-foreground))) ) ((vector? (car args)) ;; color is specified as a vector (car args) ) ((pair? (car args)) ;; color is specified as a list (list->vector (car args)) ) ((string? (car args)) ;; color specified using its CSS name (let ((color '(0 0 0))) (gimp-context-push) (gimp-context-set-foreground (car args)) (set! color (list->vector (car (gimp-context-get-foreground)))) (gimp-context-pop) color )) (else ;; red, green, and blue values provided inline (list->vector args) )))) (gimp-drawable-set-pixel drawable x y (vector-length color) color) ))
This single function could be invoked in any of the following ways:
(paint-xy layer x y) ;; using current foreground color setting (paint-xy layer x y #(255 255 255)) ;; white (paint-xy layer x y '(0 255 0)) ;; green (paint-xy layer x y "aliceblue")) ;; CSS named color (paint-xy layer x y 255 165 0) ;; orange
For those interested, the color names recognized by GIMP are listed in the gimprgb-parse.c file in the source tree.
While it is somewhat rare for GIMP scripts to attain the level of complexity where the full power of the Scheme programming language can be exploited, it is nonetheless useful to learn of its capabilities.