You’ve learned the most common approach we will use to defining procedures. To define a procedure, you use a form like the following.
(define procedure-name
(lambda (formal-parameters)
body))
If we wanted to define a procedure, add
, that adds two values, we
might write something like the following.
(define add (lambda (x y) (+ x y)))
However, you’ve already seen another way to define a procedure. Instead
of the lambda
expression, you can use define
to give another name
to a procedure that already exists. For example,
(define add +)
How are these two definitions similar? Both use the define
keyword to
associate a name (add
) with something that defines a procedure. In
the first case, it’s a lambda expression. In the second, it’s an
existing procedure. Perhaps that’s not surprising. We can also define
numeric values using expressions or constants.
(define x (+ 1 5)) (define x 6)
The Lisp family of languages (including Scheme) set
themselves apart from many programming languages by permitting you
to use a variety of kinds of expressions to define procedures.
You’ve already seen two: lambda expressions and existing procedures.
In this reading, we’ll explore two more: composition and partial
expressions. Just as an arithmetic operation, like +
, creates a
numeric value, the composition and partial-expression operations
create a procedural value.
It may not be a surprise that these operations can create new
procedures; after all, procedures like map
and apply
take
procedures as parameters.
You may have already seen composition in your study of mathematics. The composition of two functions, \(f\) and \(g\), is written \(f \circ g\) and represents a function that first applies \(g\) and then \(f\). That is, \((f \circ g)(x) = f(g(x))\).
In scamper, we use the compose
function to represent function composition.
Let’s start by composing a few procedures with themselves.
(square 3) (define quad (compose square square)) (quad 3) (define add1 (lambda (n) (+ n 1))) (add1 3) (define add2 (compose add1 add1)) (add2 3)
Scamper also provides a synonym function for compose
, o
(lower-case ‘o’), that is slightly more evocative of the mathematical definition of composition.
For example, we can rewrite the definition of quad
above using o
as follows:
(define quad (o square square))
As these examples suggest, both quad
and add2
are procedures.
We’ve created these procedures in a new way, without a lambda
.
The quad
procedure squares its parameter and then squares it again
(\(3 \times 3 = 9\), \(9 \times 9 = 81\)). The add2
procedure adds one to its
parameter and then adds another one.
What happens if we compose two different procedures? Let’s check.
(define add1 (lambda (n) (+ n 1))) (define f1 (o square add1)) (f1 4) (define f2 (o add1 square)) (f2 4)
As these examples suggest, the composed procedure applies the other
procedures from right to left. That is, f1
adds one to its parameter
and then squares its result, and f2
squares is parameter and then adds
1. If we wanted to make it perfectly clear what we want each procedure
to do, we could name them as follows.
(define add1 (lambda (n) (+ n 1))) (define add1-then-square (o square add1)) (add1-then-square 4) (define square-then-add1 (o add1 square)) (square-then-add1 4)
You can also compose more than two procedures. For example, we might write the following silly procedure.
(define add1 (lambda (n) (+ n 1))) (define fun (o add1 square add1))
Composition can be quite useful. The other day, I realized that
there was no string-reverse
, even though I had assumed there
was. But reversing a string is a straightforward operation: Convert
to a list, reverse the list, convert back to a string. So I
can just write,
(define string-reverse (o list->string reverse string->list)) (string-reverse "hello world!")
compose
allows us to build chains of functions.
This is useful when we want to define
a new function or pass a function to a higher-order function such as map
.
However, if we wish to invoke the resulting function directly, we still need to call it.
((compose square square square) 2)
We might find this syntax somewhat unwieldy for two reasons:
The pipe function |>
(a pipe |
followed by a greater-than symbol >
) is useful when we need to chain function calls together, i.e., send the inputs of one function into the next, sequentially, and we only to do it once.
For example, here is how we might express the triple square
computation using pipe:
(|> 2 square square square)
Note that the argument to the function chain comes first and the functions of the chain come afterwards, each as an argument to |>
.
Implicit in this example is the fact that pipe processes its arguments in the left-to-right direction which is likely more intuitive to read.
To unveil this, here is a use of pipe over two operations where the order matters:
(define add1 (lambda (n) (+ n 1))) (|> 11 add1 square) (|> 11 square add1)
The former example computes \((11+1)^2\) whereas the second computes \((11^2) + 1\).
Give three ways to define a procedure, subtract3
, that takes a number
as input and subtracts 3 from that number.
lambda
.o
, along with a function (sub1 n)
that subtracts 1
from its argument n
.|>
.Which of the three do you prefer? Why?