考虑:
> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]
这种行为背后的原因是什么?我希望最终列表为[2; 1]或引发异常。
答案 0 :(得分:28)
这是一个非常了不起的问题。我看了F#列表是如何编译的,它解释了行为。我不认为这是错误(这就是defaultof
模块中Unchecked
的原因,它可能会烧毁你的计算机!)
首先,空列表[]
在F#2.0中没有表示为null
,因为那样你就无法在空列表上调用方法 - 这会破坏很多东西(比如将空列表传递给任何Seq
函数)。另外,None
值 表示为null
以提高效率,这意味着option<'a>
无法实现seq<'a>
接口,即使它会有道理。
因此,F#使用特殊值list.Empty
来表示空列表。现在,此值是与表示非空列表的值相同类型的实例(类型为FSharpList<'T>
)。唯一的区别是实例的head
和tail
字段都是null
。
现在,您可以看到它的位置 - 空列表基本上编译为this.tail
字段为null
的对象(this.head
字段为{{1也是,但这并不重要)。
当null
字段为list.Tail
时,this.tail
属性将抛出异常(因为这意味着您获得了空列表的尾部 - 这是一个错误),但是还有一个内部属性null
,它返回值而不进行检查,并用于编译模式匹配。
例如,这是一个简单的list.TailOrNull
函数:
iter
这是编译为以下C#代码:
let rec iter (xs:int list) =
match list with
| x::xs -> Console.WriteLine(x); iter xs
| [] -> ()
通过检查if (list.TailOrNull != null) {
Console.WriteLine(list.HeadOrDefault);
iter(list.TailOrNull);
}
是否为list
来检查list.TailOrNull
是否为空列表。如果是,则它知道当前null
值是表示空列表的list
值(并且它不打印其list.Empty
,因为它假定这是HeadOrDefault
太)。
如果您使用null
明确创建defaultof
值,则null
会为您提供42::null
为TailOrNull
但null
为HeadOrDefault
的对象{1}}。这就是为什么不打印这个值的原因。
TL; DR - 请勿将42
与F#类型一起使用。你永远不知道会发生什么。