Никто не менее злой, чем ноль? - PullRequest
27 голосов
/ 17 декабря 2010

В F # большое значение имеет то, что они не имеют нулевых значений и не хотят их поддерживать.Тем не менее, программист должен выдвигать случаи для None, аналогичные тем, что программисты на C # должны проверять! = Null.

Является ли None действительно менее злым, чем null?

Ответы [ 4 ]

35 голосов
/ 17 декабря 2010

Проблема с null состоит в том, что у вас есть возможность использовать его почти везде , то есть вводить недопустимые состояния, когда это не предназначено или не имеет смысла .

Наличие 'a option всегда явная вещь.Вы утверждаете, что операция может выдавать Some значимое значение или None, что компилятор может принудительно проверять и обрабатывать правильно.

Отговаривая null в пользу'a option -типа, у вас есть гарантия того, что любое значение в вашей программе имеет какое-то значение.Если какой-то код предназначен для работы с этими значениями, вы не можете просто передать недопустимые значения, и если есть функция типа option, у вас будет иметь , чтобы охватить все возможности.

31 голосов
/ 17 декабря 2010

Конечно, это меньше зла!

Если вы не проверяете None, то в большинстве случаев в вашем приложении будет ошибка типа, что означает, что оно не будет компилироваться, поэтому не может произойти сбой с NullReferenceException (так как None переводит в ноль).

Например:

let myObject : option<_> = getObjectToUse() // you get a Some<'T>, added explicit typing for clarity
match myObject with
| Some o -> o.DoSomething()
| None -> ... // you have to explicitly handle this case

Все еще возможно достичь C # -подобного поведения, но оно менее интуитивно понятно, так как вы должны явно сказать «игнорируйте, что это может быть None»:

let o = myObject.Value // throws NullReferenceException if myObject = None

В C # вы не обязаны рассматривать случай, когда ваша переменная равна нулю, поэтому вполне возможно, что вы просто забудете сделать проверку. Тот же пример, что и выше:

var myObject = GetObjectToUse(); // you get back a nullable type
myObject.DoSomething() // no type error, but a runtime error

Редактировать : Стивен Свенсен абсолютно прав, у моего примера кода были некоторые недостатки, он писал его в спешке. Исправлена. Спасибо!

14 голосов
/ 17 декабря 2010

Допустим, я покажу вам определение функции следующим образом:

val getPersonByName : (name : string) -> Person

Как вы думаете, что происходит, когда вы передаете name человека, которого нет в хранилище данных?

  • Выдает ли функция исключение NotFound?
  • Возвращает ли оно ноль?
  • Создает ли он человека, если он не существует?

За исключением чтения кода (если у вас есть доступ к нему), чтения документации (если кто-то был достаточно любезен, чтобы написать его) или просто вызова функции, у вас нет возможности узнать. И это в основном проблема с нулевыми значениями: они выглядят и действуют как ненулевые значения, по крайней мере, до времени выполнения.

Теперь предположим, что у вас есть функция с этой подписью:

val getPersonByName : (name : string) -> option<Person>

Это определение делает очень явным то, что происходит: вы либо вернете человека, либо не получите, и такая информация передается в типе данных функции. Обычно , у вас есть лучшая гарантия обработки обоих случаев типа параметра, чем потенциально нулевое значение.

Я бы сказал, что типы опций гораздо более доброжелательны, чем нули.

3 голосов
/ 14 августа 2011

В F # важно, что они не имеют нулевых значений и не хотят их поддерживать.Тем не менее, программист должен выдвигать случаи для None, аналогичные тем, что программисты на C # должны проверять! = Null.

Является ли None действительно менее злым, чем ноль?ошибки времени выполнения (NullRefereceException) каждый раз, когда вы разыменовываете объект в C #, None заставляет вас указывать источники ошибки времени выполнения в F #.

Например, вызывая GetHashCodeдля данного объекта заставляет C # молча внедрить источник ошибки времени выполнения:

class Foo {
  int m;
  Foo(int n) { m=n; }
  int Hash() { return m; }
  static int hash(Foo o) { return o.Hash(); }
};

Напротив, эквивалентный код в F # ожидается null free:

type Foo =
  { m: int }
  member foo.Hash() = foo.m

let hash (o: Foo) = o.Hash()

Если вы действительно хотите получить необязательное значение в F #, тогда вы должны использовать тип option, и вы должны явно обработать его, иначе компилятор выдаст предупреждение или ошибку:

let maybeHash (o: Foo option) =
  match o with
  | None -> 0
  | Some o -> o.Hash()

Вы все еще можете получить NullReferenceException в F # путем обхода системы типов (которая требуется для взаимодействия):

> hash (box null |> unbox);;
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.UnboxGeneric[T](Object source)
   at <StartupCode$FSI_0021>.$FSI_0021.main@()
Stopped due to error
...