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

时间:2017-02-14 04:03:05

标签: recursion f#

我有一个记录:

type node = {
               content : string;
               parent : node option;
               branch : string option;
               children : seq<node> option;
            }

我想以这种方式实例化:

let rec treeHead = {
                       content = "Value"
                       parent = None;
                       branch = None;
                       children = tree (Some(treeHead)) rows;
                   };

哪里

let rec tree (parent:node option) (rows:seq<CsvRow>) :seq<node> option

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

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

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

  

值&#39; treeHead&#39;将作为其定义的一部分进行评估。

警告说:

  

对正在定义的对象的此递归引用和其他递归引用   将在运行时检查初始化 - 健全性   使用延迟参考。这是因为你正在定义一个或   更多递归对象,而不是递归函数。这个警告   可以使用&#39; #nowarn&#34; 40&#34;&#39;或者&#39; - nowarn:40&#39;。

首先,我是以正确的方式做这件事(我的意思是,不考虑可变选择)?我该如何纠正这一点。

1 个答案:

答案 0 :(得分:6)

具有不可变数据结构的东西是,它们是不可变的。 (&lt; - 一些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

好吧,如果我可以创建一个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.

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

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

type R = { r: R }
let rec x = { r = x }  // Works just fine, not even a warning.

你甚至可以一路前往海龟:

type R = { r: R }
let rec x = { r = { r = { r = { r = x } } } }  // Still works!

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

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

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