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?
[Child(parent); Child(parent); Child(parent)] |> List.iter children.Add
could be reduced to[Child(parent); Child(parent); Child(parent)] |> children.AddRange
. Personally, I use the approach you're demonstrating a lot when usingReadOnlyCollection<>
. – HokkuReadOnlyCollection
feels hacky to me. Throwing an exception when callingAdd
isn't quite the behavior I'd like. I'd rather theAdd
method not be there. – Quondamdict
. :-] – Hokkudict
returning anIDictionary<'K, 'V>
likely has more to do with compatibility than the lack of good alternatives. – QuondamReadOnlyCollection<'T>
, given that it implementsIList<'T>
. In any case, we digress... ;-] – HokkuParent
necessary? If true immutability is important, you can use records to create cycles in immutable object graphs (this can get a bit ugly, though). – GrammeParent
implements an interface. (Correction: just discovered records can implement interfaces--that's news to me). – QuondamMap<'k,'v>
count as a built-in alternative todict
? – LustralMap<>
is immutable because it returns a newMap<>
when you callAdd
,Remove
, etc., leaving the original unchanged;dict
returns anIDictionary<>
which is immutable because it throws when you attempt to alter it by callingAdd
,Remove
, etc. – HokkuParent
a normal class with a constructor takingChild seq
and pass inSeq.readonly children
sincechildren
is mutable andSeq.readonly
will use itsEnumerator
under-the-hood. – Crony