C# 函數式編程及Monads.net庫

来源:http://www.cnblogs.com/shawnhu/archive/2017/11/16/7837515.html
-Advertisement-
Play Games

函數式編程中,一切皆為函數,這個函數一般不是類級別的,其可以保存在變數中,可以當做參數或返回值,是函數級別的抽象和重用,將函數作為可重用的基本模塊,就像面向對象中一切皆為對象,把所有事物抽象為類,面向對象編程通過繼承和組合來實現類或模塊重用,而函數式編程通過局部套用來實現函數重用;兩種編程模式相輔相 ...


  函數式編程中,一切皆為函數,這個函數一般不是類級別的,其可以保存在變數中,可以當做參數或返回值,是函數級別的抽象和重用,將函數作為可重用的基本模塊,就像面向對象中一切皆為對象,把所有事物抽象為類,面向對象編程通過繼承和組合來實現類或模塊重用,而函數式編程通過局部套用來實現函數重用;兩種編程模式相輔相成,各有側重點。函數式編程涉及高階函數,純函數、引用透明、閉包、局部套用、部分應用、惰性求值、單子等概念。

  C#不是函數式程式設計語言,但是隨著委托、lambda表達式、擴展方法、Linq、並行庫的引入,不斷方便我們進行函數式程式設計,另外,Monads.net庫也方便我們進行函數式編程。

一、函數式編程基本概念

1、高階函數

  以函數為參數或返回結果的函數,如一個排序函數,其能適用於各種類型的數據,其排序邏輯一樣,但是不同數據類型的值比較方法不一樣,把比較函數當做參數,傳遞給排序函數。另外,C# 中Enumerable類中的Where、Select、SelectMany、First擴展方法都是高階函數。

 

2、引用透明/純函數

  一個函數返回值,只取決於傳遞給它的參數,程式狀態通常不會影響函數返回值,這樣的函數稱為純函數,其沒有副作用,副作用即多個方法或函數共用訪問同一數據,函數式程式設計的主要思想之一就是控制這樣的副作用。

 

3、變數不變性

  變數分局部變數(方法或類實例的局部變數) 全局變數(類的靜態欄位);變數是可變的,函數式程式設計並不歡迎程式中可變值的想法,變數值越公開帶來的問題越嚴重,一般原則是變數的值最好保持不變或在最小的作用域內保存其值,純函數最好只使用在自己模塊中定義的變數值,不訪問其作用域之外的任何變數。

 

 4、閉包

  當函數可以當成參數和返回值在函數之間傳遞時,編譯器利用閉包擴展變數的作用域,以保證隨時能得到所需要數據;局部套用(currying或加里化)和部分應用依賴於閉包。

    static Func<int, int> GetClosureFunction()

    {

      //局部變數

      int val = 10;

          //局部函數

      Func<int, int> internalAdd = x => x + val;

      Console.WriteLine(internalAdd(10));//輸出20

      val = 30;

           //局部變數的改變會影響局部函數的值,即使變數的改變在局部函數創建之後。

           Console.WriteLine(internalAdd(10));//輸出40

           return internalAdd;

    }

    static void Closoures()

    {

      Console.WriteLine(GetClosureFunction()(30));//輸出60

    }

 

 

 

        局部變數val 作用域應該只在GetClosureFunction函數中,局部函數引用了外層作用域的變數val,編譯器為其創建一個匿名類,並把局部變數當成其中一個欄位,併在GetClosureFunction函數中實例化它,變數的值保存在欄位內,併在其作用域範圍外繼續使用。

 

