Recursion 复制递归F#记录类型

Recursion 复制递归F#记录类型,recursion,f#,record,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

假设您具有以下递归记录类型

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”来抑制此警告

并突出显示“父项”定义中的“子项=子项”

我做错了什么

编辑:

还有一点:当我将“构建器”(工作)移动到不同的组件(例如测试组件)中时,它立即停止工作:

错误FS0261:递归值不能直接分配给递归绑定中类型为“Parent”的不可变字段“Children”。考虑使用可变字段代替.< /P> 编辑: 根据我试过的评论

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

但是仍然不走运-编译器仍然没有看到与工作用例类似的用例:(

首先,您在问题中发布的消息只是一个警告-它告诉您,如果构造没有立即计算整个值,您只能初始化递归值(如果第一个值取决于第二个值,则无法执行此操作,反之亦然)

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

Builder2.create "A" [Builder2.createChild "B"]
System.InvalidOperationException:ValueFactory试图访问此实例的Value属性

引入某种形式的延迟的一种方法是将父级更改为将子级作为延迟序列包含:

然后您还需要更改
Builder2
以使用
Seq.map
(使事情变得懒惰):

现在您仍然会收到警告(可以关闭该警告),但以下操作会起作用并创建一个递归值:

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

顺便说一句,我认为最好避免递归值-我怀疑单向引用(父引用子引用,但不是相反)可以让您做您需要的事情-并且您的代码可能会更简单。

cartermp在此处找到并发布了解决方案:

我在这里发表了一篇论文


当然,建议的解决方案就像一个符咒一样,首先:thx提示这只是一个警告-我一直打开“错误警告”,所以我一开始没有看到:)…我仍然不明白为什么我可以创建不带“with”但不带“with”的父/子实例关键字?从我的角度来看,更改为seq.不是一个好的选择,因为这会导致在我想要访问字段时延迟求值,对吗?@Plainiist我认为在第一种情况下,编译器可以分析您的代码,发现您只创建了两个记录-因此它最初将一个值设置为
null
,然后对其进行修补。在第二种情况下,您正在运行一些自定义代码,因此编译器无法通过
null
s安全地分析和处理这些代码。根据您的解释,我尝试了另一种方法-请参见第二个“编辑”。现在我原以为编译器可以“看到”它是相同的用例-仍然没有运气:(还有其他想法吗?最后,这意味着我不能以修改“父子”关系的方式复制递归记录结构,对吗?我在github提出了一个问题,以进一步澄清这一点
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"]