Contents

Saving and restoring global state

Introduction

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 :-

Download saveglobal.icn

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.

VarState

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 {
          ...
    }

Uses

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.

Other State classes

There 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 States 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