前言 今天和某個人聊天聊到了 C 的 LINQ,發現我認識的 LINQ 似乎和大多數人認識的 LINQ 不太一樣,怎麼個不一樣法呢?其實 LINQ 也可以用來搞函數式編程。 當然,並不是說寫幾個 和用用像 Java 那樣的 之類的就算叫做 LINQ 了,LINQ 其實是一個另外的一些東西。 LINQ ...
前言
今天和某個人聊天聊到了 C# 的 LINQ,發現我認識的 LINQ 似乎和大多數人認識的 LINQ 不太一樣,怎麼個不一樣法呢?其實 LINQ 也可以用來搞函數式編程。
當然,並不是說寫幾個 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 的語法實現了對一個列表中的偶數的篩選。
LINQ 只是一個用於方便對集合進行操作的工具而已,如果我們如果想讓我們自己的類型支持 LINQ 語法,那麼我們需要讓我們的類型實現 IEnumerable<T>
,然後就可以這麼用了。。。
哦,原來是這樣的嗎?那我全都懂了。。。。。。
???哦,我的老天,當然不是!
其實 LINQ 和 IEnumerable<T>
完全沒有關係!LINQ 只是一組擴展方法而已,它主要由以下方法組成:
方法名稱 | 方法說明 |
---|---|
Where | 數據篩選 |
Select/SelectMany | 數據投影 |
Join/GroupJoin | 數據聯接 |
OrderBy/ThenBy/OrderByDescending/ThenByDescending | 數據排序 |
GroupBy | 數據分組 |
...... |
以上方法對應 LINQ 關鍵字:where
, select
, join
, orderby
, group
...
在編譯器編譯 C# 代碼時,會將 LINQ 語法轉換為擴展方法調用的語法,例如:
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 語法翻譯為方法調用後再編譯,那麼,只要有對應名字的方法,不就意味著可以用 LINQ 語法了(逃
那麼你看這個 SelectMany
是不是。。。
SelectMany
is Monad
哦我的上帝,你瞧瞧這個可憐的 SelectMany
,這難道不是 Monad
需要的 bind
函數?
事情逐漸變得有趣了起來。
我們繼承上一篇的精神,再寫一次 Maybe<T>
。
Maybe<T>
首先,我們寫一個抽象類 Maybe<T>
。
首先我們給它加一個 Select
方法用於選擇 Maybe<T>
中的數據,如果是 T
,那麼返回一個 Just<T>
,如果是 Nothing<T>
,那麼返回一個 Nothing<T>
。相當於我們的 returns
函數:
public abstract class Maybe<T>
{
public abstract Maybe<U> Select<U>(Func<T, Maybe<U>> f);
}
然後我們實現我們的 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
實現 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>
實現完了!什麼,就這??那麼怎麼用呢?激動人心的時刻來了!
首先,我們創建幾個 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);
輸出結果:
Just 10
Nothing
完美!上面的 LINQ 被編譯成了:
var u = x.SelectMany(_ => y, (x0, y0) => x0 + y0);
var v = x.SelectMany(_ => z, (x0, z0) => x0 + z0);
此時,函數 k
為 int -> Maybe<int>
,而函數 s
為(int, int) -> int
,是一個加法函數。
函數 k
的參數我們並不關心,它用作一個 selector
,我們只需要讓它產生一個 Maybe<int>
,然後利用函數 s
將兩個 int
的值做加法運算,並把結果包裝到一個 Just<int>
裡面即可。
這個過程中,如果有任何一方產生了 Nothing
,則後續運算結果永遠都是 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
有內味了(逃
後記
該系列的後續文章將按揭編寫,如果 C# 爭氣一點,把 Discriminated Unions、Higher Kinded Generics 和 Type Classes 特性加上了,我們再繼續。