A requirement of the Actor model in Fantom is that any instance variables are constant and immutable. There is a workaround, which is 'sys::Unsafe': this is a wrapper class, which tells anyone that asks that it is immutable. To use, you simply pass the object you want wrapped in its constructor, and use 'val' (and a cast) to retrieve the value.
Here's a little 'fansh' session to illustrate:
Fantom Shell v1.0.59 ('?' for help) fansh> a := [1,2,3] [1, 2, 3] fansh> a.isImmutable false fansh> b := Unsafe (a) fan.sys.Unsafe@406199 fansh> b.isImmutable true fansh> b.val [1, 2, 3] fansh> (b.val as List).add (4) [1, 2, 3, 4] fansh> b.val [1, 2, 3, 4]
We can see that 'b' holds a reference to the list, and can even be asked to update the value of the list, but reports to the outside world that it is immutable.
We can use Unsafe to provide the equivalent of mutable instance variables in the Worker class I used in my last post. Below is the complete code for the same example, but now using Unsafe references instead of Actor.locals.
Apart from the cast needed on lines 45 and 50, this now looks more like the SwingWorker example, using instance variables to hold references to the GUI widgets. We can now even refer to the GUI widgets in the 'receive' method, which we could not do using the Actor.locals method: see line 33.
01: using concurrent 02: using fwt 03: 04: class Calculate 05: { 06: static Int fib (Int n) 07: { 08: if (n < 2) 09: return 1 10: else 11: return (fib (n-2) + fib (n-1)) 12: } 13: } 14: 15: const class Worker : Actor 16: { 17: const Unsafe result 18: const Unsafe button 19: 20: new make (Unsafe result, Unsafe button) : super (ActorPool ()) 21: { 22: this.result = result 23: this.button = button 24: } 25: 26: override Obj? receive (Obj? msg) 27: { 28: // we only care about one message - a number to compute from 29: n := msg as Int 30: if (n != null) 31: { 32: // we can access our Unsafe variables here! 33: Desktop.callAsync |->| { (button.val as Button).text = "working!" } 34: // do the actual computation 35: startTime := DateTime.now 36: res := Calculate.fib (n) 37: // call the 'update' method on the GUI thread 38: Desktop.callAsync |->| { update(n, res, DateTime.now - startTime) } 39: } 40: return null 41: } 42: 43: Void update (Int n, Int res, Duration time) 44: { 45: (result.val as Label).with 46: { 47: text = "Fib($n) = $res in $time" 48: pack 49: } 50: (button.val as Button).with 51: { 52: text = "compute" 53: enabled = true 54: pack 55: } 56: } 57: } 58: 59: class Main 60: { 61: static Void main () 62: { 63: chooseN := Text { text = "1" } 64: result := Label () 65: button := Button { text = "compute" } 66: button.onAction.add |Event e| 67: { // when button clicked 68: button.with 69: { 70: text = "Busy" 71: enabled = false 72: } 73: Worker(Unsafe(result), Unsafe(button)).send (chooseN.text.toInt) 74: } 75: 76: Window 77: { 78: title = "Fibonacci" 79: GridPane // place widgets in a column 80: { 81: chooseN, 82: button, 83: result, 84: }, 85: }.open 86: } 87: } 88: 89: