拿 C# 搞函數式編程 - 3

来源:https://www.cnblogs.com/hez2010/archive/2020/03/29/12590389.html
-Advertisement-
Play Games

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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:Java之美 日期:2020-03-28 ...
  • 一、屬性賦值順序 1.屬性可以賦值的位置 ①預設初始化; ②顯示初始化; ③構造器中初始化; ④通過“對象.屬性”或“對象.方法”的方式對屬性進行賦值; 2.先後順序 ① - ② - ③ - ④ 二、JavaBean 所謂JavaBean,是指符合以下標準的類: 1.類是公共的; 2.有一個公共的無 ...
  • 一、構造器的作用 1.創建對象; 2.初始化對象的信息。 二、說明 1.如果沒顯式的定義類的構造器的話,則系統預設提供一個空參的構造器; 2.定義構造器的格式:許可權修飾符 類名(形參列表){}; 3.一個類中定義的多個構造器,彼此構成重載; 4.一旦我們顯式的定義了類的構造器之後,系統就不再提供預設 ...
  • 方法1 初始化方法參數說明 name:自定義日誌的名字, 預設是root, 但是我這裡是使用調用文件的__name__ 作為預設名字 path:生成的日誌的文件名 level:日誌的級別,我這裡把所有的級別都預設設置了level=DEBUG 方法2 使用logging.fileconfig這個模塊實 ...
  • 方法一: 知識點:random.sample(sequence, k) 從指定序列中隨機獲取指定長度的片斷 方法二: 知識點:random.choice(sequence) 從序列中獲取一個隨機元素 方法三: 知識點:random.randint(a,b) 用於生成一個指定範圍內的整數 方法四: 列 ...
  • 前言 忘了在哪看到一位編程大牛調侃,他說程式員每天就做兩件事,其中之一就是處理字元串。相信不少同學會有同感。 在Python中,我們經常會遇到字元串的拼接問題,幾乎任何一種編程語言,都把字元串列為最基礎和不可或缺的數據類型。而拼接字元串是必備的一種技能。今天,我跟大家一起來學習Python拼接字元串 ...
  • class Ticket implements Runnable { private static int tick = 100; boolean flag = true; @Override public void run() { if (flag) { while (true) { synchr ...
  • 一、為何要引入封裝性? 程式設計的重點是追求高內聚、低耦合: > 高內聚:類的內部數據操作細節自己完成,不允許外部干涉 > 低耦合:僅對外暴露少量的方法用於使用 隱藏對象內部的複雜性,只對外公開簡單的介面。便於外界調用,從而提高系統的可擴展性、可維護性。 二、問題的引入 當我們創建一個類的對象以後, ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...