Search code examples
schemelispracketsicp

Scheme's block structure efficiency


The book defines block structure in Chapter 1, allowing you to 'package' defines inside a procedure definition.

Consider this mean-square definition for example:

(define (mean-square x y) 
    (define (square x) (* x x))
    (define (average x y) (/ (+ x y) 2))
    (average (square x) (square y)))

when I run (mean-square 2 4) I correctly get 10.

My question is, are the internal definitions ( square and average in this toy case ) run each time I invoke the mean-square procedure via the interpreter? If so, isn't that inefficient? and if not, why?


Solution

  • If the code is somewhat naively compiled, there could be some overhead. The reason is that the inner functions are defined in a brand new lexical environment that is freshly instantiated on each entry into the function. In the abstract semantics, each time the function is called, new lexical closures have to be captured and wired into the correct spots in that environment frame.

    Thus it boils down to how much of this can the compiler optimize away. For instance it can notice that neither of the functions actually references the surrounding lexical environment. (The x and y references in these functions are to their own parameters, not to those of the surrounding mean-square). Which means they both be moved to the top level without changing semantics:

    (define (__anon1 x) (* x x))
    
    (define (__anon2 x y) (/ (+ x y) 2))
    
    (define (mean-square x y)
        (define square __anon1)
        (define average __anon2)
        (average (square x) (square y)))
    

    And since now square and average are effectively simple aliases (aliases for global entities that are generated by the compiler, which the compiler knows aren't being manipulated by anything outside of its control), the values they denote can be propagated through:

    (define (mean-square x y)
      (__anon2 (__anon1 x) (__anon1 y)))