zackoverflow

Modularity, Objects, and State

Notes/musings on the third chapter, currently in-progress


Scheme as a precursor to traditional OOP?λ

The book is now introducing Scheme’s version of OOP, and it’s really interesting because I see certain explicit features of OOP languages that probably were inspired by Scheme. For example, private access modifiers are achieved in Scheme by simply using the fact that variables are only visible in their scope:

(define new-withdraw
  (let ((balance 100))
    (lambda (amount)
      (if (>= balance amount)
          (begin (set! balance (- balance amount))
                 balance)
          "Insufficient funds"))))

Here the balance variable cannot be accessed outside of new-withdraw and the lambda it returns.

SICP refining my understanding of OOPλ

Before SICP, OOP in my head was just the paradigm where “everything is an object”. But now reading through the book’s chapters on mutability and assigning state, OOP to me now is more about “encapsulated statefulness”.

Similarities between Scheme -> JSλ

One of the exercises makes us create a procedure that can tell us how many times it was executed:

(define (make-monitored f)
  (let ((count 0))
    (lambda (arg)
      (if (eq? arg 'how-many-calls?) 
          count
          (begin 
            (set! count (+ count 1))
            (f arg))))))

(define monitored-sqrt (make-monitored sqrt))

(monitored-sqrt 100) ;; 100
(monitored-sqrt 25) ;; 5
(monitored-sqrt 'how-many-calls) ;; 2

And the similarity between the JavaScript equivalent is striking:

const makeMonitored = (f) => {
  let count = 0;
  return (arg) => {
    if (arg === "how-many-calls?") {
      return count;
    }
    count += 1;
    return f(arg);
  }
}

Similarities between Scheme -> Rustλ

I liken the way imperative code is used in Scheme to that of how unsafe code is used in Rust. In Rust, you must follow the rules of the borrow checker, but if needed you can write unsafe code in unsafe blocks:

// Once you enter unsafe land you have no 
// protection from undefined behaviour!
unsafe {
  // Do something possibly sound, or completely bogus
}
// Other happy safe code

In Scheme, the similar is true but with imperative/functional programming. The nature of the language forces you to write in a functional style, but if you need side-effects and impurity you must use special variant functions that end with !:

(define (imperative-stuff some-list)
  (set-cdr! list '()))

Scheme’s syntax usually expects one form, and this helps to enforce the functional style and prevent side-effects. But if you need multiple expressions where Scheme expects one, you must use the begin keyword:

(define x 34)
(+ 420
   (begin (set! x (+ x 35) x)))

See how it is analagous to Rust’s unsafe? Except instead of delineating a departure from the borrow checker and entrance into the land of unsafety, it delineates a departure from a purely functional style and an entrance into a land of imperative code.

First time the words Functional Programming are usedλ

“So long as we do not use assignments, two evaluations of the same procedure with the same arguments will produce the same result, so that procedures can be viewed as computing mathematical functions. Programming without any use of assignments, as we did throughout the first two chapters of this book, is accordingly known as functional programming.”

300 pages into the book and the words “functional programming” are finally uttered! At first I was curious about why they chose so late into the book to discuss FP formally, but now the decision is clear to me.

It’s important that we also learned to program with state and assignments, so we can contrast this with FP, so the benefits of FP are clear to us. What this quote discusses is the referential transparency property that FP programs have. And the best way to learn about why this property is so beneficial to us, is to program with it, and then without it (so we see what we’re missing), which is exactly what this chapter is about!

The TV writer Dan Harmon talks a lot about his storytelling process, and his story circle technique. In one of the talks I heard him give, he talks about how the most profound way you can teach a character a lesson is by giving them what they want and showing them that it’s not so great after all. He employs this method in a lot of his work.

I feel like I have gone through a similar story arc. I struggled at first trying to program in Scheme in a functional manner, wishing I could reach into imperative land and have my comfortable statefulness and assigments and side-effects.

Now reading the rest of this section, which is dedicated to elucidating the pitfalls of imperative programming, my desire to return to imperative land is not so passionate.

Early on in my journey reading this book, I remarked at how easy it was to debug programs in Scheme. I was basically leveraging the property of referential transparency to test my program’s logic in isolation. But in this section, the book explicitly tells me that I cannot do this anymore, reading this brings me great pain and I’m sure will be the source of future frustrations.

This sentence in particular is ominous:

“In addition to raising complications about computational models, programs written in imperative style are susceptible to bugs that cannot occur in functional programs.”

I liken this sentence to me switching from Rust to C. With Rust I could lean on the simple fact that the nature of the language I was using would protect me from errors with memory, switching to C would mean I would have none of this!

Likewise, in using a functional style with Scheme I could lean on the fact that the functional nature of my code protected me from bugs.