Adding loop primitives/optimizations (was Making Set/Dictionaryetc. loops more robust)

Richard A. O'Keefe ok at cs.otago.ac.nz
Thu Dec 2 23:43:25 UTC 2004


"Joshua Scholar" <jscholar at access4less.net> wrote:
	I was looking from this example from the Squeak Swiki
	
	|blocks|
	blocks := (1 to: 10) collect: [:each | [each]].
	blocks first value.
	"you would expect this to be 1, but it is 10".
	
	They were claiming that with closures the value would be 1 not 10.
	
	For it to be 1, the environment for each loop would have to be distinct from
	the environment from the previous iteration, just as I said.

There are *two* blocks in that loop.

	blocks := (1 to: 10) collect: [:each | [each]].
				      1111111112222221

Let me define some ad hoc terminology.  I can't remember the Smalltalk
official vocabulary, and I'm not going to try.

    Block		an object representing a [....] syntactic unit.
    Block instance	a dynamically created object representing a
			block in a particular context.
    Block activation    a dynamically created object containing the state
                        of an invocation of a block instance.

Now let's look at 

	blocks := (1 to: 10) collect: [:each | [each]].

block.1 and block.2 are created by the compiler and stored in the
method's literal pool.

At run time, if closures were implemented properly,

    interval.instance := a new Interval instance from 1 to 10
    block.1.instance := a new instance of block.1 in the context of
		its invoking method
    inside Interval>>collect:
	r := a new Array of size 10
	for i from 1 to 10 do
	    block.1.activation := a new invocation of block.1.instance
	                          with i as a parameter
	    control passes to block.1.activation
	        block.2.instance := a new instance of block.2 in the
	        context of block.1.activation
	        return block.2.instance from block.1.activation
	    control returns from block.1.activation, which is now dead
	    r[i] := block.2.instance
	end for

The block closure stuff will in no way reduce the number of block
instances or block activations created.  You get a block activation
for every #value... message sent to any block instance.

What proper block closures will change is the way non-locals are
handled.  The current effect is

	|blocks each*|
	blocks := (1 to: 10) collect: [:t* | each* := t*. [each*]].
	blocks first value.

	What they do in Scheme I'm not sure, but I have read papers (yes
	plural) on plenty of Scheme compilers, and most Scheme compilers
	analyse the code to see if they need a new environment (is any
	variable captured etc) and they don't bother to created a full
	environment unless it's needed.

And I've written a Scheme compiler (NOT a good one, not at all).
So whose is bigger, eh?  The Scheme system I use on a day-to-day
basis is an interpreter, for what it's worth.
	
The point is that Scheme loops are defined in terms of recursive
procedure calls.  Their official semantics involves creating as many
closures (= block instances) and activation records (= block activations)
as Smalltalk loops, if not more.  Yes, a Scheme compiler may take shortcuts
where it provably doesn't matter.  And so may a Smalltalk compiler.
Both languages allow closures (= block instances) to be passed as arguments
and stored in mutable objects, making the general iterator case difficult
or impossible to optimise.

In this repsect named-LET and DO are really Scheme's equivalent of
#while{False,True}[:] rather than the equivalent of #do:.  Let's take
iteration over non-empty binary trees as an example.

    Object subclass: #BinaryTree
    BinaryTree subclass: #BinaryLeaf
      instanceVariableNames: 'datum'
      
      do: aBlock	aBlock value: datum

    BinaryTree subclass: #BinaryNode
      instanceVariableNames: 'left right'
      
      do: aBlock	left do: aBlock. right do: aBlock.

    ... aBinaryTree do: [:each | each congrobulate].

    (define-structure binary-tree-leaf datum)      
    (define-structure binary-tree-node left right)
    
    (define (for-each-leaf F Tree)
      (cond
        ((binary-tree-leaf? Tree)
          (F (binary-tree-leaf-datum Tree)))
        ((binary-tree-node? Tree)
          (for-each-leaf F (binary-tree-node-left Tree))
          (for-each-leaf F (binary-tree-node-right Tree)))
        (else
          (error "Not a binary tree"))))

    ... (for-each-leaf (lambda (Each) (congrobulate Each)) A-Binary_Tree)

How many (block instances | closures) are made in (Smalltalk | Scheme)?
    ONE.
How many activations of that (block instance|closure)?
    ONE PER LEAF, irrespective of language.
Is a Scheme compiler at all likely to be able to optimise those
activations away?
    NO.




More information about the Squeak-dev mailing list