C#函數式編程

来源:http://www.cnblogs.com/ctddjyds/archive/2017/07/07/7133189.html
-Advertisement-
Play Games

編程語言範式 常見的編程範式有命令式編程(Imperative programming),函數式編程,邏輯式編程; 許多現存的編程語言都可基於其計算模型加以分類,歸入某些語言族,或者屬於某種編程範式。按照不同的規則,可以有多種分類的方法,而且不同的學者對某些語言的具體歸屬也有不同的意見。 給出一種系 ...


編程語言範式

常見的編程範式有命令式編程(Imperative programming函數式編程邏輯式編程;

許多現存的編程語言都可基於其計算模型加以分類,歸入某些語言族,或者屬於某種編程範式。按照不同的規則,可以有多種分類的方法,而且不同的學者對某些語言的具體歸屬也有不同的意見。

給出一種系譜:

說明式(Declarative )                   命令式( Imperative

  函數式  Lisp, ML, Haskell                馮諾依曼 C, Ada, Fortran

  數據流  ld, Val                         腳本式 Perl, Python, PHP

  邏輯式  Prolog                         面向對象 Smalltalk, C++, Java, C#

  基於模板 XSLT

有些編程範式並不能按以上的方法進行分類,比如:元編程,泛型編程。

一種語言並不是只從屬於一種編程範式,有些語言本身就是為支持多範式設計的;

比如:Lisp就同時支持函數式編程、面向對象、元編程。

命令式編程是面向電腦硬體的抽象,有變數(對應著存儲單元),賦值語句(獲取,存儲指令)表達式(記憶體引用和算術運算)和控制語句(跳轉指令);

 

函數式編程

定義

In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.

函數式編程是一種編程模型,他將電腦運算看做是數學中函數的計算,並且避免了狀態以及變數的概念。<Wiki>

函數式編程是面向數學的抽象,將計算描述為一種表達式求值,一句話,函數式程式就是一個表達式

函數式編程最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(參數)和輸出(返回值)。和指令式編程相比,函數式編程的函數的計算比指令的執行更重要。和過程化編程相比,函數式編程的函數的計算可隨時調用。純函數式編程不需要變數;

 

語言族

函數式編程中最古老的例子可能要數1958年被創造出來的LISP,當年的Lisp由於各種設計缺陷(記憶體損耗、閉包問題、生成程式執行效率等)沒能發展好。較現代的例子包括Haskell、Clean、Erlang 和Miranda等。

現代編程語言,如C#、Python、Ruby、Scala等等,它們都受到了函數式編程語言的影響,比如C#中的lamada表達式、Linq。

基於JVM實現的Lisp方言如Scala, Clojure也是越來越受關註,這裡所謂的Lisp方言,主要是因為語法上沿用了Lisp中的S表達式

基於.net平臺的有F#,微軟的首個函數式編程語言。<MSDN>

 

不同語言的抽象層次

                                                   計算

              C#         ----->     對象

              Python      ----->    函數式

              C語言      ----->    函數 (面向過程)

              彙編語言

         電腦硬體  ----->    指令                 電腦

 

函數式復興

Anders Hejlsberg,C#編程語言的首席架構師,2010年關於《編程語言的發展趨勢及未來方向》演講

從一個數組中找出所有的偶數

List<int> list = new List<int> { 1,2,3,4,5,6,7};

常規的命令式寫法:

    List<int> ret = new List<int>();

    foreach (var item in list)

    { if (item % 2 == 0)

          ret.Add(item); }

聲明式的寫法: var ret = list.Where((x) => x % 2 == 0);

 

多核與並行

         使用命令式編程語言寫程式時,我們經常會編寫如x = x + 1這樣的語句,此時我們大量依賴的是可變狀態,或者說是“變數”,它們的值可以隨程式運行而改變。可變狀態非常強大,但隨之而來的便是被稱為“副作用”的問題,例如一個無需參數的void方法,它會根據調用次數或是在哪個線程上進行調用對程式產生影響,它會改變程式內部的狀態,從而影響之後的運行效果。而在函數式編程中則不會出現這個情況,因為所有的狀態都是不可變的。事實上對函數式編程的討論更像是數學、公式,而不是程式語句,如x = x + 1對於數學家來說,似乎只是個永不為真的表達式而已。

函數式編程十分容易並行,因為它在運行時不會修改任何狀態,因此無論多少線程在運行時都可以觀察到正確的結果。假如兩個函數完全無關,那麼它們是並行還是順序地執行便沒有什麼區別。

 

函數式編程特性                             與技術

     函數是一等公民                        閉包

     高階函數                                  惰性求值

     遞歸                                         緩存技術

    不可變狀態                               尾調用消除

    柯里化                                      記憶體回收

 

C#函數式支持

         Linq涉及的C#語言特性:隱式類型、匿名類型、初始化器、迭代器、委托、泛型、泛型委托、匿名方法、Lamada表達式。

函數對象必須是某種委托類型. 在C#中,我們可以定義強類型的委托類型或泛型的委托類型,委托可以代表跟這個委托類型有相同參數的方法(靜態方法,類方法)的引用.

在使用LINQ的時候我們可以經常看到高階函數。舉個例子,如果你想將一個已有的序列使用一些函數轉換為一個新的序列,你將使用類似LINQ的select函數(函數作為輸入):

var squares = numbers.Select( num => num*num );

 

函數是一等公民

         對象是面向對象的第一型,那麼函數式編程也是一樣,函數是函數式編程的第一型。

我們在函數式編程中努力用函數來表達所有的概念,完成所有的操作。

在面向對象編程中,我們把對象傳來傳去,那在函數式編程中,我們要做的是把函數傳來傳去。

函數這個術語不是指電腦中的函數,而是指數學中的函數,即自變數的映射。

函數可以在任何地方定義,在函數內或函數外,可以作為函數的參數和返回值,可以對函數進行組合。

高階函數:能接收函數做參數的函數。

1:函數自身接受一個或多個函數作為輸入參數;

2:函數自身能輸出(返回)一個函數;

不可變狀態

   純函數式編程語言中的變數也不是命令式編程語言中的變數,即存儲狀態的單元,而是代數中的變數,即一個值的名稱。變數的值是不可變的;

   函數即不依賴外部的狀態也不修改外部的狀態,函數調用的結果不依賴調用的時間和位置,使得單元測試和調試都更容易。

遞歸

   由於變數不可變,純函數編程語言無法實現迴圈,這是因為For迴圈使用可變的狀態作為計數器,而While迴圈DoWhile迴圈需要可變的狀態作為跳出迴圈的條件。因此在函數式語言里就只能使用遞歸來解決迭代問題,這使得函數式編程嚴重依賴遞歸。

遞歸定義的計算的Scala代碼如下:

def fact(n: Int):Int= {

if(n == 0) return 1

n * fact(n-1)

}

C#代碼

Public int Fact(int n)

{

 int acc = 1;

for(int k = 1; k <= n; k++){

acc = acc * k;}

}

尾遞歸

如果一個函數中所有遞歸形式的調用都出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。當遞歸調用是整個函數體中最後執行的語句且它的返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸。尾遞歸函數的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的代碼(將尾遞歸轉化為迭代);

柯里化(Currying和部分(偏)函數(Partial Function)

是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數且返回結果的新函數的技術。

主要功能是提供了強大的動態函數創建方法,通過調用另一個函數併為它傳入要柯里化(currying)的函數和必要的參數而得到。通俗點說就是利用已有的函數,再創建一個動態的函數,該動態函數內部還是通過已有的函數來發生作用。

柯里化就是一個函數在參數沒給全時返回另一個函數,返回的函數的參數正好是餘下的參數。比如:你制定了x和y, 如2的3次方,就返回8, 如果你只制定x為2,y沒指定, 那麼就返回一個函數:2的y次方, 這個函數只有一個參數:y。

curry就是對高階函數(就是一種對過程的抽象 參考map它就是一個抽象的過程)的降階處理。

2大特性:

  • 匿名函數
  • 每個函數只有1個參數

將多個參數的函數進行拆分,拆成多個只有一個參數的函數。為什麼要拆分,λ 演算。

示例:

常規的寫法:Func<int, int, int> Add = (x, y) => x + y;

拆分:      Func<int, Func<int, int>> Add = x => y => x + y;

輸入一個參數,返回一個具有一個參數的函數,接著再調用返回的函數,就完成整個調用。

調用:

         var add2 = Add(3);

     var ret = add2(4);

寫成一行:

var ret = Add(3)(4);

或者不重寫,只要為原來的方法加一個擴展方法:

public static Func<T1,Func<T2,T3>> Currey<T1,T2,T3>(this Func<T1,T2,T3> func)

{

    return x => y => func(x,y);

}

這樣就可以對C#標準的GenralAdd(int x,int y)方法執行Currey轉換為部分(偏)函數了:

Func<int, int, int> Add = GenralAdd;

         var CurreyedAdd = Add.Currey()(3)(4);

示例:比如我們經常需要執行SQL語句,當然需要使用SqlConnection,然後附加上對應的SQL語句,為此我們可以開發一個簡單的函數,用來簡化這一過程:

         Func<SqlConnection, Func<String, DataSet>> ExecSql = x => y =>

         {

              using (x)

              {

                   x.Open();

                   var com = x.CreateCommand();

                   DataSet ds = new DataSet();

                   com.CommandText = y;

                   SqlDataAdapter adapter = new SqlDataAdapter(com);

                   adapter.Fill(ds);

                   return ds;

               }

    };

調用:

         var esql = ExecSql(new SqlConnection("xxx"));

         var rds = esql("select xxxx from xxx");

         rds = esql("select ffff from ffff");

 

如果想先傳入Sql語句再傳入SqlConnection:

Func<String, Func<SqlConnection, DataSet>> ExecSqlT = x => y => ExecSql(y)(x);

看一個函數:

             static Func<int, int> GetAFunc()

        {

            var myVar = 1;

            Func<int, int> inc = delegate(int var1)

            {

                myVar = myVar + 1;

                return var1 + myVar;

            };

            return inc;

        }

 

如下調用輸入什麼結果:

            var inc = GetAFunc();

            Console.WriteLine(inc(5));

            Console.WriteLine(inc(6));

 

閉包

         閉包是可以包含自由(未綁定到特定對象)變數的代碼塊;這些變數不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變數)。

“閉包” 一詞來源於以下兩者的結合:要執行的代碼塊(由於自由變數被包含在代碼塊中,這些自由變數以及它們引用的對象沒有被釋放)和為自由變數提供綁定的計算環境(作用域)。<百度百科>

 

C#中實現閉包,實際就是通過類,封裝變數和方法,提升生命周期。

        static void Closure1()

        {

            List<Action> actions = new List<Action>();

            for (int i = 0; i < 10; i++)

            {

                int copy = i;

                actions.Add(() => Console.WriteLine(copy));

            }

            foreach (Action action in actions)

action();

        }

 

        static void Closure2()

        {

            int copy;

            List<Action> actions = new List<Action>();

            for (int i = 0; i < 10; i++)

            {

                copy = i;

                actions.Add(() => Console.WriteLine(copy));

            }

            foreach (Action action in actions)

action();

        }

 

緩存技術

怎樣使用閉包來實現緩存。如果我們創建了一個用於緩存的接收函數就可以實現緩存,並返回一個在一段時間內緩存結果的新函數。以下列表顯示的例子:

            public static Func<T> Cache<T>(this Func<T> func, int cacheInterval)

            {

                var cachedValue = func();

                var timeCached = DateTime.Now;

                Func<T> cachedFunc = () => {

                if ((DateTime.Now - timeCached).Seconds >= cacheInterval)

                {

                        timeCached = DateTime.Now;

                        cachedValue = func();

                     }

                     return cachedValue;

                };

                return cachedFunc;

            }

變數 cacheInterval, cachedValue 和 timeCached 綁定到緩存的函數並作為函數的一部分。這個可以讓我們記住最後的值並確認被緩存多長時間。

 

下麵的例子中我們可以看到如何使用這個擴展來緩存函數值和返回當前時間:

Func<DateTime> now = () => DateTime.Now;

            Func<DateTime> nowCached = now.Cache(4);

            Console.WriteLine("\tCurrent time\tCached time");

            for (int i = 0; i < 20; i++)

            {

                Console.WriteLine("{0}.\t{1:T}\t{2:T}", i + 1, now(), nowCached());

                Thread.Sleep(1000);

            }

 

惰性求值

C#語言小部分採用了非嚴格求值策略,大部分還是嚴格求值策略。

非嚴格求值的例子:邏輯或

static void NonStrictEvaluation()

        {

            bool ret = true || DoSomeThing() > 0;

            Console.WriteLine("Done!");

        }

嚴格求值策略:首先定義一個返回Int的方法

        static int DoSomeThing()

        {

            Console.WriteLine("DoSomeThing Function Excuted");

            return 7;

        }

static void StrictEvaluation(bool flag, int dsVal)

        {

            if (flag)

                Console.WriteLine("dsVal result value is {0}", dsVal);

            Console.WriteLine("Done!");

        }

調用:StrictEvaluation(false, DoSomeThing());

輸出:

DoSomeThing Function Excuted

Done!

雖然flag為false,但是DoSomeThing還是被執行了,如何改變?

將第二個參數改成方法:

static void LazyEvaluation(bool flag,Func<int> dsthing)

        {

            if (flag)

                Console.WriteLine("dsthing result value is {0}", dsthing());

            Console.WriteLine("Done!");

        }

調用:StrictEvaluation(false, DoSomeThing);

 

如果flag為true,並且其中調用兩次,那麼DoSomeThing就會被執行兩次。再次修改

static void LazyEvaluationEx(bool flag, Func<int> dsthing)

        {

            Lazy<int> lzDshting = new Lazy<int>(dsthing);

            if (flag)

      Console.WriteLine("dsthing square result value is {0}", lzDshting.Value * lzDshting.Value);

            Console.WriteLine("Done!");

        }

參考 http://www.cnblogs.com/yaozhenfa/category/652982.html

 

 


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

-Advertisement-
Play Games
更多相關文章
  • Spring Batch是一個輕量級的,完全面向Spring的批處理框架,可以應用於企業級大量的數據處理系統。Spring Batch以POJO和大家熟知的Spring框架為基礎,使開發者更容易的訪問和利用企業級服務。Spring Batch可以提供大量的,可重覆的數據處理功能,包括日誌記錄/跟蹤, ...
  • 一:共用賬號檢查 二:來賓賬戶檢查 三:口令複雜度策略 四:口令最長生存期策略 五:遠程關機授權 六:系統關閉授權 七:文件許可權指派 八:匿名許可權限制 八:登陸日誌檢查 九:系統日誌完備性檢查 十:日誌大小設置 十一:遠程登錄超時配置 十二:預設共用檢查 十三:共用許可權檢查 十四:防範病毒管理 十五 ...
  • 之前在公司用的服務端是wcf寫的,但是沒有深入研究,最近找工作,面試的時候好多人看到這個總提問,這裡做個複習 就用微軟官方上的例子,搭一個簡單的wcf服務,分6步 1 定義服務協定也就是契約,其實就是定義一個服務介面,這玩意後邊是公開客戶端用的,然後也告訴後邊承載程式應該如何載入服務 主要涉及兩個特 ...
  • 新人菜鳥 在開發一款軟體,用到了 MyS中的 DataView 查詢。查詢後判斷視圖是否有返回值,找了好久,終於找到了他的 DataView XX.Count 參數。 原文網址:http://skybirdzw.blog.163.com/blog/static/7257062620112334552 ...
  • 近期又看了一遍《C#高級編程》這本書,想對書中——任務、線程和同步這一章知識點做一個筆記,讓以後工作中忘記某個知識點能直接拿來用,在此進行一個總結。 Parallel數據和任務並行 一、Parallel.For 1、用Parallel.For並行運行迭代 static void ParallelFo... ...
  • 這裡彙總了.net基礎的相關文章,方便查閱! .net基礎 參考資料 http://www.cnblogs.com/JeffreyZhao/archive/2009/08/05/from-delegate-to-others.html http://www.cnblogs.com/JimmyZhan ...
  • 實習導師要求做一個項目,用Winform調用WebServices實現增刪改查的功能。寫下這篇博客,當做是這個項目的總結。如果您有什麼建議,可以給我留言。歡迎指正。 1、首先,我接到這個項目的時候,根本不清楚什麼是WebServices,所以第一步,我要搞清楚什麼是WebServices。現在有個模 ...
  • 水平分庫1.資料庫文件放到不同磁碟,充分利用磁碟io表分區1.表資料庫可以進行分區按照日期分區按照條件分區主動分區1.歷史數據放到歷史表當前表只放三天的數據(處理生產數據量大的表可以採用)垂直分庫把數據分成多個庫 根據業務模塊進行分用戶相關的數據都放到用戶資料庫上訂單相關數據都放到訂單資料庫上... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...