[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