The same abstract data can have multiple concrete representations. Tagged data and dispatch tables let representations coexist without knowing about each other. This is the birth of extensibility: adding a new representation requires no changes to existing code.
Representations for complex numbers
A complex number can be stored as rectangular (real, imaginary) or polar (magnitude, angle). Both are valid. The operations real-part, imag-part, magnitude, angle define the abstract interface. Two teams can implement independently.
Problem: both representations are just pairs. How does a generic operation know which one it's looking at? Answer: attach a type tag. (attach-tag 'rectangular (cons 3 4)) produces (rectangular 3 . 4). The generic operations dispatch on the tag.
The conditional dispatch above doesn't scale: adding a new representation means editing every generic operation. Data-directed programming uses a two-dimensional table indexed by (operation, type). Each representation installs its own procedures. The generic operation just looks up the table.
Instead of a central dispatch table, each object carries its own dispatch. You "send a message" (the operation name) to the object, and it returns the right value. This is what object-oriented programming looks like from first principles: an object is a closure that dispatches on a symbol.
Scheme
; Message passing: the object IS the dispatch
(define (make-from-real-imag x y)
(lambda (op)
(cond ((eq? op 'real-part) x)
((eq? op 'imag-part) y)
((eq? op 'magnitude)
(sqrt (+ (* x x) (* y y))))
((eq? op 'angle) (atan y x))
(else (error "Unknown op" op)))))
(define (make-from-mag-ang r a)
(lambda (op)
(cond ((eq? op 'magnitude) r)
((eq? op 'angle) a)
((eq? op 'real-part) (* r (cos a)))
((eq? op 'imag-part) (* r (sin a)))
(else (error "Unknown op" op)))))
; Generic apply = just call the object with the op
(define (apply-generic op obj) (obj op))
(define z1 (make-from-real-imag 34))
(define z2 (make-from-mag-ang 50.9272952))
(display "z1 real-part: ") (display (apply-generic 'real-part z1)) (newline)
(display "z1 magnitude: ") (display (apply-generic 'magnitude z1)) (newline)
(display "z2 real-part: ") (display (apply-generic 'real-part z2)) (newline)
(display "z2 magnitude: ") (display (apply-generic 'magnitude z2))
Python
importmath# Message passing: object is a closure/dictdef make_from_real_imag(x, y):
def dispatch(op):
if op == 'real_part': return x
if op == 'imag_part': return y
if op == 'magnitude': returnmath.sqrt(x**2 + y**2)
if op == 'angle': returnmath.atan2(y, x)
return dispatch
def make_from_mag_ang(r, a):
def dispatch(op):
if op == 'magnitude': return r
if op == 'angle': return a
if op == 'real_part': return r * math.cos(a)
if op == 'imag_part': return r * math.sin(a)
return dispatch
z1 = make_from_real_imag(3, 4)
z2 = make_from_mag_ang(5, 0.9272952)
print("z1 real_part: " + str(z1('real_part')))
print("z1 magnitude: " + str(z1('magnitude')))
print("z2 real_part: " + format(z2('real_part'), '.6f'))
print("z2 magnitude: " + str(z2('magnitude')))
Notation reference
Scheme
Python
Meaning
(attach-tag 'type data)
dict(type=..., ...)
Tag data with its type
(type-tag datum)
datum.get('type')
Extract the type tag
(contents datum)
datum (minus type key)
Extract the payload
(put op type proc)
dispatch[(op, type)] = fn
Install in dispatch table
(get op type)
dispatch.get((op, type))
Look up dispatch table
(apply-generic op datum)
apply_generic(op, datum)
Dispatch on type tag
Translation notes
The three dispatch strategies — explicit conditional, dispatch table, message passing — correspond directly to patterns in mainstream OOP. Explicit conditionals are isinstance checks. Dispatch tables are the visitor pattern. Message passing is virtual method dispatch. Python classes combine all three: isinstance for ad-hoc checks, __init_subclass__ for registry patterns, and regular methods for message passing.
SICP's insight is that these are all the same mechanism (dispatch on type) organized differently. Data-directed programming is the most extensible: new types and new operations can be added independently. This is exactly the expression problem that haunts every language design.
Neighbors
SICP chapters
๐ช Chapter 6 โ Symbolic Data (pattern matching on expression types)
๐ช Chapter 8 โ Generic Operations (extending dispatch to arithmetic across types)
Foundations (Wikipedia)
Multiple dispatch โ generalizing single dispatch to multiple arguments
Dynamic dispatch โ runtime selection of method implementations
Polymorphism โ the broader concept behind multiple representations