Skip to main content

Abstraction and HOF Lab

Build a small numerical methods library in both Scheme and Python. Use it as a scaffold to practice procedural abstraction, data abstraction, higher-order functions, and closures -- all together, on the same code.

Retrieval Prompts​

Answer closed-book before starting:

  1. What goes into a "contract" for a procedural abstraction?
  2. What fields does a closure carry with it, and why?
  3. Write the types of map, filter, reduce, and compose.
  4. When is a returned procedure the right move, and not a passed one?

Target Library​

Implement these operations. Each of them is built in SICP §1.3, which you should only open if stuck.

NameSignatureMeaning
sum(term, a, next, b)summation of a sequenceΣ term(i) for i = a, next(a), next(next(a)) …, ≤ b
product(term, a, next, b)product of a sequencesame, with * and initial 1
accumulate(combine, id, term, a, next, b)generalize sum and product
integral(f, a, b, dx)numerical integrationSimpson or midpoint
fixed-point(f, x0)iterate f until `x - f(x)
average-damp(f)returns (λx. (x + f(x))/2)
deriv(f, dx)returns the derivative of f
newton(f, guess)fixed-point of x − f(x) / deriv(f)(x)

Two things to notice:

  • accumulate should fully subsume sum and product by being called with the right combine and id.
  • average-damp, deriv, newton all return procedures. They must be implemented as closures.

Workflow​

  1. In Scheme, implement each operation. Keep procedures small; every one has a contract in a comment.
  2. Port the entire library to Python. Use lambda or nested def freely. Closures should look nearly identical.
  3. Write at least three callers of the library:
    • sum-of-cubes 1..10
    • integral of cube over [0, 1] (should approach 1/4)
    • square root via newton, compared to the host language's sqrt
  4. Extract a pressure table in a README.md that lists, for each piece of the library, which abstraction move it demonstrates (procedural? data? HOF? closure?).

Compare and Distinguish​

  • sum vs accumulate. Which should be the primitive, and which should be a one-liner in terms of the other?
  • deriv with closures vs deriv taking an extra dx every call. What does the closure version buy you? What does it cost?
  • newton in Scheme vs in C. Sketch how you would write newton in C. Which parts become harder? Which become impossible without closures?

Common Mistake Check​

Identify the error in each of these patterns (do NOT write correct code yet -- explain the bug):

  1. newton is implemented as a loop that mutates x, then returns x. Why is this a smell in a library that wants to be composable?
  2. average-damp is implemented as average-damp(f, x) -- taking x as an extra argument instead of returning a procedure.
  3. integral hard-codes dx = 0.001 deep inside its body.
  4. fixed-point uses set! inside a loop over a mutable binding captured by a closure; two callers of the same library share a cached guess.

Extension: Abstract Data Types​

Add a Point, Vector, and Matrix ADT to the library. Design the constructors and selectors first; forbid callers from touching the representation.

  • Implement in Scheme using cons/list, with clearly-labeled constructors and selectors.
  • Re-implement in Python using a namedtuple or a @dataclass(frozen=True). Hide fields that should not be read by clients.
  • Write one vector operation (dot-product) using only selectors. Change the representation (e.g. swap internal order) and confirm dot-product does not change.

Evidence Check​

This lab is complete only if:

  • Both Scheme and Python implementations exist and pass the same acceptance tests.
  • accumulate is the primitive; sum and product are one-liners on top.
  • average-damp, deriv, newton are closures that return procedures.
  • The Point/Vector/Matrix ADT can swap representations without touching callers.
  • The README has the pressure table described above.
  • You can explain, in one sentence per operation, which abstraction move justifies its shape.