复制递归F#记录类型

时间:2017-12-18 12:56:55

标签: recursion f# record

假设你有以下递归记录类型

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

我可以使用

轻松创建实例
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 }

但是,当我尝试使用“with”将现有成年人“转化”为父母时:

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 }

我明白了:

错误FS0040:通过使用延迟引用,将在运行时检查对正在定义的对象的此递归引用和其他递归引用的初始化 - 健全性。这是因为您定义了一个或多个递归对象,而不是递归函数。使用'#nowarn“40”'或'--nowarn:40'可以抑制此警告。

和“父级”定义中的“Children = children”会突出显示。

我做错了什么?

修改:

还有一点:当我将“Builder”(有效)移动到另一个组件(例如测试组件)时,它会立即停止使用:

错误FS0261:递归值不能直接分配给递归绑定中“父”类型的非可变字段“Children”。请考虑使用可变字段。

修改 基于我试过的评论

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

但仍然没有运气 - 编译器仍然没有看到这个类似于工作的用例:(

2 个答案:

答案 0 :(得分:4)

首先,您在问题中发布的消息只是一个警告 - 它告诉您只有在构造不立即评估整个值时才能初始化递归值(当第一个值取决于时,这不能完成在第二个,反之亦然)。

有时你可以忽略警告,但在你的情况下,这些值实际上是相互依赖的,所以下面给出了一个错误:

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

System.InvalidOperationException:ValueFactory试图访问此实例的Value属性。

引入某种形式延迟的一种方法是更改​​父级以将子级包含为惰性序列seq<'T>而不是完全评估的列表list<'T>

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

然后您还需要更改Builder2以使用Seq.map(以保持懒惰):

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

现在您仍然收到警告(可以关闭),但以下工作并创建递归值:

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

顺便说一下,我认为避免递归值可能更好 - 我怀疑单向引用(父引用子代,但不是反过来)会让你做你需要的 - 而你的代码很可能是简单。

答案 1 :(得分:0)

cartermp在此发现并发布了解决方案:

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

我在这里发表了一个复制品

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

当然,建议的解决方案就像一个魅力