1. 程式人生 > >拿 C# 搞函數語言程式設計 - 3

拿 C# 搞函數語言程式設計 - 3

## 前言 今天和某個人聊天聊到了 C# 的 LINQ,發現我認識的 LINQ 似乎和大多數人認識的 LINQ 不太一樣,怎麼個不一樣法呢?其實 LINQ 也可以用來搞函數語言程式設計。 當然,並不是說寫幾個 `lambda` 和用用像 Java 那樣的 `stream` 之類的就算叫做 LINQ 了,LINQ 其實是一個另外的一些東西。 ## LINQ 在 C# 中,相信大家都見過如下的 LINQ 寫法: ```csharp IEnumerable EvenNumberFilter(IEnumerable list) { return from c in list where c & 1 == 0 select c; } ``` 以上程式碼藉助 LINQ 的語法實現了對一個列表中的偶數的篩選。 LINQ 只是一個用於方便對集合進行操作的工具而已,如果我們如果想讓我們自己的型別支援 LINQ 語法,那麼我們需要讓我們的型別實現 `IEnumerable`,然後就可以這麼用了。。。 哦,原來是這樣的嗎?那我全都懂了。。。。。。 ???哦,我的老天,當然不是! 其實 LINQ 和 `IEnumerable` 完全沒有關係!LINQ 只是一組擴充套件方法而已,它主要由以下方法組成: | 方法名稱 | 方法說明 | | ------- | ------- | | Where | 資料篩選 | | Select/SelectMany | 資料投影 | | Join/GroupJoin | 資料聯接 | | OrderBy/ThenBy/OrderByDescending/ThenByDescending | 資料排序 | | GroupBy | 資料分組 | | ...... | 以上方法對應 LINQ 關鍵字:`where`, `select`, `join`, `orderby`, `group`... 在編譯器編譯 C# 程式碼時,會將 LINQ 語法轉換為擴充套件方法呼叫的語法,例如: ```csharp from c in list where c > 5 select c; ``` 會被編譯成: ```csharp list.Where(c => c > 5).Select(c => c); ``` 再例如: ```csharp from x1 in list1 join x2 in list2 on x1.k equals x2.k into g select g.u; ``` 會被編譯成: ```csharp list1.GroupJoin(list2, x1 => x1.k, x2 => x2.k, (x1, g) => g.u); ``` 再例如: ```csharp from x in list orderby x.k1, x.k2, x.k3; ``` 會被編譯成: ```csharp list.OrderBy(x => x.k1).ThenBy(x => x.k2).ThenBy(x => x.k3); ``` 再有: ```csharp from c in list1 from d in list2 select c + d; ``` 會被編譯成: ```csharp list1.SelectMany(c => list2, (c, d) => c + d); ``` 停停停! 此外,編譯器在編譯的時候總是會先將 LINQ 語法翻譯為方法呼叫後再編譯,那麼,只要有對應名字的方法,不就意味著可以用 LINQ 語法了(逃 那麼你看這個 `SelectMany` 是不是。。。 ![jpg](https://img2020.cnblogs.com/blog/1590449/202003/1590449-20200329000822036-790619986.jpg) ## `SelectMany` is `Monad` 哦我的上帝,你瞧瞧這個可憐的 `SelectMany`,這難道不是 `Monad` 需要的 `bind` 函式? 事情逐漸變得有趣了起來。 我們繼承上一篇的精神,再寫一次 `Maybe`。 ## `Maybe` 首先,我們寫一個抽象類 `Maybe`。 首先我們給它加一個 `Select` 方法用於選擇 `Maybe` 中的資料,如果是 `T`,那麼返回一個 `Just`,如果是 `Nothing`,那麼返回一個 `Nothing`。相當於我們的 `returns` 函式: ```csharp public abstract class Maybe { public abstract Maybe Select(Func> f); } ``` 然後我們實現我們的 `Just` 和 `Nothing`: ```csharp public class Just : Maybe { private readonly T value; public Just(T value) { this.value = value; } public override Maybe Select(Func> f) => f(value); public override string ToString() => $"Just {value}"; } public class Nothing : Maybe { public override Maybe Select(Func> _) => new Nothing(); public override string ToString() => "Nothing"; } ``` 然後,我們給 `Maybe` 實現 `bind` —— 即給 `Maybe` 加上一個叫做 `SelectMany` 的方法。 ```csharp public abstract class Maybe { public abstract Maybe Select(Func> f); public Maybe SelectMany(Func> k, Func s) => Select(x => k(x).Select(y => new Just(s(x, y)))); } ``` 至此,`Maybe` 實現完了!什麼,就這??那麼怎麼用呢?激動人心的時刻來了! 首先,我們建立幾個 `Maybe`: ```csharp var x = new Just(3); var y = new Just(7); var z = new Nothing(); ``` 然後我們分別利用 LINQ 計算 `x + y`, `x + z`: ```csharp var u = from x0 in x from y0 in y select x0 + y0; var v = from x0 in x from z0 in z select x0 + z0; Console.WriteLine(u); Console.WriteLine(v); ``` 輸出結果: ``` Just 10 Nothing ``` 完美!上面的 LINQ 被編譯成了: ```csharp var u = x.SelectMany(_ => y, (x0, y0) => x0 + y0); var v = x.SelectMany(_ => z, (x0, z0) => x0 + z0); ``` 此時,函式 `k` 為 `int -> Maybe`,而函式 `s` 為`(int, int) -> int`,是一個加法函式。 函式 `k` 的引數我們並不關心,它用作一個 `selector`,我們只需要讓它產生一個 `Maybe`,然後利用函式 `s` 將兩個 `int` 的值做加法運算,並把結果包裝到一個 `Just` 裡面即可。 這個過程中,如果有任何一方產生了 `Nothing`,則後續運算結果永遠都是 `Nothing`,因為 `Nothing.Select(...)` 還是 `Nothing`。 ## 一點擴充套件 我們再給這個 `Maybe` 加一個 `Where`: ```csharp public abstract class Maybe { public abstract Maybe Select(Func> f); public Maybe SelectMany(Func> k, Func s) => Select(x => k(x).Select(y => new Just(s(x, y)))); public Maybe Where(Func, bool> f) => f(this) ? this : new Nothing(); } ``` 然後我們就可以玩: ```csharp var just = from c in x where true select c; var nothing = from c in x where false select c; Console.WriteLine(just); Console.WriteLine(nothing); ``` 當滿足條件的時候返回 `Just`,否則返回 `Nothing`。上述程式碼將輸出: ``` Just 3 Nothing ``` 有內味了(逃 ## 後記 該系列的後續文章將按揭編寫,如果 C# 爭氣一點,把 Discriminated Unions、Higher Kinded Generics 和 Type Classes 特性加上了,我們再繼續。