В F # очень важно, что они не имеют нулевых значений и не хотят его поддерживать. Тем не менее программист должен делать случаи для None, аналогичные программистам на С#, которые должны проверить!= Null.
Является ли кто-нибудь менее злым, чем null?
В F # очень важно, что они не имеют нулевых значений и не хотят его поддерживать. Тем не менее программист должен делать случаи для None, аналогичные программистам на С#, которые должны проверить!= Null.
Является ли кто-нибудь менее злым, чем null?
Конечно, это менее зло!
Если вы не проверяете None, то в большинстве случаев у вас будет ошибка типа в вашем приложении, что означает, что она не будет компилироваться, поэтому она не может сбой с помощью NullReferenceException (поскольку None переводит на null).
Например:
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
По-прежнему возможно достичь поведения, подобного С#, но оно менее интуитивно, поскольку вы должны явно сказать "игнорировать, что это может быть None":
let o = myObject.Value // throws NullReferenceException if myObject = None
В С# вы не обязаны рассматривать случай, когда ваша переменная имеет значение null, поэтому возможно, что вы просто забыли сделать чек. Тот же пример, что и выше:
var myObject = GetObjectToUse(); // you get back a nullable type
myObject.DoSomething() // no type error, but a runtime error
Изменить: Стивен Свенсен абсолютно прав, мой примерный код имел некоторые недостатки, писал его в спешке. Исправлена. Спасибо!
Проблема с null
заключается в том, что у вас есть возможность использовать ее почти везде, т.е. ввести недопустимые состояния, где это не предназначено и не имеет смысла.
Наличие 'a option
всегда является явным. Вы заявляете, что операция может либо выражать значение Some
значимое значение, либо None
, которое компилятор может обеспечить, чтобы его проверяли и обрабатывали правильно.
Отказываясь null
в пользу типа 'a option
, вы в основном гарантируете, что любое значение в вашей программе каким-то образом имеет смысл. Если какой-либо код предназначен для работы с этими значениями, вы не можете просто передавать недопустимые, а если есть функция типа option
, вам придется покрыть все возможности.
Скажем, я покажу вам определение функции следующим образом:
val getPersonByName : (name : string) -> Person
Как вы думаете, что происходит, когда вы передаете name
человека, которого нет в хранилище данных?
Не прочитав код (если у вас есть к нему доступ), прочитав документацию (если кто-то был достаточно любезен, чтобы написать ее) или просто вызвал функцию, у вас нет способа узнать. И это в основном проблема с нулевыми значениями: они выглядят и действуют так же, как ненулевые значения, по крайней мере до времени выполнения.
Теперь скажем, что у вас есть функция с этой сигнатурой:
val getPersonByName : (name : string) -> option<Person>
Это определение делает очень ясным, что происходит: вы либо вернете человека, либо не хотите, и эта информация передается в виде данных функции. Обычно у вас есть лучшая гарантия обработки обоих случаев типа опции, чем потенциально нулевое значение.
Я бы сказал, что типы опций гораздо более доброжелательны, чем нули.
В F # очень важно, что они не имеют нулевых значений и не хотят его поддерживать. Тем не менее программист должен делать случаи для None, аналогичные программистам на С#, которые должны проверить!= Null.
Является ли кто-нибудь менее злым, чем null?
В то время как null
вводит потенциальные источники ошибок во время выполнения (NullRefereceException
) каждый раз, когда вы разыскиваете объект на С#, None
заставляет вас явно указывать источники ошибок во время выполнения в F #.
Например, вызов GetHashCode
для данного объекта приводит к тому, что С# тихо вводит источник ошибки во время выполнения:
class Foo {
int m;
Foo(int n) { m=n; }
int Hash() { return m; }
static int hash(Foo o) { return o.Hash(); }
};
Напротив, эквивалентный код в F # должен быть null
свободен:
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>[email protected]()
Stopped due to error