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

Unchecked.defaultof в списке

Рассмотрим:

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

Каковы причины такого поведения? Я ожидаю, что окончательный список будет [2; 1] или вызовет исключение.

4b9b3361

Ответ 1

Это довольно изумительный вопрос. Я посмотрел, как списки F # скомпилированы и объясняет поведение. Я бы не счел это ошибкой (поэтому defaultof находится в модуле Unchecked, он может также сжечь ваш компьютер!)

Прежде всего, пустой список [] не представлен как null в F # 2.0, потому что тогда вы не сможете вызывать методы в пустом списке - и это может сломать многие вещи (например, передать пустой список любая функция Seq). В стороне, значение None представлено как null для эффективности, что означает, что option<'a> не может реализовать интерфейс seq<'a>, хотя это имеет смысл.

Поэтому вместо этого F # использует специальное значение list.Empty для представления пустых списков. Теперь это значение является экземпляром того же типа, что и значения, представляющие непустой список (тип FSharpList<'T>). Единственное отличие состоит в том, что оба поля head и tail экземпляра null.

Теперь вы можете видеть, где это происходит - пустой список по существу компилируется для объекта, где поле this.tail равно null (и поле 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
  | [] -> ()

Это скомпилировано на следующий код С#:

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 - 42. Поэтому значение не печатается.

TL; DR. Не используйте Unchecked.defaultof для типов F #. Вы никогда не знаете, что произойдет.