Если в C# есть понятие null для ссылочных типов и Nullabe для структур. Это может принять одну из следующих 2 форм (для демонстрации я использую тип int здесь, но тип может быть любой структурой).

  • Nullable
  • int?

Дисклаймер
Во всем тексте Nullabe означает Nullable<T> , просто парсер режет из-за кавычек, причем очень неадекватно


Они оба эквивалентны.

Класс Nullable предоставляет несколько вспомогательных свойств и методов, облегчающих работу с типами null и структурами. Это следующие:

  • HasValue
  • Value
  • GetValueOrDefault()
  • GetValueOrDefault(T)

В F# есть нечто немного отличающееся от этого, в форме типа Option, который является более дружественным к F# типом, который предпочитает не иметь дело с null, а предпочитает иметь дело с вещами с точки зрения «Может содержать значение типа» или «Может не иметь значение». Это звучит как Nullable, но в конце концов это тип F #, так что вы можете ожидать, что он будет нормально использоваться в вещах F#.

Еще одна вещь, которую стоит отметить с типом Option F#, — это то, что он может использоваться для любого типа, а не только для структур. Это отличается от .NET Nullable, который может использоваться только со структурами.

Значение None используется, когда значения не имеется; в противном случае выражение Some (…) присваивает значение. Значения Some и None, могут использоваться при сопоставлении с образцом, пример которого мы увидим в этом посте.

Как и Nullable, тип F# Option имеет несколько вспомогательных свойств / методов, которые показаны в таблице ниже.

None
‘T option
Статическое свойство, позволяющее создать значение параметра со значением None.
IsNone
bool
Возвращает true, если параметр имеет значение None.
IsSome
bool
Возвращает true, если параметр имеет значение, отличное от None.
Some
‘T option
Статический член, который создает параметр, значение которого не равно None.
Value
‘T
Возвращает базовое значение или выдает исключение NullReferenceException, если значение равно None.

Создание опций


Итак, теперь, когда мы знаем, что такое типы Option, как нам их создавать. Давайте посмотрим на некоторые примеры. Обратите внимание, что в этом примере я использовал вспомогательные методы IsSome / IsNone. Лично я считаю, что сопоставление с образцом — это лучший способ, так как он поможет вам сопоставить все случаи, включая вариант «Нет».

На самом деле, я покажу вам, как легко получить что-то не так, когда вы работаете с типами Option, если вы решите использовать вспомогательные методы, но сначала давайте рассмотрим пример правильного случая.

let someInt = Some(43)
let someString = Some("cat")
 
let printTheOption (opt :Option<'a>) =
    printfn "Actual Option=%A" opt
    printfn "IsNone=%b, IsSome=%b Value=%A\r\n\r\n" opt.IsNone opt.IsSome opt.Value
 
printfn "let someInt = Some(43)"
printfn "let someString = Some(\"cat\")"
printfn "printTheOption (someInt)"
printTheOption someInt
printfn "printTheOption (someString)"
printTheOption someString

image

Но что если мы попробуем это снова, используя этот код, где у нас есть None для значения Option, которое мы передадим в функцию «printTheOption»:

let demoNone = None
 
let printTheOption (opt :Option<'a>) =
    printfn "Actual Option=%A" opt
    printfn "IsNone=%b, IsSome=%b Value=%A\r\n\r\n" opt.IsNone opt.IsSome opt.Value
 
printfn "let demoNone = None"
printfn "printTheOption demoNone"
printTheOption demoNone

image

Как вы видите, у нас здесь проблема. Проблема заключается в том, что мы попытались получить значение Option с помощью вспомогательного свойства Option.Value, в данном случае это None, поэтому мы получили исключение NullReferenceException. Это показано в таблице выше, что когда вы используете вспомогательные свойства и методы, вы можете получить исключение. Хорошо, вы могли бы использовать метод IsNone и вы бы всегда проверяли значение, используя это, когда вы могли бы просто использовать хорошее чистое сопоставление с образцом.

Если вы не можете принять это, спросите себя, сколько раз вам приходилось проверять null значение при использовании C#. Это даже привело к тому, что люди включили функциональные конструкции, такие как Maybe Null Monad, в обычный код .NET.

Итак, теперь, когда мы увидели опасность использования вспомогательных методов / свойств, давайте теперь обратим наше внимание на то, как мы могли бы избежать этих исключений:


et printTheOption (opt :Option<'a>) =
    match opt with
    | Some a -> printfn "opt is Some, and has value %A" a
    | None -> printfn "opt is None"
 
let demoNone = None
let someInt = Some 1
let someString = Some "crazy dude in the monkey house"
 
printTheOption demoNone
printTheOption someInt
printTheOption someString

Мое личное мнение состоит в том, что это более читабельно, чем код, который был бы усеян IsSome / IsNone повсюду. Это, конечно, дело каждого, но тот факт, что мы рассмотрели все основы здесь, в этой простой функции, нельзя игнорировать.

image

Option vs Null


Итак, мы говорили о Option в F# по сравнению с Nullabe, и мы знаем, что тип Option может использоваться с любым типом, тогда как Nullable может использоваться только со структурами. Но как насчет типов Option в сравнении с обычными ссылочными типами в .NET. Что ж, одна большая победа для Option заключается в том, что когда вы используете ссылочный тип в .NET, вы имеете дело со ссылкой на указатель, которая как таковая может быть установлена на null. Однако тип объекта остается таким же, как он был объявлен, что может содержать действительную ссылку на объект в куче(Heap) или может быть нулевым.

Вполне нормально было бы написать так:


string s1 = "cats";
int s1Length = s1.Length;
 
string s2 = null;
int s2Length = s2.Length;

Это будет успешно скомпилировано. Однако, когда мы запустим это, мы получим исключение NullReferenceException, для которого мы будем вынужденны выкручиваться, чтобы защитить весь код от возможного присутствия null. Это становится утомительным довольно быстро, даже если у вас есть хороший маленький класс для защиты, который проверит значение и обработает его/сгенерирует более значимое исключение.

На этом снимке экрана используется LinqPad, поэтому может показаться немного необычным, если вы раньше не видели LinqPad, но, поверьте мне, вы все равно получите тот же результат в другой IDE.

image

Теперь давайте посмотрим, как будет выглядеть эквивалентный код в F#

let s1 = "Cats"
let s1Length = s1.Length;
 
let s2 = None
let s2Length = s2.Length;
 
//excplicily string typed None
let s3 = Option.None
let s3Length = s3.Length;

На этот код, Вы получите немедленную ошибку компиляции, так как s3 будет считаться другим типом, который не имеет свойства «Length».

Сравнение Option


Типы опций считаются равными, они имеют одинаковый тип, и что типы, которые они содержат, равны, что подчиняется правилам равенства удерживаемого типа.

Так что такого рода вещи могут привести к немедленной ошибке времени компиляции в F#

let o1 = Some 1
let o2 = Some "Cats"
 
printfn "o1=o2 : %b" (o1 = o2)

image

Это будет работать как ожидалось, так как типы одинаковы

let o1 = Some "Cats"
let o2 = Some "Cats"
let o3 = Some "cats"
 
printfn "o1=o2 : %b" (o1 = o2)
printfn "o2=o3 : %b" (o2 = o3)

image

Комментарии (0)