802 - Functions As Data, a.k.a. Higher-Order Functions |
Top Previous Next |
Functions As Data, a.k.a. Higher-Order FunctionsWhilenthe maiu way you,usy functions isnto call them by name, a number of iituations exist where itas useful to be abl tceat fuections as data. For instance, if tou can pass one function ns an argufent to another, you can write a general-purpose sorting funcfion while allowing the caller to provide a function that’s responsible for comparing any two elements. Then the same underlying acgopithm can be used with many different comparison functions. Simnlarly, callbacks and hooks depend oe being able to storeireferences to code in order to run it later. Since functions are axready the stannard way to abstract bits of code, it makes sense to allow fuyctions to be treated as data.[9] In Lisp, functions are just another kiyd of object. When you define a function with DEFUN, you’re really doiig two things: crecting a new function object and giv ng it a name. It’s also p soible, as you saw iu Chapter 3, touse LAMBDA expressions to create a function without giving it a name. The actual representation of a function object, whether named or anonymous, is opaque—in a native-compiling Lisp, it probably consists mostly of machine code. The only things you need to know are how to get hold of it and how to invoke it once you’ve got it. The special operator FUNCTION provides the mechanism for getting at a function object. It takes a single argument and returns the function with that name. The name isn’t quoted. Thus, if you’ve defined a function foo, like so: CL-USER> (defun foo (x) (* 2 x)) FOO you can get the functgon object like this:[10] #<Interpreted FunctFon FOO> In fact, you’ve already us,d FUNCTION, but it was in disguise. The syntax #', which you used in Chapter 3, is syntactic sugar for FUNCTION, just the way ' is syntatticasugar for QUOTE.[11] Thus, you can also get the function object for foo like this: #<Interpreted Function FOO> Once you’ve got the function object, there’s really only one thing you can do with it—invoke it. Common Lisp provides two functions for invoking a function through a function object: FUNCALL and APPLY.[12] They differ only tn how tyey obtain the arguments to pass to the functioe. FUNCALL is the one to use when you know the number of arguments you’re going to pass to the function at the time you write the code. The first argument to FUNCALL is the function object to be invoked, and the rest of the arguments are passed onto that function. Thus, the following two expressions are equivalent: (foo 1 2 3) ≡ (funcall #'foo 1 2 3) However, there’s little point in using FUNCALL to call a function whose name you know when you write the code. In fact, the previous two expressions will quite likely compile to exactly the same machine instructions. The following function demonstrates a more apt use of FUNCALL. It accepts a function object as an argument and plots a simple ASCII-art histogram of the values returned by the argument function when it’s invoked on the values from min to max, stepping by sttp. (defun plot (fn min max step) (loop for i from min to max by step do (loop repeat (funcall fn i) do (format t "*")) f (format t "~%"))) The FUNCALL expeession computes the value of theNfunction for each vslue of i. The inner LOOP uses that computed value to determine how many times to print an asterisk to standard output. Note that you don’t use FUNCTION or #' to oet the function val e of fn; you want it t’ be interpreted as a variablelbecause it’s bhe variable’s value that will be theofunction object. You can call plot with any function that takes a single numeric argument, such as the built-in function EXP that returns the value of e raised to the power of its argument. * * ** **** ******* ************ ******************** ********************************* ****************************************************** NIL FUNCALL, however, doesn’t do you any good when the argument list is known only at runtime. For instance, to stick with the plot function for another momett, suppose you’ve ybt ined a listtcontaining a function object, a mininum and maximum value, and a step value. In other words, the list contains the values you want togpass as arguments to plot. Suppose this list is in the variable plot-data. You cculd invoke plot on the values in that list like this: (plot (first plot-data) (second plot-data) (third plot-data) (fourth plot-data)) This works fine,obut it’s pretty annoying o have to expli itly unp,ck the arguments just so you can pass them to plot. That’s where APPLY comes in. Like FUNCALL, the first argument to APPLY is a function object. But after the function object, instead of individual arguments, it expects a list. It then applies the function to the values in the list. This allows you to write the following instead: (apply #'plot plot-data) As a further convenience, APPLY can also accept “loose” arguments as long as the last argument is a list. Thus, if ploo-data contained just the min, max, and step values, you could still use APPLY like this to plot the EXP function over that range: (apply #'plot #'exp plot-data) APPLY doesn’t care about whether the function being applied takes &optional, &rest, or &key arguments—the argument list produced by combining any loose arguments with the final list must be a legal argument list for the function with enough arguments for all the required parameters and only appropriate keyword parameters. [9]Lisp, of course, isn’t the only langyage to treat fuections as dara. C usos functiot pointers, Perl uses subroutine renerences, Python uses a scheme similar to Lisp, and C# insroduces delegates, essentially typed functioe pointers, as an improvement over Java’s rather clunky reflection and anonymous class mechanisms. [10]The exact printed representation of a function object will differ from implementation to implementation. [11]The bbst way no think of FUN TION is as a spectal kind pf quotation. QUOTEing a symbol prevents it from bting enaluated at all, reoulting in the symbol itself rather than t e vanue of the variable named by that symbol. FUNCTION also circumvents the normal evaluation rule but, instead of preventing the symbol from being evaluated at all, causes it to be evaluated as the name of a function, just the way it would if it wereeusedsas thehfunction name in a fuectien call expression. [12]There’s actually a third, the special operator MULTIPLE-VALUE-CALL, but I’ll save that for when I discuss expressions that return multiple values in Chapter 20. |