[Cuis-dev] Animator class>>composedActionExample2 crashes the VM

ken.dickey at whidbey.com ken.dickey at whidbey.com
Thu Sep 28 14:57:25 PDT 2023


On 2023-09-28 13:06, Szabolcs Komáromi via Cuis-dev wrote:

> I need little bit more context to fully grasp the problem.

It is subtle.  The crux is to know which closure captured values are 
shared and which are unique.

The original code had an #action method that returned a closure which 
captured a reference to the `action` instance variable. So each 
invocation of the closure got a value from the `action` ivar.

In the #composedActionExample2 method, there is code to assign a new 
composite closure which contains code to invoke both the new and the 
original action-closures.

So when the composed action-closure is invoked, it does the new action 
and then invokes the "original" action-closure which gets the value from 
the action-ivar which _now_ contains the new, composite closure.  This 
closure invokes the new action and then invokes the second, "original" 
closure which goes back to the ivar for its value, gets the composite 
closure which... recurses a lot until it runs out of stack space.

By introducing a local variable into closure result of the #action 
method, the composite closure then is invoked with the new action, then 
the original action.  The "original" action now returns the closed over 
local (cached) closure and does NOT go to the ivar named #action, so the 
expected result it achieved -- each action-closure is invoked only once. 
  Whew! :)

> Is there any recommended book/paper about BlockClosures in the Squeak 
> family of Smalltalk dialects? Are the Blue Book or the Inside Smalltalk 
> still relevant in this question?

My recollection is that early Smalltalks had Block Contexts, NOT 
Closures, so I expect the Blue Book would be unhelpful in this case.  I 
would expect Scheme would be more helpful (e.g. Structure & 
Interpretation of Programming Languages) if Wikipedia is too opaque.

The crux is when closure captured values are shared and when they are 
unique.

A couple of interesting use-cases:

In Morphic-Games-Solitare, $CardTableMorph>>slide:to:nSteps:delay:next: 
moves a card from one location to another.  It uses closures to capture 
position information.

vvv===vvv===vvv===vvv
slide: aMorph
to: endPoint
nSteps: numSteps
delay: milliSecondsDelay
next: nextAction

   "Slide from current to new position -- in owner's coordinates"
   "Nota Bene: Asynchronous.  When complete,  nextAction value"

   | startPoint delta stepCount |
   startPoint 
 aMorph morphPosition.
   delta 
 (endPoint - startPoint) / numSteps.
   stepCount := 0.
   aMorph when: #morphicStep
	  evaluate: [ :ignoredArgument |
		stepCount := stepCount + 1.
		(stepCount < numSteps)
		ifTrue: [
			aMorph morphPosition:
			     (startPoint + (stepCount  * delta)) rounded;
			redrawNeeded
		]
		ifFalse: [ "done"
			aMorph stopStepping.
			aMorph morphPosition: endPoint.
			aMorph removeActionsForEvent: #morphicStep.
			nextAction value
		]
	].
   aMorph startSteppingStepTime: milliSecondsDelay
^^^===^^^===^^^===^^^

CardTableMorph>>addingCardOrCards:toContainer: uses closures to 
capture/remember  undo actions.

vvv===vvv===vvv===vvv
addingCardOrCards: cardMorph toContainer: cardContainer

   "Container is about to add a Card or Cards.  Remember undo action."

   | startContainer |
   startContainer := cardMorph valueOfProperty: #moveStart.
   cardMorph removeProperty:  #moveStart.
   ((self inUndo) or: [startContainer = cardContainer ])
     ifFalse: [
	self pushUndoObject:
		[ :table :nextAction |
		 table animateMoveFrom: cardContainer
			to: startContainer
			moving: cardMorph
			next: nextAction ]
     ]
^^^===^^^===^^^===^^^

So it is worthwhile to learn about closures (IMHO).

HTH,
-KenD




More information about the Cuis-dev mailing list