我有一个记录:
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;。
首先,我是以正确的方式做这件事(我的意思是,不考虑可变选择)?我该如何纠正这一点。
答案 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 。
不要这样做。好吧,好吗?回到光明的一面,我们也有饼干。