Bind函數在函數式編程中是如此重要,以致於函數式編程語言會爲bind函數設計語法糖。另外一個角度Bind函數很是難以理解,幾乎不多有人能經過簡單的描述說明白bind函數的由來及原理。
這篇文章試圖經過「人話」來描述bind函數,並經過淺顯的實例爲零函數式編程語言的開發者揭祕bind函數的做用及用法。html
public string GetSomething(int id) { var x = GetFirstThing(id); if (x != null) { var y = GetSecondThing(x); if(y != null) { var z = GetThirdThing(y); if (z != null) { return z; } } } return null; }
你必定寫過相似的代碼,估計你也明白這樣的代碼看起來很醜陋,一層層的判空嵌套打亂了代碼的主題結構。
有無法讓他變的更優雅?固然你能夠經過"early return"的作法,不過這種方式不在咱們的討論範圍以內。
這種風格的代碼存在一個明顯的code smell, GetFirstThing()/GetSecondThing()/GetThirdThing()等方法有可能返回null,咱們說return null是一種不真確的作法,相關分析見拒絕空引用異常。使用Optional類型重構以下:express
public Optional<string> GetSomething(int id) { var x = GetFirstThing(id); if (x.HasValue()) { var y = GetSecondThing(x); if(y.HasValue()) { var z = GetThirdThing(y); if (z.HasValue()) { return z; } } } return Optional.None<string>(); }
看起來代碼結果跟以前如出一轍,重構後的代碼並無變得更漂亮。不過如今的GetFirstThing()/GetSecondThing()/GetThirdThing()方法返回值爲Optional<string>類型,再也不是普通的string類型:編程
public Optional<string> GetFirstThing(int id) { //... return Optional.None<string>(); }
重構後的這段代碼頗有意思,咱們能夠從函數組合的角度來讓整個代碼段變的更加優雅。數組
這段代碼其實作了一件事,那就是經過調用三個函數GetFirstThing()/GetSecondThing()/GetThirdThing()來完成一個業務邏輯。從函數式編程思想的角度出發,咱們傾向於把若干個小的函數鏈接起來,根據之前學過的知識,只有一個輸入和一個輸出的函數才能鏈接起來:
他們之因此可以鏈接是由於這兩個函數的簽名一致,都擁有一個輸入和一個輸出。
例如:int -> string, string -> bool就能夠組合爲int -> bool。
而咱們此時擁有的三個函數方法簽名以下:編程語言
GetFirstThing: int -> Optional<string> GetSecondThing: string -> Optional<string> GetThirdThing: string -> Optional<string>
顯然GetFirstThing和GetSecondThing是沒法直接鏈接的,緣由是GetFirstThing返回了Optional<string>類型,而GetSecondThing的輸入倒是一個普通的string類型。若是咱們可以在Optional<T>上擴展一個函數,函數接受一個簽名爲T -> Optional<T>的函數,那麼咱們就有可能將上面的三個函數串聯起來:函數式編程
public static class Optional { public static Optional<T> Bind<T>(this Optional<T> input, Func<T, Optional<T>> f) { if (input.HasValue()) { return f(input.Value); } return Optional.None<T>(); } }
有了上面這個神奇的bind函數你就能夠將上面的三個函數鏈接起來了:函數
public string GetSomething(int id) { return GetFirstThing(id).Bind(GetSecondThing).Bind(GetThirdThing); }
用F#實現:this
let address = getFirstThing id |> bind getSecondThing |> bind getThirdThing
經過bind函數咱們成功將三個函數鏈接了起來, 同時將判空放在了bind函數裏,從而保持主要邏輯部分更加線性和清晰。設計
咱們常常用的List<T>就是一個典型的泛型類型,那他上面有沒有bind函數?固然有,不過叫作SelectMany, Scala中也叫flatMap。
看一下SelectMany的方法簽名,正好符合bind函數的簽名要求:code
public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector) { //... }
SelectMany能夠用在什麼樣的場景中?
例若有這樣一個場景,一篇文章(paper)能夠有若干章節(section)組成,每一個章節(section)又有若干行(row)組成,每行(row)有若干單詞(word)組成。
問:給定一篇文章(paper),請找出大於10行(row)的章節(section),裏面排除註釋的行(row)總共的單詞(word)數量。
首先根據需求變下下面的若干函數:
private List<Paper.Section> GetSections(Paper paper) { return paper.Sections.Where(s => s.Rows.Count > 10).ToList(); } private List<Paper.Section.Row> GetRows(Paper.Section section) { return section.Rows.Where(r=>!r.IsComment).ToList(); } private List<Paper.Section.Row.Word> GetWords(Paper.Section.Row row) { return row.Words; }
且看這三個函數的簽名:
GetSections: Papaer -> List<Section> GetRows: Section -> List<Row> GetWords: Row -> List<Word>
正好這就是就符合bind函數鏈接的需求:
var length = GetSections(paper) .SelectMany(GetRows) .SelectMany(GetWords) .Count();
F#實現:
let words = getSections paper |> bind getRows |> bind getWords words.Length
bind函數在函數式編程中如此常見,以致於須要設計單獨的語法糖,Haskell中叫do natation
, Scala中叫for comprehension
,F#用Computation expressions
:
list { let! section = getSections(paper) let! row = getRows(section) let! word = getWord(row) return word }