Hey Guys,

I’ve been going through the video lectures "Structure and

Interpretation of Computer Programs by Hal Abelson and Gerald Jay

Sussman. " (

Project MAC Home Page ).

The section on Higher-Order Procedures was a huge eye-opener for me and

I wanted to condense down what I learned in Lisp to guys who program in

Ruby. Now I know that for most of the experienced on this list this

will be old-news, but I hope to provide a valuable tutorial of Abelson

and Sussman’s material to some guys who are just learning about this

stuff in Ruby.

Posted below is the straight text and code examples of what I have so

far. ( I’ve also posted the pdf of slides here:

http://tech.natemurray.com/2006/12/higher-order-procedures-in-ruby.html

if you’re interested. ) Now, I am not just trying to drive up traffic

to my site. My purpose in posting this here is two-fold:

- To submit it for peer review. I’d like to know if you guys have any

suggestions or improvements on the code examples and/or copy. For

example a part that seems particularly ugly to me is the

cube = self.method(:cube).to_proc

cube.call(3)

part. Any suggestions on how to make this a little more transparent or

simplified?

- To provide a valuable introductory tutorial to the power of

higher-order procedures and how to implement them in Ruby.

The text below can be copied and pasted into a file. It should run with

no problems.

DISCLAIMER: As mentioned above and below the copy is taken mainly from

"Structure and Interpretation of Computer Programs by Hal Abelson and

Gerald Jay Sussman. " (

Project MAC Home Page ). A

few paraphrases and examples were added and the code was converted to

Ruby.

enjoy,

Nate M.

http://www.natemurray.com

#!/usr/bin/ruby

# == Higher-Order Procedures (in Ruby)

# based on Structure and Interpretation of Computer Programs (1985 MIT

Press)

# by Hal Abelson and Gerald Jay Sussman.

# * Project MAC Home Page

# Nathan M. [email protected] v1.0 12/13/06

# http://www.natemurray.com

# == Legal

# The copy in this presentation is taken directly from Structure and

# Interpretation of Computer Programs by Hal Abelson and Gerald Jay

Sussman

# (MIT Press, 1984; ISBN 0-262-01077-1). Specifically section 1.3

Formulating

# Abstractions with Higher-Order Procedures. There are a few

paraphrases and

# additional examples added.

# The main difference is that the code has been converted from Lisp to

Ruby.

# The full text of this book and accompanying video lectures can be

found at:

# Project MAC Home Page

# http://mitpress.mit.edu/sicp/

# The video lectures are copyright by Hal Abelson and Gerald Jay

Sussman. The

# video lectures, and in turn this document, are licensed under a

Creative

# Commons License.

# Creative Commons — Attribution-ShareAlike 2.0 Generic — CC BY-SA 2.0

# == Slide

# Mathematical procedures are, in effect, abstractions that describe

compound operations on

# numbers independent of the particular numbers. For example, when we

def cube(a)

a * a * a

end

# define cube we are not talking about the cube of a particular number,

but rather about a

# method for obtaining the cube of any number.

# == Slide

# Of course we could get along

# without ever defining this procedure, by always writing expressions

such as

# (3 * 3 * 3)

# (x * x * x)

# (y * y * y)

# and never mentioning cube explicitly. This would place us at a

serious

# disadvantage, forcing us to work always at the level of the

particular

# operations that happen to be primitives in the language

