Criticism of immutable classes with circular link design and better options

I have a factory class that creates objects with circular references. I would like them to be unchanged (in a sense, this word). Therefore, I use the following method, using sort closure:

[<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 it better than the internal AddChild method, because there is a stronger guarantee of immutability. It may be a neurotic, but I prefer closure for access control.

Are there any pitfalls in this project? Are there any better, perhaps less cumbersome, ways to do this?

+6
source share
3 answers

You can use F # support for recursive initialization even when instantiating an abstract class:

 let makeParent() = let rec children = seq [ Child(parent); Child(parent); Child(parent) ] and parent = { new Parent() with member __.Children = children } parent 

When compiling the code, F # uses lazy values, so the value of children becomes the lazy value, and the children property refers to the value of this lazy calculation. This is great because it can first instantiate the Parent (referring to the lazy value) and then actually build the sequence.

Performing the same action with the records will not work just as well, because none of the calculations will be postponed, but it works pretty well here, because when creating a Parent this sequence is actually not available (if it was a record, this will be the field to be evaluated).

The F # compiler cannot tell (in general) whether this is correct, therefore it gives a warning that can be disabled using #nowarn "40" .

In general, I think that using let rec .. and .. to initialize recursive values ​​is good - it is a bit limited (one of the links should be delayed), but it forces you to isolate the recursive links, and I think it simplifies your the code.

EDIT To add an example where this might go wrong - if the Child constructor tries to access the children collection of its parent, then it forces the lazy value to be evaluated before it can be created, and you get a runtime error (which is what the warning says). Try adding this to the Child constructor:

 do printfn "%d" (Seq.length parent.Children) 
+9
source

I think Thomas's answer is the way to go. However, for completeness, I will show how you could use recursive records to create circular immutable objects. This can get pretty ugly, so I hid the implementation of immutable writing behind some more nice features:

 type Parent = internal { children : Children option } and internal Children = { first : Child; rest : Children option } and Child = internal { parent : Parent } let rec internal listToChildren = function | [] -> None | c::cs -> Some { first = c; rest = listToChildren cs } let rec internal childrenToList = function | None -> [] | Some { first = c; rest = cs } -> c::(childrenToList cs) module Factory = let makeParent() = let rec parent = { children = children } and child1 = { parent = parent } and child2 = { parent = parent } and child3 = { parent = parent } and children = [child1; child2; child3] |> listToChildren parent type Parent with member p.Children = childrenToList p.children type Child with member c.Parent = c.parent 
+4
source

I think something like this can also be done:

 type ParentFactory private (n) as X = inherit Parent() let childs = [for i=1 to n do yield Child(X :> Parent)] override X.Children = childs |> List.toSeq; static member Create n = (new ParentFactory(n)) :> Parent 
0
source

Source: https://habr.com/ru/post/894874/


All Articles