为什么None表示为null?

时间:2012-08-14 19:22:37

标签: f# discriminated-union

CompilationRepresentationFlags.UseNullAsTrueValue可用于

  

允许使用null作为区分联合中的无效判别符的表示

Option.None就是最突出的例子。

为什么这有用?如何检查null检查比检查并集案例(生成的Tag属性)的传统机制更好?

这可能导致意外行为:

Some(1).ToString() //"Some(1)"
None.ToString()    //NullReferenceException

修改

我测试了Jack的断言,即与null相比,而不是静态只读字段更快。

[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type T<'T> =
  | Z
  | X of 'T

let t = Z

使用ILSpy,我可以看到t编译为null(如预期的那样):

public static Test.T<a> t<a>()
{
    return null;
}

测试:

let mutable i = 0
for _ in 1 .. 10000000 do
  match t with
  | Z -> i <- i + 1
  | _ -> ()

结果:

  

Real:00:00:00.036,CPU:00:00:00.046,GC gen0:0,gen1:0,gen2:0

如果删除CompilationRepresentation属性,t将成为静态只读字段:

public static Test.T<a> t<a>()
{
    return Test.T<a>.Z;
}

public static Test.T<T> Z
{
    [CompilationMapping(SourceConstructFlags.UnionCase, 0)]
    get
    {
        return Test.T<T>._unique_Z;
    }
}

internal static readonly Test.T<T> _unique_Z = new Test.T<T>._Z();

结果是一样的:

  

Real:00:00:00.036,CPU:00:00:00.031,GC gen0:0,gen1:0,gen2:0

模式匹配在前一种情况下编译为t == null,在后一种情况下编译为t is Z

2 个答案:

答案 0 :(得分:10)

F#编译器有时使用null作为None的表示,因为它比实际创建FSharpOption的实例更有效&lt;'T&gt;并检查Tag属性。

考虑一下 - 如果你有一个不允许为空的普通F#类型(如记录),那么任何指向该类型实例的指针(CLR内部使用的指针)将永远不会是{{ 1}}。同时,如果NULL是可以表示T个州的类型,那么n可以代表T option个州。因此,使用n+1作为None的表示只是利用了一个额外的状态值,这是因为F#类型不允许为空。

如果您想尝试关闭此行为(对于普通的F#类型),您可以对其应用null

答案 1 :(得分:6)

Jack的回答似乎很好,但为了扩展一点,在IL级别,CLR提供了一个特定的操作码来加载空值(ldnull)和有效的测试方法(ldnull关注beq / bne.un / ceq / cgt.un)。在JITted时,这些应该比取消引用Tag属性和相应的分支更有效。虽然每次呼叫节省可能很小,但选项类型的使用频率足以使累积节省可能很大。

当然,正如您所指出的那样存在权衡:从obj继承的方法可能会抛出空引用异常。在处理F#值时,这是使用string x / hash x / x=y代替x.ToString() / x.GetHashCode() / x.Equals(y)的一个很好的理由。遗憾的是,x.GetType()代表的值没有(可能)等效null