5、局部套用或函數柯里化

        函數柯里化是一種使用單參數函數來實現多參數函數的方法

  多參函數:

    Func<int, int, int> add = (x, y) => x + y;

  單參數函數:

    Func<int, Func<int, int>> curriedAdd = x => (y => x + y);

  調用:curriedAdd (5)(3)

 

  應用場景:預計算,記住前邊計算的值避免重覆計算

    static bool IsInListDumb<T>(IEnumerable<T> list, T item)

    {

      var hashSet = new HashSet<T>(list);

      return hashSet.Contains(item);

    }

  調用:

    IsInListDumb(strings, "aa");

    IsInListDumb(strings, "aa");

 

  改造後:

    static Func<T, bool> CurriedIsInListDumb<T>(IEnumerable<T> list)

    {

      var hashSet = new HashSet<T>(list);

      return item => hashSet.Contains(item);

    }

   調用:

    var curriedIsInListDumb = CurriedIsInListDumb(strings);

    curriedIsInListDumb("aa");

    curriedIsInListDumb ("bb");

  

 6、部分應用或偏函數應用

  找一個函數,固定其中的幾個參數值,從而得到一個新的函數;通過局部套用實現。

    static void LogMsg(string range, string message)

     {

       Console.WriteLine($"{range} {message}");

     }

    //固化range參數

     static Action<string> PartialLogMsg(Action<string, string> logMsg, string range)

     {

       return msg => logMsg(range, msg);

     }

     static void Main(string[] args)

     {

       PartialLogMsg(LogMsg, "Error")("充值失敗");

       PartialLogMsg(LogMsg, "Warning")("金額錯誤");

     }

  部分應用例子:

    代碼重覆版本:

    using(var trans = conn.BeginTransaction()){

      ExecuteSql(trans, "insert into people(id, name)value(1, 'Harry')");

      ExecuteSql(trans, "insert into people(id, name)value(2, 'Jane')");

      ...

      trans.Commit();

    }

    優化1:函數級別模塊化

    using(var trans = conn.BeginTransaction()){

      Action<SqlCeTransaction, int, string> exec = (transaction, id, name) =>

        ExecuteSql(transaction, String.Format(

          "insert into people(id, name)value({0},'{1}'", id, name));

      exec (trans, 1, 'Harry');

      exec (trans, 2, 'Jane');

      ...

      trans.Commit();

    }

    優化2:部分應用

    using(var trans = conn.BeginTransaction()){

      Func<SqlCeTransaction, Func<int, Action<string>> exec = transaction => id => name =>
        ExecuteSql(transaction, String.Format(

          "insert into people(id, name)value({0},'{1}'", id, name)))(trans);
       exec (1)( 'Harry');

       exec (2)( 'Jane');

       ...

      trans.Commit();

    }

  

    優化3:直接通過閉包簡化

    using(var trans = conn.BeginTransaction()){

      Action<SqlCeTransaction, int, string> exec = ( id, name) =>

        ExecuteSql(trans , String.Format(

          "insert into people(id, name)value({0},'{1}'", id, name));

      exec (1, 'Harry');

      exec ( 2, 'Jane');

      ...

      trans.Commit();

    }

 

7、惰性求值/嚴格求值

  表達式或表達式的一部分只有當真正需要它們的結果時才對它們求值,嚴格求值指表達式在傳遞給函數之前求值,惰性求值的優點是可以提高程式執行效率,複雜演算法中很難決定某些操作執行還是不執行。

  如下例子:

    static int BigCalculation()

    {

      //big calculation

      return 10;

    }

 

    static void DoSomething(int a, int b)

    {

      if(a != 0)

      {

        Console.WriteLine(b);

      }

    }

    DoSomething(o, BigCalculation()) //嚴格求值

 

    static HigherOrderDoSomething(Func<int> a, Func<int> b)

    {

      if(a() != 0)

      {

        Console.WriteLine(b());

      }

    }

    HigherOrderDoSomething(() => 0, BigCalculation)//惰性求值

  這也是函數式編程的一大好處。

 8、單子(Monad)

  把相關操作按某個特定類型鏈接起來。代碼更易閱讀,更簡潔,更清晰。

 

二、Monads.net

 

    Monads.net是GitHub上一個開源的C#項目,提供了許多擴展方法,以便能夠在C#編程時編寫函數式編程風格的代碼。主要針對class、Nullable、IEnuerable以及Events類型提供

