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 likely 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, Racket, and Scamper) 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.
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 the CSC 151 Racket library, we use o (a lowercase “oh”) to represent function composition. Let’s start by composing a few procedures with themselves.
> (sqr 3)
9
> (define quad (o sqr sqr))
> quad
(lambda (x) ...)
> (quad 3)
81
As this examples suggests, quad is a procedure. We’ve created this procedure in a new way, without a lambda. The quad procedure squares its parameter and then squares it again (3x3 is 9, 9x9 is 81).
What happens if we compose two different procedures? Let’s check.
> (define add1 (lambda (x) (+ 1 x)))
> (define f1 (o sqr add1))
> (f1 4)
25
> (define f2 (o add1 sqr))
> (f2 4)
17
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-then-square (o sqr add1))
> (add1-then-sqr 4)
25
> (define square-then-add1 (o add1 sqr))
> (square-then-add1 4)
17
Some programmers find this right-to-left behavior perfectly natural since it mimics both mathematics and the way we write things in Scheme. That is, if we want to add1 and then square, we write (sqr (add1 5)), with the first operation on the right. Others find the right-to-left behavior backwards, since we speak of the operations from left to write (“add then square”). For now, we’ll stick with the right-to-left behavior. Later in the semester, we may explore some variants of the composition operation.
You can also compose more than two procedures. For example, we might write the following silly procedure, which adds one to its parameter, squares it, and then adds another 1..
> (define fun (o add1 sqr add1))
> (fun 5)
37
> (fun 0)
2
Composition can be quite useful. One day, I discovered that there was no string-reverse in Racket, 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))
As you might guess, there are some things we cannot easily do with composition. For example, suppose we want to write a procedure, half, that takes a number as input and divides that input by two. In this case, we don’t have anything to build upon, other than division, and division is traditionally a binary procedure. We could use lambda to write something like the following.
(define half
(lambda (x)
(/ x 2)))
However, that seems like a lot of work. If we were describing half to another person, we might say something like “The half procedure divides by 2” or, if we were a little bit more formal, “The half procedure is division with a divisor of 2”.
If we tried to rexpress that in Scheme, we might try to write something like the following expression, where the ? is intended to represent the dividend.
> (define half (/ ? 2))
Unfortunately, we can’t write that expression because as soon as Scheme sees (/ ...), it says to itself “I’m supposed to do division right now. So I need to divide ? by 2 and, um, I don’t know what ? represents.” What we’re really trying to do is tell Racket “We want a procedure that divides something by 2.”
Scamper provides a special syntax that lets you build a new function by filling in some of the values in a function call. Instead of writing (/ ? 2), we write #(/ %1 2). The %1 represents “parameter 1”.
Let’s try it.
> (define half #(/ %1 2))
> (half 10)
5
> (half 7)
3.5
> (half 8.4)
4.2
That looks pretty good, doesn’t it? Note, however, that the placement of the %1 is important. Since (/ a b) computes a divided by b, and we want to divide by 2, the %1 comes immediately after the /. We call that the “left section” of a binary procedure.
What happens if we make the %1 the second parameter of /? Let’s see
> (define flah #(/ 2 %1))
> (flah 10)
0.2
> (flah 7)
0.2857142857142857
> (flah 0)
. . /: division by zero
As these examples suggest, flah divides 2 by whatever number you give it.
We can also use multiple percent-placeholders (e.g., %1 and %2) when we have a procedure that takes more than two parameters.
> (define this-and-that #(string-append %1 " and " %2))
> (this-and-that "ham" "eggs")
"ham and eggs"
> (this-and-that "self gov" "the individually advised curriculum")
"self gov and the individually advised curriculum"
> (this-and-that "Lyle's" "Bob's")
"Lyle's and Bob's"
> (this-and-that "Bob's" "Roberts'")
"Bob's and Roberts'"
> (this-and-that "gyre" "gimble")
"gyre and gimble"
We can also build this to take the parameters in reverse order.
> (define that-and-this #(string-append %2 " and " %1))
> (that-and-thjis "ham" "eggs")
"eggs and ham"
Wasn’t that fascinating?
Why do we Schemers call this “cutting”? Because they envision building new procedures by cutting a hole for input in an expression.
The cut operation has a somewhat strange syntax. It is also very powerful. Many functional programmers prefer a simpler version, which we’ll call “sectioning”. Like cutting, sectioning fills in some parameters of a function. Unlike cutting, sectioning does not have a special syntax; it looks like any function call. (Later in the semester, we’ll even learn how to write the section functions.) Andunlike cutting but like composition, sectioning leaves the parameter unspecified.
The left-section function, written l-s, takes two parameters: a two-parameter function, f, and a value, left (or an expression that produces a value). It returns a new function that takes one parameter. l-s then returns a new function of one parameter. When you call the new function to a value, x, it applies f to left and x: (f left x). Alternately, you can think of (l-s f left) as another way to write #(f left %1). That is, (l-s f left) fills in the left parameter of f.
> (define add1 (l-s + 1)) ; f is +, left is 1
> (add1 5) ; x is 5
6 ; (+ 1 5)
> (add1 0) ; x is 0
1 ; (+ 1 0)
> (add1 1.5) ; x is 1.5
2.5 ; (+ 1 1.5)
> (add1 -2) ; r is -2
-1 (+ 1 -2)
Wasn’t that exciting?
As you might guess, right-section (r-s) is much like left-section except that it fills in the second (right) parameter of f. Once again, we get back a one-parameter function that takes one input. That is, (r-s f right) returns a new function that corresponds to #(f %1 right). When we apply the new function to a value, x, it applies the original function to x and right, computing (f x right).
> (define sub1 (r-s - 1) ; f is -, right is 1.
> (sub1 5) ; x is 5
4 ; (- 5 1)
> (sub1 0) ; x is 0
-1 ; (- 0 1)
If both l-s and r-s build a new, one-parameter, procedure, does it matter which we use? Yes! Well, sometimes. As you’ve seen, many binary operations behave differently depending on what’s on the left and what’s on the right. (- 1 4) is not the same as (- 4 1).
> (define subfrom1 (l-s - 1)) ; (f is -, left is 1)
> (subfrom1 5)
> -4 ; (- 1 5)
> (define andthat (l-s string-append " and "))
> (define thisand (r-s string-append " and "))
> (andthat "eggs")
" and eggs" ; (string-append " and " "eggs")
> (thisand "eggs")
"eggs and " ; (string-append "eggs" " and ")
Composition and partial functions provide concise sysntax for defining certain kinds of procedures. However, composition works only for one-parameter procedurs and partial-functions only work when you’re filling in some parameters of a multi-parameter procedure. What if you want to do both? For example, consider the problem of counting the number of words in a string. While we haven’t explored all of the component parts in close details, we have seen all of them.
You may recall from early examples that string-split converts a string into a list by dividing the string at a particular character sequence.
> (string-split "Jack and Jill went up the hill" " ")
'("Jack" "and" "Jill" "went" "up" "the" "hill")
> (string-split "Beware the Jabberwock, my son!" " ")
'("Beware" "the" "Jabberwock," "my" "son!")
The length procedure tells us how many elements there are in a list.
> (length (list 4 8 11))
3
> (length (list "Jack" "and" "Jill" "bewared" "the" "Jabberwock"))
6
So, to count the number of words in a string, we might split that string into a list and then count the number of elements in that list. We could write that with lambda as follows.
> (define numwords
(lambda (str)
(length (string-split str " "))))
> (numwords "Jack and Jill went up the hill")
7
However, we can also use composition and cutting to define it somewhat more concisely.
> (define numwords (o length #(string-split %1 " ")))
> (numwords "Jack and Jill went up the hill")
7
Using r-s, we could also write,
> (define numwords (o length (r-s string-split " ")))
> (numwords "Jack and Jill went up the hill")
7
What are the advantages of the latter definitions? The each fit on one line. However, we could probably put the lambda expression on one line. Each cuts a few characters (eight, if we count correctly), but that’s not a big difference. Rather, we choose the latter definitions because many people find it clearer. What does numwords do? It splits a string at space and then takes the length.
The procedures become even more useful (powerful?) when you combine them with other tools, particularly with procedures like map. (We know that you haven’t learned many such procedures, but you’ll learn more soon.) Consider, for example, the problem of adding two to each number in a list.
Before this reading, you might have written something like the following.
(define addtwo
(lambda (x)
(+ 2 x)))
(map addtwo (list ...))
After what you’ve seen in this reading, you might now be tempted to write something like this, which is shorter (and perhaps simpler).
(define addtwo #(+ %1 2))) ; or (define addtwo (r-s + 2))
(map addtwo (list ...))
But you can do something even shorter! As you may recall, the Scheme evaluator, upon encountering a named value, substitutes the value for the name. So it converts the second of the prior two expressions to
(map #(+ %1 2)) (list ...)) ; or (map (r-s + 2) (list ...))
There’s no reason you can’t do the same. Here are a few examples.
> (map #(+ %1 2) (list 3 1 4 1 5 9))
'(5 3 6 3 7 11)
> (map #(+ %1 4) (list 3 1 4 1 5 9))
'(7 5 8 5 9 13)
> (map (o list->string reverse string->list)
(string-split "Once upon a time" " "))
'("ecnO" "nopu" "a" "emit")
That’s right. We’ve created new procedures—one that adds two, one that adds four, one that reverses a string–without naming them. Many computer scientists, computer programmers, and software engineers find the use of such “anonymous procedures” to be particularly useful; after all, if you’re only going to use a procedure once, why go to the bother of naming it?
You’ll find that you use anonymous procedures regularly throughout this course (and whenever you use languages that permit them). As these examples suggest, they go particularly well with the procedures used with lsits.
You can also create anonymous procedures in other ways. You’ve seen that the compose operation (o) and cut create procedures. What else does? That’s right. lambda! We can build anonymous procedures with lambda, too.
> (map (lambda (x) (+ 1 (sqr x)))
(list 1 2 3 4 5))
'(2 5 10 17 26)
You can read this as “map (a function that takes one parameter, x, squares x and then adds 1) to the list …”. What’s the name of that function? It doesn’t have one. it’s anonymous.
Give three ways to define a procedure, subtract3, that takes a number as input and subtracts 3 from that number.
o. Note that you can use sub1,
which subtracts one from its parameter. (You may have to copy our definition.)lambda.Which of the three do you prefer? Why?
This reading is closely based on an earlier one on higher-order procedures. I’ve added a section on anonymous procedures. It was updated for 2023 Fall with a bit of cleanup and replacing section with the new cut procedure. It was updated again in Fall 2026 using the new Scamper syntax for cut. A bit later, the l-s and r-s procedures were added.