In this assignment, we'll experiment with higher-order functions in various languages. Assume you already have some implementation of a list (the notion applies to things other than lists too, but these are the simplest examples I can think of). Implement the following operations:
countIf
on the list of numbers
{1,7,8,2,6,3,-4}
and the "even" function,
it would return 3 because there are 3 even numbers in that list. If we
called countIf
on the same list of numbers and a function
that tells whether its argument is greater than 1, it would return 5
because 5 of the numbers in the list are greater than 1.filterIf
applied to {1,7,8,2,6,3,-4}
and the "even" test would return the list {8,2,6}
.
applyToEach
applied to {1,7,8,2,6,3,-4}
and the "multiply by
3" function would return the list {3,21,24,6,18,9,-12}
.
n
and a function
f
from natural numbers to some other type, and returns a
list of length n
consisting of {f(0),f(1),...f(n-1)}
in that order.
After you've tried some of these, think about other situations in which
you could make use of a function that takes in a function... or perhaps
even a function that returns a function as its value... or even
both! (An example of this is "derivative", which takes in a
mathematical function and returns a different mathematical function.)
Consider how you could rewrite the functions from
homework 6 more briefly and elegantly using
filterIf
and applyToEach
.
How you write higher-order functions varies considerably from one language to another:
This is easier in Scheme than in any other language I know, since a Scheme function can easily use one of its parameters in function position (i.e. just after a left parenthesis). Thus for example
(define (do-twice f arg) (f (f arg))) (do-twice sqr 3) "should be 81" (do-twice 1+ 3) "should be 5"
Prolog isn't really good at representing functions, but it can be done. For "functions returning boolean," the natural approach is to use a rule's success or failure; for "functions returning another type," use a two-place predicate that unifies the second argument with the result of the function on the first argument.
The
remaining task is how to invoke a rule whose name you were given as a
variable. Ideally, given variables Operator and Argument, you would
simply ask Operator(Argument).
But most implementations
(including Amzi) consider that an error.
There's a call
/1 rule that takes in a complete
question and asks it, e.g.
Question=even(6), call(Question).which (assuming you've got an
even
/1 predicate working)
should succeed.
However, call
/1 requires that you have a
whole question, not an operator and an argument separately. Again, you'd
like to say something simple like
call(Operator(Argument))
or
Question = Operator(Argument), call(Question)
And again, most implementations (including Amzi) consider these an error.
So instead, you need to build up the question piece by piece:
functor(Question,Operator,1), arg(1,Question,Argument), call(Question).To invoke a two-place predicate
Operator(Arg1, Arg2)
, use
functor(Question,Operator,2), arg(1,Question,Arg1), arg(2,Question,Arg2), call(Question).and so on.
The Java language doesn't allow you to pass functions as parameters, but you can do the next best thing: define a class consisting of a single method (and no instance variables), create an object of that class, and pass the object. A method that takes in such an object, naturally, needs to know what method to call on it; this is normally done by defining an interface specifying the method signature. Thus for example
interface IntToIntFunction { public int apply(int x); } class AddOne implements IntToIntFunction { public int apply(int x) { return x+1; } } class Square implements IntToIntFunction { public int apply(int x) { return x * x; } } class Example { public int doTwice (IntToIntFunction f, int x) { return f.apply(f.apply(x)); } }
In C, there is a data type "pointer to function", and one can take the address of a function. (In fact, referring to a function name without calling it is referring to its address, just as referring to an array name without subscripting it is referring to its base address.) Thus for example
int addOne(int x) { return x+1; } int square(int x) { return x*x; } int doTwice (int (*f)(int), int x) { return f(f(x)); /* which is really shorthand for return (*f)((*f)(x)); */ } int main () { printf ("doTwice(addOne, 3) should be 5: %d\n", doTwice(addOne, 3)); /* which is really shorthand for printf ("doTwice(&addOne, 3) should be 5: %d\n", doTwice(&addOne, 3)); */ printf ("doTwice(square, 3) should be 81: %d\n", doTwice(square, 3)); return 0; }
Since C++ is supposed to be upward-compatible from C, the above C code should still work in C++. However, C++ adds an additional wrinkle: "pointers to member functions". You can read about these in Stroustrup, chap. 15.5.