25

考虑:

> Unchecked.defaultof<list<int>>;;
val it : int list = null
> 2 :: 1 :: Unchecked.defaultof<list<int>>;;
val it : int list = [2]

这种行为背后的原因是什么?我希望最终列表是 [2;1] 或引发异常。

4

1 回答 1

29

这是一个非常了不起的问题。我查看了 F# 列表的编译方式并解释了行为。我不会认为这是错误(这就是为什么defaultofUnchecked模块中,它可能会烧毁您的计算机!)

首先,空列表[]不像nullF# 2.0 中那样表示,因为那样您将无法在空列表上调用方法 - 这会破坏很多事情(例如将空列表传递给任何Seq函数)。顺便说一句,该None表示null效率,这意味着即使它有意义option<'a>也无法实现接口。seq<'a>

因此,F# 使用特殊值list.Empty来表示空列表。现在该值是与表示非空列表的值相同类型的实例(类型为FSharpList<'T>)。唯一的区别是实例的headtail字段都是null.

现在,您也许可以看到这是怎么回事 - 一个空列表本质上被编译为该字段所在的对象this.tailnull并且该this.head字段也是null如此,但这并不重要)。

当字段为时,该list.Tail属性将引发异常(因为这意味着您this.tail正在null获得一个空列表的尾部 - 这是一个错误),但还有一个内部属性list.TailOrNull无需检查即可返回值并用于编译模式匹配。

例如,这是一个简单的iter函数:

let rec iter (xs:int list) = 
  match list with
  | x::xs -> Console.WriteLine(x); iter xs
  | [] -> ()

这被编译为以下 C# 代码:

if (list.TailOrNull != null) {
  Console.WriteLine(list.HeadOrDefault);
  iter(list.TailOrNull);
}

这通过检查其是否为来检查是否为list空列表。如果是,则它知道当前值是表示空列表的值(并且它不打印它,因为它假设这也是)。list.TailOrNullnulllistlist.EmptyHeadOrDefaultnull

如果您使用显式defaultof创建null值,则为42::null您提供一个对象 where TailOrNullis nullbut HeadOrDefaultis 42。这就是为什么不打印值的原因。

TL;DR - 不要Unchecked.defaultof与 F# 类型一起使用。你永远不知道会发生什么。

于 2012-11-23T01:01:26.787 回答