I have a factory class that creates objects with circular references. I’d like them to be immutable (in some sense of the word) too. So I use the following technique, using a closure of sorts:
[<AbstractClass>]
type Parent() =
abstract Children : seq<Child>
and Child(parent) =
member __.Parent = parent
module Factory =
let makeParent() =
let children = ResizeArray()
let parent =
{ new Parent() with
member __.Children = Seq.readonly children }
[Child(parent); Child(parent); Child(parent)] |> children.AddRange
parent
I like this better than an internal AddChild method because there’s a stronger guarantee of immutability. Perhaps it’s neurotic, but I prefer closures for access control.
Are there any pitfalls to this design? Are there better, perhaps less cumbersome, ways to do this?
You can use F#’s support for recursive initialization even when creating an instance of abstract class:
When compiling the code, F# uses lazy values, so the value
childrenbecomes a lazy value and the propertyChildrenaccesses the value of this lazy computation. This is fine, because it can first create instance ofParent(referencing the lazy value) and then actually construct the sequence.Doing the same thing with records wouldn’t work as nicely, because none of the computations would be delayed, but it works quite nicely here, because the sequence is not actually accessed when creating the
Parent(if it was a record, this would be a field that would have to be evaluated).The F# compiler cannot tell (in general) whether this is correct, so it emits a warning that can be disabled using
#nowarn "40".In general, I think that using
let rec .. and ..to initialize recursive values is a good thing – it is a bit limited (one of the references must be delayed), but it forces you to keep the recursive references isolated and, I think, it keeps your code simpler.EDIT To add an example when this may go wrong – if the constructor of
Childtries to access theChildrencollection of its parent, then it forces evaluation of the lazy value before it can be created and you get a runtime error (which is what the warning says). Try adding this to the constructor ofChild: