SICP Exercise 2.58
Question
Suppose we want to modify the differentiation program so that it works with
ordinary mathematical notation, in which +
and *
are infix rather than
prefix operators. Since the differentiation program is defined in terms of
abstract data, we can modify it to work with different representations of
expressions solely by changing the predicates, selectors, and constructors that
define the representation of the algebraic expressions on which the
differentiator is to operate.
- Show to do this in order to differentiate algebraic expressions presented in
infix form, such as
(x + (3 * (x + (y + 2))))
. To simplify the task, assume that+
and*
always take two arguments and that expressions are fully parenthesised. - The problem becomes substantially harder if we allow standard
algebraic notation, such as
(x + 3 * (x + y + 2))
, which drops unnecessary parentheses and assumes that multiplication is done before addition. Can you design appropriate predicates, selectors, and constructors for this notation such that our derivative program still works?
Answer
The easiest way to start here is probably the constructors. The only thing that needs to be modified is the list construction, as can be seen in the highlighted lines.
(define (make-product m1 m2)
(cond ((or (=number? m1 0)
(=number? m2 0))
0)
((=number? m1 1) m2)
((=number? m2 1) m1)
((and (number? m1) (number? m2))
(* m1 m2))
(else (list m1 '* m2))))
(define (make-sum a1 a2)
(cond ((=number? a1 0) a2)
((=number? a2 0) a1)
((and (number? a1) (number? a2))
(+ a1 a2))
(else (list a1 '+ a2))))
Let us now turn our attention to the predicates that determine whether something
is a sum or a product, or neither. Before, we had to check if the first element
of a list (the car
) was an operator such as '*
or '+
. Now, this should be
the second element or the cadr
. This is the only change needed here.
(define (product? x)
(and (pair? x) (eq? (cadr x) '*)))
(define (sum? x)
(and (pair? x) (eq? (cadr x) '+)))
And finally, the selectors. We only need to modify multiplier
and addend
here. These used to be the second list element, but now are the first. The
multiplicand
and augend
remain in their position on the third place in the
list and do not have to be changed at all.
(define (multiplier p) (car p))
(define (multiplicand p) (caddr p))
(define (addend s) (car s))
(define (augend s) (caddr s))
Let’s test the function from the question:
(deriv '(x + (3 * (x + (y + 2)))) 'x)
Result:
4
The result is correct, since \[\frac{\partial}{\partial x}(x+(3\times (x+(y+2))))\] \[=\frac{\partial}{\partial x}(x+3(x+y+2))\] \[=\frac{\partial}{\partial x}(4x+3y+6)\] \[=4\]
Now for the second part of the question.
First, we need a function to figure out the lowest priority operator within the
expression. If +
exists, it always has the lowest priority, followed by *
and finally **
with the highest priority. We can use memq
for this:
(define (lowest-op exp)
(cond ((memq '+ exp) '+)
((memq '* exp) '*)
((memq '** exp) '**)
(else (error "expression contains no operand: LOWEST-OP" exp))))
This works because the conditionals are evaluated in order. If a +
exists
anywhere in the equation, it will be returned as lowest-op
.
This now allows us to rewrite our sum?
, product?
and exponentiation?
functions using lowest-op
:
(define (sum? x) (eq? (lowest-op x) '+))
(define (product? x) (eq? (lowest-op x) '*))
(define (exponentiation? x) (eq? (lowest-op x) '**))
Next, we need to turn our attention to the selectors. For the selectors
targeting the second operand (augend
, multiplicand
and exponent
), we can
simply use memq
with the operator. However, if we have a regular function like
(x + 2)
, this will return (2)
rather than 2
for the augend. To prevent
this, we add another function to check whether the second operand is atomic
i.e. a single value rather than an expression.
(define (atomic? exp) (null? (cdr exp)))
(define (augend s)
(if (atomic? (cdr (memq '+ s))) (car s) s))
(define (multiplicand p)
(if (atomic? (cdr (memq '* p))) (car p) p))
(define (exponent p)
(if (atomic? (cdr (memq '** p))) (car p) p))
Unfortunately, Scheme does not come with an inverse function of memq
, so
getting the first operand is a little more difficult. Some Scheme dialects have
a function called list-head
serving a similar purpose, but mine does not. I
used an implementation of this function by saulgoode
that can be found here:
http://paste.lisp.org/display/146734
This can then be used to write a function to get the first operand:
(define (get-first-operand exp operator)
(list-head exp (- (length exp)
(length (memq operator exp)))))
Now we can rewrite addend
, multiplier
and base
, again making sure to check
for atomicity.
(define (addend s)
(let ((a (get-first-operand s '+)))
(if (atomic? a) (car a) a)))
(define (multiplier p)
(let ((a (get-first-operand p '*)))
(if (atomic? a) (car a) a)))
(define (base p)
(let ((a (get-first-operand p '**)))
(if (atomic? a) (car a) a)))
And that’s it. We don’t need to change the constructors at all.