Copy recursive record types F #

Suppose u has the following recursive record type

type Parent = { Name : string Age : int Children : Child list } and Child = { Name : string Parent : Parent option } 

I can easily instantiate with

 module Builder = let create name kids = let rec makeChild kid = { kid with Parent = parent |> Some } and parent = { Name = name Age = 42 Children = children } and children = kids |> List.map makeChild parent let createChild name = { Child.Name = name; Parent = None } 

But when I try to "transform" an existing adult into a parent, using "c" as follows:

 module Builder2 = let createAdult name age = { Parent.Name = name; Age = age; Children = [] } let create name kids = let rec makeChild kid = { kid with Parent = parent |> Some } and parent = { (createAdult name 42) with Children = children } and children = kids |> List.map makeChild parent let createChild name = { Child.Name = name; Parent = None } 

I get:

error FS0040: this and other recursive references to the designated object will be checked for reliable initialization at runtime using a slow link. This is because you are defining one or more recursive objects, not recursive functions. This warning can be suppressed using "#nowarn" 40 "or" --nowarn: 40 ".

and "Children = children" in the "parent" definition are highlighted.

What am I doing wrong?

Edit:

Another point: when I move the "Builder" (which worked) to another assembly (for example, a test assembly), it immediately stops working with:

error FS0261: Recursive values โ€‹โ€‹cannot be directly bound to a non-variable field "Children" of type "Parent" in a recursive binding. Instead, consider using a mutable field.

Edit: Based on the comments I tried

 let create name kids = let rec makeChild kid = { kid with Parent = parent |> Some } and adult = createAdult name 42 and parent = { adult with Children = children } and children = kids |> List.map makeChild 

but still no luck - the compiler still does not see this utility, similar to the working one: (

+5
source share
2 answers

cartermp found and posted a solution here:

https://github.com/Microsoft/visualfsharp/issues/4201

I posted the repro here

https://github.com/plainionist/DevNull/tree/master/src/FSharpCopyRecordRecursive

and of course, the proposed solution works like a charm

0
source

First of all, the message you sent in your question is just a warning - it tells you that you can only initialize a recursive value, only if the construct does not immediately evaluate the entire value (this cannot be done when the first value depends on the second and vice versa) .

Sometimes you can simply ignore the warning, but in your case the values โ€‹โ€‹are actually interdependent, so the following message gives an error:

 Builder2.create "A" [Builder2.createChild "B"] 

System.InvalidOperationException: ValueFactory tried to access the Value property of this instance.

One way to introduce some form of delay is to change the parent to include the children as a lazy sequence of seq<'T> rather than a fully-valued list<'T> :

 type Parent = { Name : string Age : int Children : Child seq } and Child = { Name : string Parent : Parent option } 

Then you also need to change Builder2 to use Seq.map (so that everything is lazy):

 let create name kids = let rec makeChild kid = { kid with Parent = parent |> Some } and parent = { (createAdult name 42) with Children = children } and children = kids |> Seq.map makeChild 

Now you still get a warning (which you can disable), but the following works and creates a recursive value:

  let p = Builder2.create "A" [Builder2.createChild "B"] 

As an aside, I think it's probably best to avoid recursive values โ€‹โ€‹- I suspect that a link in one direction (parent links to children, but not vice versa) will allow you to do what you need - and your code will probably be easier.

+4
source

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


All Articles