Object Icon has several global variables and keywords, such as &subject
, &pos
and &why
.
Occasionally it is necessary to evaluate an expression without disturbing the value of one or more of these variables. This involves saving and restoring the value of the variable(s) concerned, and there is a library procedure to help with this, util.save_state
.
This procedure takes two parameters; firstly a co-expression whose results we wish to evaluate, and an instance of a class, util.State
, which controls which global values are actually saved and restored, as each result is generated.
Notionally, save_state
works like this :-
procedure save_state(e, state)
local v
state.swap()
while v := @e do {
state.swap()
suspend v
state.swap()
}
state.swap()
end
However, in practice its implementation is a little more involved, because it takes care not to dereference any variables generated by e
. The above implementation unfortunately does dereference when it stores the result of the @e
in the temporary variable, v
. In any case, the swap()
method is called just before, and just after, e
is activated.
Here is an example program showing how a global variable g
can be saved and restored using save_state
:-
import io, util
global g
class MyGlobalState(State)
private tg
public override swap()
g :=: tg
end
#
# i gives the initial value seen by the co-expression we're evaluating
#
public new(i)
self.tg := i
return
end
end
procedure other()
local i
write("other begin: g=", g)
every i := 1 to 5 do {
g +:= 1
suspend i
write("other loop: g=", g)
}
write("other end: g=", g)
end
procedure main()
local i, e
e := create other()
g := 300
write("main begin: g=", g)
every i := save_state(e, MyGlobalState(100)) do {
g -:= 1
write("main loop: i=", i, " g=", g)
}
write("main end: g=", g)
end
The output is :-
main begin: g=300
other begin: g=100
main loop: i=1 g=299
other loop: g=101
main loop: i=2 g=298
other loop: g=102
main loop: i=3 g=297
other loop: g=103
main loop: i=4 g=296
other loop: g=104
main loop: i=5 g=295
other loop: g=105
other end: g=105
main end: g=295
If you follow through what save_state
does, in conjunction with MyGlobalState
, you will see that just before e
is invoked by save_state
for the first time, the value of g
is exchanged with the temporary tg
, which has been initialized to the value i
. Then, as each value is generated by e
, the (potentially changed) value of g
is saved, and the old value is restored. The generated value is suspended. Should resumption occur, the same thing happens again, only in reverse, and the loop continues. Finally, e
is exhausted, and the saved value is restored for a final time.
In this way, e
, effectively has its own copy of g
, which it can read and write consistently between generating results.
There is a useful class, util.VarState
, which in fact can save us from writing a class like MyGlobalState
. It takes a co-expression which generates a global variable to save and restore, and an optional initial value. We could have used this in main
above as follows :-
every i := save_state(e, VarState(create g, 100)) do {
...
}
save_state
is particularly useful when an expression may exit via a co-expression activation that doesn’t return. One example would be the throwing of an exception. For example,
import io, exception
procedure main()
"outer subject" ? {
tab(3)
try1{{
"inner subject" ? {
tab(5)
throw("Some problem")
}
}} | write("Exception occurred: ", &why)
write("Now &subject=", image(&subject), " &pos=", &pos)
}
end
when run this produces :-
Exception occurred: Some problem
Now &subject="inner subject" &pos=5
Note how the values of &subject
and &pos
have “escaped” from the inner scanning expression, overwriting the values in the outer scan.
This can easily be solved by enclosing the try1
in a call to save_state
, and using util.ScanState
, a State
that saves &subject
and &pos
.
...
save_state(create try1{{
"inner subject" ? {
tab(5)
throw("Some problem")
}
}} | write("Exception occurred: ", &why)
, ScanState())
...
This leaves &subject
and &pos
set to their correct values.
Another example is the reverting of an io.Task
to its scheduler. For example, consider a Task
like the following :-
t := Task(scheduler, create {{
"some subject" ? {
tab(5)
...
t.sleep(100)
...
}
}})
In this example, the call to sleep
will activate the scheduler, leaving &subject
and &pos
with the local values. By the time the scheduler re-activates the Task
, &subject
and &pos
may well have changed, and their values will not be what the Task
expects.
To address this problem, Task
accepts an optional third parameter, giving a State
, which is used to save and restore state each time the task switches to and from the scheduler. So in this case we would need to change the above to
t := Task(scheduler, create {{
... as above ...
}}, ScanState())
For more information on schedulers, see here.
State
classesThere are various State
subclasses for saving the common global keywords and variables; see the list of subclasses in State
’s documentation page.
Most notably there is util.States
, which brings together several State
s into one State
. So, if we wanted a state to save the scanning state as well as global variables g1
and g2
, we could use :-
States(ScanState(), VarState{g1}, VarState{g2})
Contents