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?
I guess something like this can also be done:
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
children
becomes a lazy value and the propertyChildren
accesses 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
Child
tries to access theChildren
collection 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
:I think that Tomas's answer is the way to go. However, for completeness I'll show how you could use recursive records to create cyclic immutable objects. This can potentially get quite ugly, so I've hidden the immutable record implementation behind some nicer properties: