Mutable reference cells don't work with static members

130 Views Asked by At

I think I understand what's happening here, but I'd really appreciate someone throwing some light on why it's happening.

Basically (and I'm coding on the fly here, to give the simplest possible illustration) I have a class S which, among other things, maintains a symbol table relating names to objects of the class. So, inside the class I define:

static member (names:Map<string,S> ref) = ref Map.empty

... and then later on, also in S, I define:

member this.addSymbol (table: Map<string,S> ref) (name:string)
    table := (!table).Add(name, this)
    this

I call this from elsewhere, thus:

ignore (s.addSymbol S.names "newname")
...

Inside S.addSymbol I can see the new key/value being added to table, but S.names doesn't get updated. If, instead, I call addSymbol like this:

ignore (
    let tmp = S.names
    s.addSymbol tmp "newName"
)

then I can see tmp being updated with the new key/value pair, but S.names still doesn't get updated.

Here's what I think I know: there's nothing wrong with the ref mechanism - that's updating the referent correctly both inside and outside the function. But there seems to be something odd about the static member - it's like, rather than just handing me the same static ref 'a each time, it's copying the 'a and then creating a new ref for that.

Have I got this right? If so, why is it doing it? And what can I do to make it behave more sensibly?

Thanks in advance.

1

There are 1 best solutions below

3
On BEST ANSWER

The static member names is a property, not a static value. It is, internally, a function that creates a new map every time it is called.

If the value has to be in the class, use static let to define it inside the class. That way, the reference cell is only created once.

type S () =
    static let names = ref Map.empty : Map<string, S> ref
    static member Names = names

S.Names := ["Don", S()] |> Map.ofList
S.Names // gives map with one element

Oftentimes, the class doesn't expose the reference cell itself, but rather members to read, add, or remove names. Then, Names would return !names -- or names would be a plain mutable instead of a reference cell -- and other methods like AddName would alter the value. If there is no need for such encapsulation, see kvb's comment for how to shorten this example to only one member. (Thanks for the hint!)


Alternative: Normally, this is a case in which F# modules are used. Just define

let names = ref Map.empty : Map<string, S> ref

or

let mutable names = Map.empty : Map<string, S>

in a module, with additional access restrictions or encapsulation as required.