拿 C# 搞函數式編程 - 3


前言

今天和某個人聊天聊到了 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 是不是。。。

jpg

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);
}

然后我們實現我們的 JustNothing

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);

此時,函數 kint -> 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 特性加上了,我們再繼續。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM