2011-08-31: Sidestepping Immutability

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.

Unsafe Worker

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: 

Page from Peter's Scrapbook, output from a VimWiki on 2024-01-29.