衆多語言都會設計Option類型,例如Java 8和Swift都設計了Optional類型。其實這種類型早就出如今了函數式語言中,在OCaml和Scala中叫Option,在Haskell中叫Maybe。Option類型是爲了解決了什麼樣的問題呢?git
你必定寫過相似的C#代碼:github
public string GetCustomerName(int id) { if (id < 0) return null; //.... }
這段代碼有什麼問題嗎?null在這裏表明了什麼意思?是否是要表示不存在這樣的Cusotmer?
Null在C#或者Java這類語言中表示未初始化的空引用。例如:函數
string input;
這時的input就是一個沒有初始化空引用。設計
可是在上面的代碼中,咱們實際上是想表達沒有這樣的Customer,不存在這樣的CustomerName,而不是null,null沒有類型,天然沒法表達出不存在Name這樣的領域模型含義。code
但是在C#中咱們彷佛並無其餘選擇,那就勉強用null來表達吧。
接下來你必定寫過相似的代碼:開發
var name = GetCustomerName(id); var length = name.Length;
也許你一眼就看出了問題所在,上面的代碼有可能會發生運行時
的空引用異常。get
是否是經過加上判空就能解決這個問題?且不說這個方案好很差,你們有沒有想過做爲一門靜態強類型的語言,能不能讓這樣的錯誤發生在編譯階段?input
假如咱們可以定義一個這樣的類型Optional
public Optional<string> GetCustomerName(int id) { //... }
這個方法簽名是自描述的,使用者從方法簽名中就能得知CustomerName有多是存在的,有多是不存在的。若是咱們還能經過技術手段強制開發者必須處理這兩種狀況,那麼咱們就有機會消除空引用異常。
實現一個簡易版的Optional
public class Optional<T> { private readonly bool _hasValue; private readonly T _value; public Optional(T value, bool hasValue) { _value = value; _hasValue = hasValue; } } public static class Optional { public static Optional<T> Some<T>(T value) => new Optional<T>(value, true); public static Optional<T> None<T>() => new Optional<T>(default(T), false); }
有了Optional類型,就能夠這樣使用它了:
var s1 = Optional.Some("hello"); var s2 = Optional.None<string>();
從新定義GetCustomerName函數:
public Optional<string> GetCustomerName(int id) { if (id < 0) return Optional.None<string>(); //... return Optional.Some("name"); }
看起來快要成功了,咱們已經用本身定義的Optional
存在
或者
不存在
這兩種狀況。
截至目前,咱們並無在Optional
var name = GetCustomerName(1); //沒法訪問,由於name是Optional<string>類型,並無Length屬性 var length = name.Length;
此時若是在Optional
public TResult Match<TResult>(Func<T, TResult> some, Func<TResult> none) { return _hasValue ? some(_value) : none(); }
開發者就能夠這樣讀取Length:
var name = GetCustomerName(1); var length = name.Match(s => s.Length, () => 0);
Match方法接受兩個lambda,第一個用來處理name存在的狀況,第二個用來處理name不存在的狀況。
至此,咱們定義的Optional類型看起來改善了null帶來的一些問題,不過此時的Optional
得益於F#強大的類型系統,定義Option類型只須要三行代碼:
type Option<'a> = // use a generic definition | Some of 'a // valid value | None // missing
上面的代碼定義了兩種狀況:Some或者是None,當類型爲Some時還包含了一個類型'a。這種可以描述狀況A或者狀況B的類型叫作可區分聯合(Discriminated Unions),可區分聯合是一種F#中很是有用的建模類型。在將來的章節將會詳細描述函數式語言經常使用的數據類型。
相似於C# Optional類型,你可使用相似的方法使用它:
let s1 = "abc" let len1 = s1.Length let s2 = Option<string>.None let len2 = s2.Length
上面的代碼會出現編譯錯誤,s2並非string類型,他是Option類型,所以Option類型並無Length這樣的屬性。若是你想訪問Option裏面包含的類型,你不得不使用模式匹配(Pattern Matching),模式匹配會強制你處理Option的兩種狀況。
let len2 = match s2 with | Some s -> s.Length | None -> 0
模式匹配會在後面的章節詳細描述,此時的場景你能夠參考上面C#中對Optional類型的用法。
再看一個使用模式匹配處理Option的例子:
let x = Some 99 let result = match x with | Some i -> Some(i * 2) | None -> None
若是此時忘記編寫對任何一個分支的處理,編譯器都會給予警告,提示你忘記了處理Option的另外一種狀況。
下一節將會描述模式匹配。