(multiplication, in

# this case) rather than in terms of higher-level operations. Our

programs

# would be able to compute cubes, but our language would lack the

ability to

# express the concept of cubing. One of the things we should demand

from a

# powerful programming language is the ability to build abstractions by

# assigning names to common patterns and then to work in terms of the

# abstractions directly. Procedures provide this ability.

# == Slide

# Yet even in numerical processing we will be severely limited in our

ability

# to create abstractions if we are restricted to procedures whose

parameters

# must be numbers.

# Often the same programming pattern will be used with a number of

different

# procedures. To express such patterns as concepts, we will need to

construct

# procedures that can accept procedures as arguments or return

procedures as

# values.

# Procedures that manipulate procedures are called higher-order

procedures.

# This presentation shows how higher-order procedures can serve as

powerful

# abstraction mechanisms, vastly increasing the expressive power of our

# language.

# == Slide

# Consider the following three procedures.

# == Slide

# The first computes the sum of the integers from a through b:

def sum_integers(a, b)

return 0 if a > b

a + sum_integers((a + 1), b)

end

sum_integers(1, 10) #=> 55

# == Slide

# The second computes the sum of the cubes of the integers in the given

range:

def sum_cubes(a, b)

return 0 if a > b

cube(a) + sum_cubes((a + 1), b)

end

sum_cubes(1, 3) #=> 36

# The third computes the sum of a sequence of terms in the series which

# converges to pi/8 (very slowly)

def pi_sum(a, b)

return 0 if a > b

(1.0 / ((a + 2) * a)) + (pi_sum((a + 4), b))

end

pi_sum(1, 1000) * 8 #=> 3.13959265558978

# == Slide

# These three procedures clearly share a common underlying pattern.

# They are for the most part identical, differing only in the

# * name of the procedure

# * the function of a used to compute the term to be added, and

# * the function that provides the next value of a.

# We could generate each of the procedures by filling in slots in the

same template:

# == Slide

# def (a, b)

# return 0 if a > b

# (a) + ((a), b)

# end

# == Slide

# The presence of such a common pattern is strong evidence that there

is a

# useful abstraction waiting to be brought to the surface.

# Mathematicians long ago identified the abstraction of summation of a

series

# and invented ``sigma notation,‘’ for example to express this concept.

# The power of sigma notation is that it allows mathematicians to deal

with the

# concept of summation itself rather than only with particular sums

# For example, you can formulate general results about sums that are

# independent of the particular series being summed.

# Similarly, as program designers, we would like our language to be

powerful

# enough so that we can write a procedure that expresses the concept of

# summation itself rather than only procedures that compute particular

sums.

# == Slide

# We can do so readily in our procedural language by taking the common

template

# shown above and transforming the ``slots’’ into formal parameters:

def sum(term, a, the_next, b)

return 0 if a > b

term.call(a) + sum(term, the_next.call(a), the_next, b)

end

# == Slide

# Now to define sum cubes all we have to do is:

def inc(n)

n + 1

end

def sum_cubes(a, b)

cube = self.method(:cube).to_proc

inc = self.method(:inc ).to_proc

sum(cube, a, inc, b)

end

sum_cubes(1, 3) #=> 36

# == Slide

# With the aid of an identity procedure to compute the term, we can

define

# sum-integers in terms of sum:

def identity(x)

x

end

def sum_integers(a, b)

id = self.method(:identity).to_proc

inc = self.method(:inc ).to_proc

sum(id, a, inc, b)

end

#Then we can add up the integers from 1 to 10:

sum_integers(1, 10) #=> 55

# == Slide

# We can also define pi-sum in the same way

def pi_term(x)

(1.0 / (x * (x+2)))

end

def pi_next(x)

(x + 4)

end

def pi_sum(a, b)

term = self.method(:pi_term).to_proc

nex = self.method(:pi_next).to_proc

sum(term, a, nex, b)

end

# Using these procedures, we can compute an approximation to pi:

pi_sum(1, 1000) * 8 #=> 3.13959265558978

# In using #sum it seems terribly awkward to have to define trivial

procedures

# such as pi_term and pi_next just so we can use them as arguments to

our

# higher-order procedure.

# Rather than define pi-next and pi-term, it would be more convenient

to have a way to directly specify

# * ``the procedure that returns its input incremented by 4’’ and

# * ``the procedure that returns the reciprocal of its input times its

input plus 2.‘’

# We can do this by introducing the special form lambda, which creates

# procedures. Using lambda we can describe what we want as

# lambda{ |x| (1.0 / (x * (x+2))) }

# lambda{ |x| (x + 4) }

# == Slide

# Then our pi_sum procedure can be expressed without defining any

auxiliary procedures as in:

def pi_sum(a, b)

sum( lambda{ |x| (1.0 / (x * (x+2))) },

a,

lambda{ |x| (x + 4) },

b )

end

# == Slide

# The above examples demonstrate how the ability to pass procedures as

# arguments significantly enhances the expressive power of our

programming

# language.

# We can achieve even more expressive power by creating procedures

whose

# returned values are themselves procedures.

# Lets say we want to filter out the even numbers in a list.

# This procedure takes a list and returns a new list containing the

even numbers.

def even?(i)

i % 2 == 0

end

# at the end, functions that return functions

def filter_evens(list)

new_list = []

list.each do |element|

new_list << element if even?(element)

end

new_list

end

list = [1,2,3,4,5,6,7,8,9,10]

filter_evens(list) #=> [2, 4, 6, 8, 10]

# Well, later on we may want to filter by odd numbers, or by prime

numbers.

# What we need is a way to express the concept of filtration.

# == Slide

# (predicate : Logic something that is affirmed or denied concerning an

argument of a proposition.)

# Notice that #make_filter returns not just a value, but a procedure.

This

# procedure can then be used on other data.

def make_filter(predicate)

lambda do |list|

new_list = []

list.each do |element|

new_list << element if predicate.call(element)

end

new_list

end

end

filter_odds = make_filter( lambda{|i| i % 2 != 0} )

filter_odds.call(list) #=> [1, 3, 5, 7, 9]

# == Slide

# The power of this is that our filter is not limited to numeric

evaluations.

# Lets say we want to filter by numbers where the ordinal ends in th

such as 10th.

require ‘facet/integer/ordinal’

10.ordinal #=> “10th”

filter_ths = make_filter(

lambda do |i|

i.ordinal =~ /th$/ ? true : false

end

)

filter_ths.call(list) #=> [4, 5, 6, 7, 8, 9, 10]

# You do this all the time with #map

# == Slide

# As programmers, we should be alert to opportunities to identify the

# underlying abstractions in our programs and to build upon them and

generalize

# them to create more powerful abstractions.

# This is not to say that one should always write programs in the most

abstract

# way possible; expert programmers know how to choose the level of

abstraction

# appropriate to their task. But it is important to be able to think in

terms

# of these abstractions, so that we can be ready to apply them in new

contexts.

# The significance of higher-order procedures is that they enable us to

# represent these abstractions explicitly as elements in our

programming

# language, so that they can be handled just like other computational

elements.