F#:可以引用自身的递归值

Aym*_*udi 4 recursion f#

我有一个记录:

type node = {
               content : string;
               parent : node option;
               branch : string option;
               children : seq<node> option;
            }
Run Code Online (Sandbox Code Playgroud)

我想以这种方式实例化:

let rec treeHead = {
                       content = "Value"
                       parent = None;
                       branch = None;
                       children = tree (Some(treeHead)) rows;
                   };
Run Code Online (Sandbox Code Playgroud)

哪里

let rec tree (parent:node option) (rows:seq<CsvRow>) :seq<node> option
Run Code Online (Sandbox Code Playgroud)

是一个获取节点子节点(构造树)的递归函数.因此,您可以看到对象treeHead需要通过树函数调用自身.

我想以这种方式避免使用treeHead作为可变值并在之后更改其子属性.

我的问题是treeHead会引发错误和警告.错误说:

值'treeHead'将作为其自己定义的一部分进行评估.

警告说:

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

首先,我是以正确的方式做到这一点(我的意思是,不考虑可变选择)?我该如何纠正这个问题.

Fyo*_*kin 6

具有不可变数据结构的东西是,它们是不可变的.(< - 一些Yoda级别的东西就在那里)

使用可变数据结构,您首先创建一个框,然后开始将内容放入框中.你输入的其中一件事可能是对盒子本身的引用.由于你已经有一个盒子,没关系,你可以参考一下.

使用不可变的数据结构,这不起作用:您必须枚举在框甚至存在之前进入框中的所有内容!因此盒子本身不可能是这些东西之一.这就是编译器在错误消息中告诉你的:the value foo would be evaluated as part of its own definition.做不到.厄运.

如果只是你可以将参考文件包装起来,以便不立即评估它,而是将评估推迟到以后,直到盒子完全构造......我知道!把它关在一起!

let getMe42 x = 42

type R1 = { r1: int }
let rec x1 = { r1 = getMe42 x1 }  // Doesn't work: the definition of `x1` references `x1` itself

type R2 = { r2: unit -> int }
let rec x2 = { r2 = fun() -> getMe42 x2 } // This works: the definition of `x2` only creates a _closure_ around `x2`, but doesn't reference it right away
Run Code Online (Sandbox Code Playgroud)

好的,所以如果我可以创建一个lambda,也许我可以通过将lambda传递给另一个函数并立即调用它来欺骗编译器?

let getMe42 f = printfn "%O" <| f(); 42

type R = { r: int }
let rec x = { r = getMe42 (fun () -> x) }

> System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
Run Code Online (Sandbox Code Playgroud)

该死的!这支部队的力量很强大.即使编译器无法证明我在编译期间顽皮,它也巧妙地将初始化包装起来Lazy<T>,因此我在运行时遇到错误.

有趣的是,在仅插入直接自引用的非常有限的情况下,没有复杂的处理,它可以工作:

type R = { r: R }
let rec x = { r = x }  // Works just fine, not even a warning.
Run Code Online (Sandbox Code Playgroud)

你甚至可以一直去海龟:

type R = { r: R }
let rec x = { r = { r = { r = { r = x } } } }  // Still works!
Run Code Online (Sandbox Code Playgroud)

这是一个非常特殊的案例,对于我的存在,我看不出合理的理由.它起作用,因为F#编译支持字段internal,而不是private,它允许它在构造记录后改变字段.一个必然结果是,这只能在定义类型的同一个程序集中工作.


递归值是暗魔法功能之一,需要您将灵魂分成两部分才能使用.我认为正确的术语是hackrux.

不要这样做.只是不,好吗?回到光明的一面,我们也有饼干.