Подтвердить что ты не робот

Почему None представлен как null?

CompilationRepresentationFlags.UseNullAsTrueValue можно использовать для

Разрешить использование null в качестве представления для нулевых дискриминаторов в разграниченном объединении

Option.None является наиболее ярким примером этого.

Почему это полезно? Как нулевая проверка лучше, чем традиционный механизм проверки случаев объединения (сгенерированное свойство Tag)?

Это приводит к неожиданному поведению:

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

ИЗМЕНИТЬ

Я тестировал утверждение Джека, что сравнение с нулевым, а не статическим полем readonly выполняется быстрее.

[<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
  | _ -> ()

Результаты:

Реал: 00: 00: 00.036, CPU: 00: 00: 00.046, GC gen0: 0, gen1: 0, gen2: 0

Если атрибут CompilationRepresentation удален, t становится статическим полем readonly:

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();

И результаты те же:

Реал: 00: 00: 00.036, CPU: 00: 00: 00.031, GC gen0: 0, gen1: 0, gen2: 0

Соответствие шаблону компилируется как t == null в первом случае и t is Z в последнем.

4b9b3361

Ответ 1

Ответ на ответ кажется хорошим, но немного расширяться, на уровне IL CLR предоставляет конкретный код операции для загрузки нулевых значений (ldnull) и эффективных средств тестирования для них (ldnull, за которым следует beq/bne.un/ceq/cgt.un). Когда JITted, они должны быть более эффективными, чем разыменование свойства Tag и ветвление соответственно. В то время как экономия за счет звонков, вероятно, небольшая, типы опций используются достаточно часто, чтобы накопительная экономия могла быть значительной.

Конечно, как вы заметили, есть компромисс: методы, унаследованные от obj, могут вызывать ненужные ссылочные исключения. Это одна из веских причин использовать string x/hash x/x=y вместо x.ToString()/x.GetHashCode()/x.Equals(y) при работе с значениями F #. К сожалению, нет (возможного) эквивалента x.GetType() для значений, представленных null.

Ответ 2

Компилятор F # иногда использует null как представление для None, потому что он более эффективен, чем фактически создает экземпляр FSharpOption < T и проверяет свойство Tag.

Подумайте об этом - если у вас есть обычный тип F # (например, запись), который не может быть пустым, то любой указатель на экземпляр этого типа (указатель, используемый внутри CLR) никогда не будет null. В то же время, если T - тип, который может представлять состояния n, тогда T option может представлять состояния n+1. Таким образом, использование null в качестве представления для None просто использует одно дополнительное значение состояния, которое доступно тем фактом, что типы F # не могут быть пустыми.

Если вы хотите отключить это поведение (для обычных типов F #), вы можете применить к ним [<AllowNullLiteral(true)>].