Skip to main content

Other ways to write procedures

Due
Friday, 1 February 2019
Summary
While lambda expressions are the most common way to write procedures, there are also a variety of others. We consider how to use composition and sectioning to build new procedures from old.
Prerequisites
Writing your own procedures.

Introduction

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 and Racket) 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.

Building new procedures through composition

You may have already seen composition in your study of mathematics. The composition of two functions, f and g, is written fg and represents a function that first applies g and then f. That is, (fg)(x) = f(g(x)).

In the Racket library for FunDHum, we use o to represent function composition. Let’s start by composing a few procedures with themselves.

> (square 3)
9
> (define quad (o square square))
> quad
#<procedure:quad>
> (quad 3)
81
> (add1 3)
4
> (define add2 (o add1 add1))
> add2
#<procedure:add2>
> (add2 3)
5

As these examples suggest, both quad and add2 are procedures. We’ve created these procedures in a new way, without a lambda or just renaming an existing procedure. The quad procedure squares its parameter and then squares it again (3*3 is 9, 9*9 is 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 f1 (o square add1))
> (f1 4)
25
> (define f2 (o add1 square))
> (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 square add1))
> (add1-then-square 4)
25
> (define square-then-add1 (o add1 square))
> (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 (square (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 will explore some variants of the composition operation.

You can also compose more than two procedures. For example, we might write the following silly procedure.

> (define fun (o add1 square add1))

Procedure sectioning

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 Racket, we might try to write something like the following expression, where the ? is intended to recommend the dividend.

> (define half (/ ? 2))

Unfortunately, we can’t write that expression because as soon as DrRacket 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.”

The loudhum library, which we designed for this course, provides a procedure called section that lets you build a new procedure by filling in some of the parameters to a procedure. However, instead of writing (/ ? 2), we write (section / <> 2). As you might have guessed, the <>, which we tend to call “diamond”, is supposed to represent “here’s the input to our function”; we think it was originally designed to represent an empty space in a clearer way than _. You may have also noted that we put the section immediately after the open paren. That’s because section, like o, builds a new procedure; in effect, the section delays the operation until we give it the remaining arguments.

Let’s try it.

> (define half (section / <> 2))
> (half 10)
5
> (half 7)
3 1/2
> (half 8.4)
4.2
> (half 4+5i)
2+5/2i
> (half 0+6i)
0+3i

That looks pretty good, doesn’t it? Note, however, that the placement of the <> is important. Since (/ a b) computes a divided by b, and we want to divide by 2, the <> comes immediately after the /. We call that the “left section” of a binary procedure.

What happens if we make the <> the second parameter of /? (We call that the “right section”.) Let’s see.

> (define flah (section / 2 <>))
> (flah 7)
2/7
> (flah 10)
1/5
> (flah 0+6i)
0-1/3i
> (flah 0)
Error! . . ../../Applications/Racket v6.5/collects/racket/private/kw.rkt:929:25: /: division by zero

As these examples suggest, flah divides 2 by whatever number you give it.

We can also use multiple <>’s in a section when we have a procedure that takes more than two parameters.

> (require loudhum)                                               
> (define this-and-that (section string-append <> " and " <>))    
> (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 "gyre" "gimble")
"gyre and gimble"

Combining composition and sectioning

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 hte 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.

> (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

(define numwords (lambda (str) (length (string-split str “ “))))

However, we can also use composition and sectioning to define it somewhat more concisely.

> (define numwords (o length (section string-split <> " ")))
> (numwords "Jack and Jill went up the hill")
7

What are the advantages of the latter definition? It fits on one line. However, we could probably put the lambda expression on one line. It cuts a few characters (six, if we count correctly), but that’s not a big difference. Rather, we choose the latter definition because many people find it clearer. What does numwords do? It splits a string a space and then takes the length.

Self checks

Check 1: Subtracting three

Give three ways to define a procedure, subtract3, that takes a number as input and subtracts 3 from that number.

  • Using the composition operation, o. Note that you can use sub1, which subtracts one from its parameter.
  • Using section.
  • Using lambda.

Which of the three do you prefer? Why?

Acknowledgements

Much of this reading is new. However, some examples are taken from a reading entitled “Defining your own procedures” from Grinnell College’s CSC 151.