Generalising Procedures 

In Processing Items in a List we saw how to perform an operation on every element in a list with:

(defun double-list (numbers)
  (if (null numbers) nil
    (cons (* 2 (first numbers)) (double-list (rest numbers)))))

Suppose we want to generalise this so we can perform any action, not just doubling, on each of the elements in the list. We would like to be able to create a procedure apply-list so we could write:

(defun double (n) (* 2 n))
(apply-list 'double '(2 3 5 7 11 13 17 19))

to double the numbers, or:

(apply-list 'evenp '(2 3 5 7 11 13 17 19))

to set each number to t if it's even.

To do this we can use a new procedure funcall, that calls a chosen procedure with the arguments we specify:

Calling a procedure: funcall

The procedure funcall (for function call) takes one or more parameters. The first parameter should evaluate to a procedure, which is called with the remaining parameters as its parameters. So, for example:

CL-USER > (funcall '+ 2 3)
5

sum-listin the previous section:

(defun apply-list (function items)
  (if (null items) nil
    (progn
      (funcall function (first items))
      (apply-list function (rest items)))))

We can test it out with the procedure print like this:

CL-USER > (apply-list 'print '(2 3 4 5 6))
2 
3 
4 
5 
6 
NIL

Applying a function to make a new list: change-list

As a second example we can write a generalised version of double-list, defined in Processing Items in a List, to create a general-purpose version called change-list:

(defun change-list (function items)
  (if (null items) nil
    (cons (funcall function (first items)) (change-list function (rest items)))))

Let's test it:

CL-USER > (change-list 'double '(2 3 5 7 11 13 17 19))
(4 6 10 14 22 26 34 38)
CL-USER > (change-list 'evenp '(2 3 5 7 11 13 17 19))
(T NIL NIL NIL NIL NIL NIL NIL)

In Lisp, the operation of performing an action on every element of a list is called mapping. In fact Lisp includes a whole range of built-in mapping procedure. But rather than learning about the built-in procedures I felt that it's more important to learn how to write your own, to understand the general principles they use.

Exercises

1. Repeat a procedure for a range of numbers

Write a procedure repeat-for, based on print-for in Repeating Operations, which calls a procedure for each number in a specified range. The procedure should take the number as an argument. For example:

(repeat-for 4 7 'print)

should print:

4 
5 
6 
7 

2. Combine a list of numbers using a binary operator

Write a procedure combine that takes a binary operator and a list of two or more numbers, and combines the numbers using the operator. (You can assume the operator is associative.) For example:

(combine '+ '(1 2 3 4))

should find 1+2+3+4, and give 10, and:

(combine '* '(1 2 3 4))

should give 24.


Previous: More about Recursion

Next: Projects


blog comments powered by Disqus