Higher-order Functions |
Currying is an idea, which is important in contemporary functional programming languages, such as Haskell. In Scheme, however, the idea is less attractive, due to the parenthesized notation of function calls.
Despite of this, we will discuss the idea of currying in Scheme via some higher-order functions like curry and uncurry. We will also study some ad hoc currying of Scheme functions, which has turned out to be useful for practical HTML authoring purposes, not least when we are dealing with tables.
17.1 The idea of currying | 17.4 Ad hoc currying in Scheme (1) |
17.2 Currying in Scheme | 17.5 Ad hoc currying in Scheme (2) |
17.3 Examples of currying |
17.1. The idea of currying
Contents Up Previous Next Slide Subject index Program index Exercise index
|
The illustration below shows what happens to function signatures (parameter profiles) when we introduce currying.
Figure 17.1 The signatures of curried functions. In the upper frame we show the signature of a function f, which takes three parameters. The frames below show the signature when f is curried. In the literature, the notation shown to the bottom right is most common. The frame to the left shows how to parse the notation (the symbol -> associates to the right). |
Currying and Scheme is not related to each other. Currying must be integrated at a more basic
level to be elegant and useful |
17.2. Currying in Scheme
Contents Up Previous Next Slide Subject index Program index Exercise index
Despite the observations from above, we can explore and play with currying in Scheme. We will not, however, claim that it comes out as elegant as, for instance, in Haskell.
It is possible to generate curried functions in Scheme. But the parenthesis notation of Lisp does not fit very well with the idea of currying |
The function curry2 generates a curried version of a function, which accepts two parameters. The curried version takes one parameter at a time. Similarly, curry3 generates a curried version of a function that takes three parameters.
The functions uncurry2 and uncurry3 are the inverse functions.
It is worth a consideration if we can generalize curry2 and curry3 to a generation of curryn via a higher-order function curry, which takes n as parameter. We will leave that as an open question.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | (define (curry2 f) (lambda(x) (lambda(y) (f x y)))) (define (curry3 f) (lambda(x) (lambda(y) (lambda(z) (f x y z))))) (define (uncurry2 f) (lambda (x y) ((f x) y))) (define (uncurry3 f) (lambda (x y z) (((f x) y) z))) | |||
|
Try out the functions curry2 and curry3 on a number of different functions.
You can, for instance, use then curry functions on plus (+) and map.
Demonstrate, by a practical example, that the uncurry functions and the curry functions are inverse to each other.
There is no solution to this exercise
17.3. Examples of currying
Contents Up Previous Next Slide Subject index Program index Exercise index
Let us here show a couple of examples of the curry functions from Section 17.2.
Curried functions are very useful building blocks in the functional paradigm In particular, curried functions are adequate for mapping and filtering purposes |
The function font-1 is assumed to take three parameters. The font size (an integer), a color (in some particular representation that we do not care about here) and a text string on which to apply the font information. We show a possible implementation of font-1 in terms of the font mirror function in Program 17.2.
Expression | Value |
(font-1 4 red "Large red text") | Large red text |
(define curried-font-1 (curry3 font-1)) (define large-font (curried-font-1 5)) ((large-font blue) "Very large blue text") | Very large blue text |
(define small-brown-font ((curried-font-1 2) brown)) (small-brown-font "Small brown text") | Small brown text |
(define large-green-font ((curried-font-1 5) green)) (list-to-string (map large-green-font (list "The" "End")) " ") | The End |
Table 17.1 Examples of currying in Scheme. |
1 2 3 4 | (define (font-1 size color txt) (font 'size (as-string size) 'color (rgb-color-encoding color) txt)) | |||
|
17.4. Ad hoc currying in Scheme (1)
Contents Up Previous Next Slide Subject index Program index Exercise index
In some situations we would wish that the map function, and similar functions, were curried in Scheme. But we cannot generate an f- mapper by evaluating the expression (map f). We get an error message which tells us that map requires at least two parameters.
In this section we will remedy this problem by a pragmatic, ad hoc currying made via use of a simple higher-order function we call curry-generalized.
It is possible to achieve 'the currying effect' by generalizing functions, which requires two or more parameters,
to only require a single parameter |
In order to motivate ourselves, we will study a couple of attempts to apply a curried mapping function.
Expression | Value |
(map li (list "one" "two" "three")) | ("<li>one</li>" "<li>two</li>" "<li>three</li>") |
(define li-mapper (map li)) | map: expects at least 2 arguments, given 1 |
(define li-mapper ((curry2 map) li)) (li-mapper (list "one" "two" "three")) | ("<li>one</li>" "<li>two</li>" "<li>three</li>") |
Table 17.2 A legal mapping and an impossible attempt to curry the mapping function. The last example shows an application of curry2 to achieve the wanted effect, but as it appears, the solution is not very elegant. |
In Program 17.3 we program the function curry-generalized. It returns a function that generalizes the parameter f. If we pass a single parameter to the resulting function, the value of the red lambda expression is returned. If we pass more than one parameter to the resulting function, f is just applied in the normal way.
1 2 3 4 5 6 | (define (curry-generalized f) (lambda rest (cond ((= (length rest) 1) (lambda lst (apply f (cons (car rest) lst)))) ((>= (length rest) 2) (apply f (cons (car rest) (cdr rest))))))) | |||
|
The blue expression aggregates the parameters - done in this way to be compatible with the inner parts of the red expression. In a simpler version (cons (car rest) (cdr rest)) would be replace by rest.
In the next section we see an example of curry generalizing the map function.
17.5. Ad hoc currying in Scheme (2)
Contents Up Previous Next Slide Subject index Program index Exercise index
We may now redefine map to (curry-generalized map). However, we usually bind the curry generalized mapping function to another name, such as gmap (for generalized map).
This section shows an example, where we generate a li mapper, by (gmap li).
Expression | Value |
(define gmap (curry-generalized map)) (define li-mapper (gmap li)) (li-mapper (list "one" "two" "three")) | ("<li>one</li>" "<li>two</li>" "<li>three</li>") |
(gmap li (list "one" "two" "three")) | ("<li>one</li>" "<li>two</li>" "<li>three</li>") |
Table 17.3 Examples of curry generalization of map. Using curry-generalized it is possible to make a li-mapper in an elegant and satisfactory way. The last row in the table shows that gmap can be used instead of map. Thus, gmap can in all respect be a substitution for map, and we may chose to redefine the name map to the value of (curry-generalized map). |
If we redefine map to (curry-generalized map), the new mapping function can be used instead of the old one in all respects. In addition, (map f) now makes sense; (map f) returns a function, namely an f mapper. Thus ((map li) "one" "two" "three") does also make sense, and it gives the result shown in one of value cells to the right of Table 17.3.