Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy recursive F# record types

Tags:

Assume u have the following recursive record type

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

I can easily create instances 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 "with" like that:

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 object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning may be suppressed by using '#nowarn "40"' or '--nowarn:40'.

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

What am i doing wrong?

Edit:

One more point: when i move the "Builder" (which worked) into a different assembly (e.g. the test assembly) it immediately stops working with:

error FS0261: Recursive values cannot be directly assigned to the non-mutable field 'Children' of the type 'Parent' within a recursive binding. Consider using a mutable field instead.

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 usecase similar to the working one :(

like image 584
plainionist Avatar asked Dec 18 '17 12:12

plainionist


1 Answers

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

You can sometimes just ignore the warning, but in your case, the values are actually mutually dependent, so the following gives an error:

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

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

One way to introduce some form of delay is to change the parent to include children as a lazy sequence seq<'T> rather than a fully evaluated list 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 (to keep things 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 the warning (which you can turn off), but the following works and creates a recursive value:

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

As an aside, I think it is probably better to avoid recursive values - I suspect that one way reference (parent referencing children, but not the other way round) would let you do what you need - and your code would likely be simpler.

like image 101
Tomas Petricek Avatar answered Sep 23 '22 13:09

Tomas Petricek