一些擴展方法。地址:https://github.com/sergeyzwezdin/monads.net。下麵舉些例子:

 

  示例一: 使用With擴展方法獲取某人工作單位的電話號碼

 

    var person = new Person();

     var phoneNumber = ""; 

    if(person != null && person.Work != null && person.Work.Phone != null)

     {

       phoneNumber = person.Work.Phone.Number;

    }

  在Monads.net中:

    var person = new Person();

     var phoneNumber = person.With(p => p.Work).With(w => w.Phone).With(p => p.Number);


  代碼中主要使用了With擴展方法, 源代碼如下:

    public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)

        where TSource : class

     {

       if ((object) source != (object) default (TSource))

          return action(source);

       return default (TResult);

      }

  person.With(p => p.Work)這段代碼首先判斷person是否為空,如果不為Null則調用p => p.Work返回Work屬性,否則返回Null。
  接下來With(w => w.Phone), 首先判斷上一個函數返回值是否為Null,如果不為Null則調用w => w.Phone返回Phone屬性,否則返回Null。
  由此可以看出, 在上面的With函數調用鏈上任何一個With函數的source參數是Null,則結果也為Null, 這樣不拋出NullReferenceException。

 

  示例二: 使用Return擴展方法獲取某人工作單位的電話號碼


  在示例一中,如果person,Work,Phone對象中任一個為Null值phoneNumber會被賦於Null值。如果在此場景中要求phoneNumber不能Null,而是設置一個預設值,應該怎麼辦?

    var person = new Person();

    var phoneNumber = person.With(p => p.Work).With(w => w.Phone).Return(p => p.Number, defaultValue:"11111111");

  當調用Return方法的source參數為Null時被返回。

 

  示例三: Recover

 

    Person person = null;

      //person = new Person();

      if(null == person)

     {

       person = new Person();

     }

 

  在Monads.net中:

    Person person = null;       //person = new Person();      person.Recover(p => new Person());  

  示例四: try/catch

 

    Person person = null;

      try {

        Console.WriteLine(person.Work);

      } catch(NullReferenceException ex)

      {

        Console.WriteLine(ex.message);

     }

  在Monads.net中:

    Person person = null;

    person.TryDo(p => Console.WriteLine(p.Work), typeof(NullReferenceException)).Catch(ex => Console.WriteLine(ex.Message));

    //忽略異常

      Person person=null;

     try {

        Console.WriteLine(person.Work);

      } catch()

      {

     }

  在Monads.net中:

      person.TryDo(p=>Console.WriteLine(p.Work)).Catch();

 

  示例五: Dictionary.TryGetValue

 

    var data = new Dictionary<int,string>();

     string result = null;

     if(data.TryGetValue(1, out result))

      {

        Console.WriteLine($"已找到Key為1的結果:{result}");

      }else

      {

       Console.WriteLine($"未找到Key為1的結果");

     }

  在Monads.net中:

    data.With(1).Return(_ => $"已找到Key為1的結果:{_}", "未找到Key為1的結果").Do(_ => Console.WriteLine(_));


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

-Advertisement-
Play Games
更多相關文章
  • 前言 在開發過程中,我們不難發現,客戶的需求以及產品的定位對開發內容的走向有很大的決策作用,而這些往往需要在一開始就儘可能考慮周全和設計完善。為什麼說是儘可能,因為我們都知道,需求這種東西,一言難盡...作為開發者,既然無法掌控需求的變更等因素,那我們就要把握好自身能決定的工具資源等,架構設計、技術 ...
  • 一、封裝的查詢方法 /** * solr查詢方法 * @param client solr客戶端 * @param query solr查詢對象 * @return list集合 * @throws SolrServerException * @throws IOException */ publi ...
  • 最最常用的關鍵詞及音標 數據類型:boolean、byte、short、int、long、double、char、float、double。 包引入和包聲明:import、package。 用於類和介面的聲明:class、extends、implements、interface。 流程式控制制:if、e ...
  • 基礎配置 vim的配置是在用戶主目錄下的 ~/.vimrc 文件中完成的,如果沒有的話,需要自己新建一下: 首先做些簡單的配置: 為py文件添加下支持pep8風格的配置: 分割視窗 vim在編輯的時候就可以打開多個文件: :vs 或者 :vsplit 將當前視窗豎直分割,併在上面新視窗中顯示當前文件 ...
  • 大致介紹 好久沒有寫博客了,正好今天有時間把前幾天寫的利用python定時發送QQ郵件記錄一下 1、首先利用request庫去請求數據,天氣預報使用的是和風天氣的API(www.heweather.com/douments/api/s6/weather-forecast) 2、利用python的ji ...
  • 詳情參見:https://www.cnblogs.com/landiljy/p/5764515.html 1.@PostConstruct說明 被@PostConstruct修飾的方法會在伺服器載入Servlet的時候運行,並且只會被伺服器調用一次,類似於Serclet的inti()方法。被@Pos... ...
  • 1.功能: 只能有一個實例的類,用於類似計數、記憶體池的情況。 2.實現方法: [1]構造函數設置為private,因此不能在外部創建實例。 [2]提供一個public方法訪問實例。 [3]析構函數,析構函數是為了銷毀這個類的成員變數,private和public都可以,但是析構函數裡面不能delet ...
  • 歡迎想學習網頁設計的伙伴們,我會定期開始錄製免費的網頁設計教程,主要是作為一種學習的分享。 首先,給大家介紹一下,我在紐特邏輯工作,主要從事前端設計,本次課程循序漸進,難度初級,最後是一個設計出題網頁為結束。 開始之前,要做一些準備: 1.軟體準備 (1)<首先安裝>Java開發環境JDK,一種用於 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...