前言
今天和某個人聊天聊到了 C# 的 LINQ,發(fā)現(xiàn)我認識的 LINQ 似乎和大多數(shù)人認識的 LINQ 不太一樣,怎么個不一樣法呢?其實 LINQ 也可以用來搞函數(shù)式編程。
當然,并不是說寫幾個 lambda 和用用像 Java 那樣的 stream 之類的就算叫做 LINQ 了,LINQ 其實是一個另外的一些東西。
LINQ
在 C# 中,相信大家都見過如下的 LINQ 寫法:
IEnumerable<int> EvenNumberFilter(IEnumerable<int> list)
{
return from c in list where c & 1 == 0 select c;
}
以上代碼借助 LINQ 的語法實現(xiàn)了對一個列表中的偶數(shù)的篩選。
LINQ 只是一個用于方便對集合進行操作的工具而已,如果我們?nèi)绻胱屛覀冏约旱念愋椭С?LINQ 語法,那么我們需要讓我們的類型實現(xiàn) IEnumerable<T>,然后就可以這么用了。。。
哦,原來是這樣的嗎?那我全都懂了。。。。。。
???哦,我的老天,當然不是!
其實 LINQ 和 IEnumerable<T> 完全沒有關系!LINQ 只是一組擴展方法而已,它主要由以下方法組成:
| 方法名稱 |
方法說明 |
| Where |
數(shù)據(jù)篩選 |
| Select/SelectMany |
數(shù)據(jù)投影 |
| Join/GroupJoin |
數(shù)據(jù)聯(lián)接 |
| OrderBy/ThenBy/OrderByDescending/ThenByDescending |
數(shù)據(jù)排序 |
| GroupBy |
數(shù)據(jù)分組 |
| ...... |
|
以上方法對應 LINQ 關鍵字:where, select, join, orderby, group...
在編譯器編譯 C# 代碼時,會將 LINQ 語法轉(zhuǎn)換為擴展方法調(diào)用的語法,例如:
from c in list where c > 5 select c;
會被編譯成:
list.Where(c => c > 5).Select(c => c);
再例如:
from x1 in list1 join x2 in list2 on x1.k equals x2.k into g select g.u;
會被編譯成:
list1.GroupJoin(list2, x1 => x1.k, x2 => x2.k, (x1, g) => g.u);
再例如:
from x in list orderby x.k1, x.k2, x.k3;
會被編譯成:
list.OrderBy(x => x.k1).ThenBy(x => x.k2).ThenBy(x => x.k3);
再有:
from c in list1
from d in list2
select c + d;
會被編譯成:
list1.SelectMany(c => list2, (c, d) => c + d);
停停停!
此外,編譯器在編譯的時候總是會先將 LINQ 語法翻譯為方法調(diào)用后再編譯,那么,只要有對應名字的方法,不就意味著可以用 LINQ 語法了(逃
那么你看這個 SelectMany 是不是。。。

SelectMany is Monad
哦我的上帝,你瞧瞧這個可憐的 SelectMany,這難道不是 Monad 需要的 bind 函數(shù)?
事情逐漸變得有趣了起來。
我們繼承上一篇的精神,再寫一次 Maybe<T>。
Maybe<T>
首先,我們寫一個抽象類 Maybe<T>。
首先我們給它加一個 Select 方法用于選擇 Maybe<T> 中的數(shù)據(jù),如果是 T,那么返回一個 Just<T>,如果是 Nothing<T>,那么返回一個 Nothing<T>。相當于我們的 returns 函數(shù):
public abstract class Maybe<T>
{
public abstract Maybe<U> Select<U>(Func<T, Maybe<U>> f);
}
然后我們實現(xiàn)我們的 Just 和 Nothing:
public class Just<T> : Maybe<T>
{
private readonly T value;
public Just(T value) { this.value = value; }
public override Maybe<U> Select<U>(Func<T, Maybe<U>> f) => f(value);
public override string ToString() => $"Just {value}";
}
public class Nothing<T> : Maybe<T>
{
public override Maybe<U> Select<U>(Func<T, Maybe<U>> _) => new Nothing<U>();
public override string ToString() => "Nothing";
}
然后,我們給 Maybe 實現(xiàn) bind —— 即給 Maybe 加上一個叫做 SelectMany 的方法。
public abstract class Maybe<T>
{
public abstract Maybe<U> Select<U>(Func<T, Maybe<U>> f);
public Maybe<V> SelectMany<U, V>(Func<T, Maybe<U>> k, Func<T, U, V> s)
=> Select(x => k(x).Select(y => new Just<V>(s(x, y))));
}
至此,Maybe<T> 實現(xiàn)完了!什么,就這??那么怎么用呢?激動人心的時刻來了!
首先,我們創(chuàng)建幾個 Maybe<int>:
var x = new Just<int>(3);
var y = new Just<int>(7);
var z = new Nothing<int>();
然后我們分別利用 LINQ 計算 x + y, x + z:
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);
輸出結(jié)果:
Just 10
Nothing
完美!上面的 LINQ 被編譯成了:
var u = x.SelectMany(_ => y, (x0, y0) => x0 + y0);
var v = x.SelectMany(_ => z, (x0, z0) => x0 + z0);
此時,函數(shù) k 為 int -> Maybe<int>,而函數(shù) s 為(int, int) -> int,是一個加法函數(shù)。
函數(shù) k 的參數(shù)我們并不關心,它用作一個 selector,我們只需要讓它產(chǎn)生一個 Maybe<int>,然后利用函數(shù) s 將兩個 int 的值做加法運算,并把結(jié)果包裝到一個 Just<int> 里面即可。
這個過程中,如果有任何一方產(chǎn)生了 Nothing,則后續(xù)運算結(jié)果永遠都是 Nothing,因為 Nothing.Select(...) 還是 Nothing。
一點擴展
我們再給這個 Maybe<T> 加一個 Where:
public abstract class Maybe<T>
{
public abstract Maybe<U> Select<U>(Func<T, Maybe<U>> f);
public Maybe<V> SelectMany<U, V>(Func<T, Maybe<U>> k, Func<T, U, V> s)
=> Select(x => k(x).Select(y => new Just<V>(s(x, y))));
public Maybe<U> Where(Func<Maybe<T>, bool> f) => f(this) ? this : new Nothing<T>();
}
然后我們就可以玩:
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
有內(nèi)味了(逃
后記
該系列的后續(xù)文章將按揭編寫,如果 C# 爭氣一點,把 Discriminated Unions、Higher Kinded Generics 和 Type Classes 特性加上了,我們再繼續(xù)。
|