ShoneSharp語言(S#)的設計和使用介紹系列(9)— 一等公民“函數“愛炫巧

来源:http://www.cnblogs.com/ShoneSharp/archive/2017/12/10/ShoneSharp-9.html
-Advertisement-
Play Games

匿名函數指函數定義體(即代碼塊)本身,使得函數成為所謂的“一等公民”,函數也可以像變數一樣進行賦值定義、傳遞和使用。本文還介紹了函數式編程的各種技巧,如嵌套、遞歸、高階、閉包等,站在函數式編程的頂峰,讓你感嘆“會當凌絕頂,一覽眾山小”! ...


 

ShoneSharp語言(S#)的設計和使用介紹

系列(9)— 一等公民“函數“愛炫巧

作者:Shone

聲明:原創文章歡迎轉載,但請註明出處,https://www.cnblogs.com/ShoneSharp。

摘要: 匿名函數指函數定義體(即代碼塊)本身,使得函數成為所謂的“一等公民”,函數也可以像變數一樣進行賦值定義、傳遞和使用。本文還介紹了函數式編程的各種技巧,如嵌套、遞歸、高階、閉包等,站在函數式編程的頂峰,讓你感嘆“會當凌絕頂,一覽眾山小”!

軟體: S#語言編輯解析運行器,運行環境.NET 4.0,綠色軟體,單個EXE直接運行,無副作用。網盤下載鏈接為https://pan.baidu.com/s/1dF4e08p

 

在所有支持函數式編程的語言中,如LISP, Python, Ruby, JavaScript,函數的地位很高,都被視為“一等公民”。其背後的理論依據就是Lambada演算,業界早已證明其表達能力與傳統過程式程式是等價的。函數式編程有很多技巧(即功能特性),高手能玩出花來。同樣S#語言也支持這些特性,可能語法還更簡潔靈活一點。

本文將全面展示“函數式編程”的各種特性或技巧,註意不是S#獨有,其他函數式語言也都有。其他語言的學習者也可以進來看看,對比一下自己的語言實現方式,可以提高自己的函數式編程領悟力。

一、匿名函數

匿名函數(其他語言也叫lambada式、函數模板、元函數或自定義函數)指函數定義體(即代碼塊)本身,可以當作變數進行傳遞和使用,使得函數成為所謂的“一等公民”。實現方式上其實是通過指向函數入口的指針、地址或引用傳遞,在程式後面調用匿名函數時則把當前程式跳轉到匿名函數體上執行,當然每次執行還要處理參數傳遞和堆棧進出。

S#匿名函數的表達方式很多種,其傳遞的語法解析樹入口節點的引用。

1.1 func專用公式

func(形參序列 : 結果公式)

func專用基本公式建立形參變數堆棧,並對結果公式進行解析,但不求值,最終輸出語法樹入口節點的引用。其中每個形參定義一個變數名稱,有多個變數時用逗號分割。func專用基本公式有局部變數堆棧,結果公式可以引用形參變數,還可以引用上層變數堆棧中的外部變數,若函數返回的是數組或列表還可以採用省略寫法。例如:

func(x: 2*x)          //單參數

func(x: x,2*x,3*x)    //單參數、返回數組

func(x: x;2*x;3*x)    //單參數、返回列表

func(x,y: (x+y)/2)    //多參數

 

func專用基本公式可以把它賦值給變數後像系統函數那樣調用(參見前一章節),其實還可以直接進行調用如下。

func(x: 2*x)(2)          //結果4

func(x: x,2*x,3*x)(2)    //結果[2,4,6]

func(x: x;2*x;3*x)(2)    //結果{2,4,6}

func(x,y: (x+y)/2)(2,5)  //結果3.5

 

func(形參賦值序列 : 結果公式)

func專用擴展公式在func專用基本公式基礎上擴展,差別對形參進行了初始化賦值。其中每個形參賦值寫法為:變數名稱=變數公式,有多個變數時用逗號分割。例如:

func(x=5: 2*x)           //單參數

func(x=5: x,2*x,3*x)     //單參數、返回數組

func(x=5: x;2*x;3*x)     //單參數、返回列表

func(x=5,y=10: (x+y)/2)  //多參數

 

func擴展公式可以採用與func專用基本公式一樣的調用方式如下。

func(x=5: 2*x)(2)            //結果4

func(x=5: x,2*x,3*x)(2)      //結果[2,4,6]

func(x=5: x;2*x;3*x)(2)      //結果{2,4,6}

func(x=5,y=10: (x+y)/2)(2,5) //結果3.5

 

func擴展公式還可以採用指定實參賦值的調用方式,註意由於實參調用有名字,因此賦值順序可以隨意,也可以預設,示例如下。

func(x=5: 2*x)(x=2)                //結果4

func(x=5: x,2*x,3*x)(x=2)          //結果[2,4,6]

func(x=5: x;2*x;3*x)(x=2)          //結果{2,4,6}

func(x=5,y=10: (x+y)/2)(x=2,y=5)   //結果3.5

func(x=5,y=10: (x+y)/2)(y=5,x=2)   //結果3.5

func(x=5,y=10: (x+y)/2)(x=2)       //結果6

func(x=5,y=10:
(x+y)/2)(y=5)       //結果5

 

1.2 func演算公式

func(形參序列) => 結果公式

func演算基本公式與func專用基本公式相似,差別主要是寫法上使用=>強調函數演算關係。註意1.2和1.3小節中的所有演算公式寫法在函數返回數組或列表時不可以採用省略寫法,必須顯式使用數組或列表的構造符號。例如:

func(x)=>2*x          //單參數

func(x)=>[x,2*x,3*x]  //單參數、返回數組

func(x)=>{x;2*x;3*x}  //單參數、返回列表

func(x,y)=>(x+y)/2)   //多參數

 

func演算基本公式的調用如下,註意函數定義體必須也加上()進行隔離,否則代碼含義不清。這種調用寫法,學習JavaScript的同學應該很熟悉,其中如果函數定義嵌套的層次深了,()就會更多,也會有LISP的即視感,哈!

(func(x)=>2*x)(2)            //結果4

(func(x)=>[x,2*x,3*x])(2)    //結果[2,4,6]

(func(x)=>{x;2*x;3*x})(2)    //結果{2,4,6}

(func(x,y)=>(x+y)/2)(2,5)    //結果3.5

 

func(形參賦值序列) => 結果公式

func演算擴展公式與func專用擴展公式相似,差別也是寫法上使用=>強調函數演算關係。例如:

func(x=5)=>2*x           //單參數

func(x=5)=>[x,2*x,3*x] //單參數、返回數組

func(x=5)=>{x;2*x;3*x} //單參數、返回列表

func(x=5,y=10)=>(x+y)/2)     //多參數

 

func演算擴展公式調用也要註意函數定義體必須也加上()進行隔離:

(func(x=5)=>2*x)(x=2)              //結果4

(func(x=5)=>[x,2*x,3*x])(x=2)      //結果[2,4,6]

(func(x=5)=>{x;2*x;3*x})(x=2)      //結果{2,4,6}

(func(x=5,y=10)=>(x+y)/2)(x=2,y=5) //結果3.5

(func(x=5,y=10)=>(x+y)/2)(y=5,x=2) //結果3.5

(func(x=5,y=10)=>(x+y)/2)(x=2)     //結果6

(func(x=5,y=10)=>(x+y)/2)(y=5)     //結果5

 

1.3 簡化演算公式

單個形參 => 結果公式

單參簡化演算公式與func演算基本公式相似,差別主要是寫法上只支持單個參數,表達上更加精簡,有C# lambada 單參數表達式的即視感。例如:

x=>2*x          //單參數

x=>[x,2*x,3*x]  //單參數、返回數組

x=>{x;2*x;3*x}  //單參數、返回列表

 

單參簡化演算公式的調用也要註意函數定義體必須也加上()進行隔離:

(x=>2*x)(2)           //結果4

(x=>[x,2*x,3*x])(2)   //結果[2,4,6]

(x=>{x;2*x;3*x})(2)   //結果{2,4,6}

 

() => 結果公式

無參簡化演算公式與單參簡化演算公式相似,差別主要是用()表示該函數無形式參數,與C# lambada 無參數表達式也類似。例如:

()=>3*PI           //單參數

 

註意S#的無參數匿名函數只有這麼一種寫法。無參簡化演算公式的調用也要註意函數定義體必須也加上()進行隔離:

(()=>3*PI)()          //結果9.42477796076938

 

二、函數定義

由於函數是一等公民了,因此函數通常也可以像變數一樣進行賦值定義。S#有兩種函數定義形式,但是其函數調用方法是一樣的。

2.1 隱式函數定義

變數名稱=匿名函數

這種寫法最為靈活多變,因為匿名函數被看作普通數據,可以通過命名變數進行傳遞和使用,無論是傳給其他函數作為實參調用,或是作為其他函數的返回值,都沒有任何違和感覺。例如:

{

f = x => 2 * x  ,

g = f(5)  ,                 //直接調用,g結果10

h = { a = f , b = a(5) }    //變數傳遞後間接調用,b結果10

}

 

隱式函數定義可以使用在任何變數賦值公式中,而且函數變數的作用域還可以用專門語法進行修改。函數式編程的好多技巧就是通過匿名函數和隱式定義實現的,本文後面小節會專門論述。

2.2 顯式函數定義(註:只能使用在eval專用公式中)

函數名稱(形參序列) = 結果公式

函數定義基本公式與func專用基本公式相似,差別主要直接指定函數名稱,並用=替代=>,最終輸出時把語法解析樹入口節點的引用賦值給指定名稱的函數變數。註意2.2小節中的所有函數定義寫法在函數返回數組或列表時也不可以採用省略寫法,必須顯式使用數組或列表的構造符號。例如:

eval(

f(x) = 2 * x  ,

g = f(5)  :              //直接調用,g結果10

{ a = f , b = a(5) }     //變數傳遞後間接調用,b結果10

)

 

從上面例子可以看出,顯式函數定義寫法更加簡潔,但是函數變數的作用域只能按照預設的公開設置,不可以修改。

函數名稱(形參賦值序列) = 結果公式

函數定義擴展公式在函數定義基本公式基礎上擴展,差別對形參進行了初始化賦值。例如:

eval(

f(x=1) = 2 * x  ,

g = f(x=5)  :               //直接調用,g結果10

{ a = f , b = a(x=5) }      //變數傳遞後間接調用,b結果10

)

 

從上面例子可以看出,顯式函數定義寫法更加簡潔,但是函數變數的作用域只函數名稱() = 結果公式

無參函數定義公式更加簡潔,直接使用()。例如:

eval(

f() = 2 * PI  ,

g = f()  :               //直接調用,g結果6.2831853071795862

{ a = f , b = a() }      //變數傳遞後間接調用,b結果6.2831853071795862

)

 

三、函數式編程技巧

3.1 函數嵌套調用

函數嵌套調用其實就是公式的嵌套,就是一個函數可以嵌套調用相同函數的另一次調用的結果作為參數。例如:

eval(

m(x) = x / 2  :    //定義對折函數

m(m(m(10)))       //三次對折後,結果1.25

)

 

函數嵌套調用使用上很簡單,考驗的是軟體對函數變數傳遞的處理。

 3.2 遞歸函數調用

遞歸函數是指函數定義體內部也調用了函數自身,其實這就形成了迴圈的嵌套函數調用,因此通常必須明確指定終止條件,否則會進入死迴圈導致程式出錯。

eval(

m(x) = if(x<2? x : m(x/2)) :   //定義遞歸對折函數

m(10)                          //遞歸對折後,結果1.25

)

 

3.3 函數參數是函數(高階函數)

函數參數是函數其實就是一個函數變數作為另一個函數的參數進行傳遞。

eval(

m(x)=x/2 ,         //定義對折函數

f(array,y)=each(k@array:y(k)) :    //定義映射函數

f([10,20,30],m)    //對數組映射對折,結果[5,10,15]

)

 

上面是手工寫,如果使用系統方法調用就會更加簡潔,一句話搞定:

[10,20,30].Cast(x=>x/2)      //映射元素,結果[5,10,15]

 

這裡的.Cast其實與其他C#的Convert,Python的map函數功能相同,可以接受函數作為參數。類似的函數還有:

[10,20,30].Where(x=>x%4==0)  //過濾元素,結果[20]

[10,20,30].Count(x=>x%4==0)  //條件計數,結果1

 

列表也一樣,例如:

{10,20,30}.Cast(x=>x/2)      //映射元素,結果{5,10,15}

{10,20,30}.Where(x=>x%4==0)  //過濾元素,結果{20}

{10,20,30}.Count(x=>x%4==0)  //條件計數,結果1

 

3.4 函數返回值是函數(閉包函數)

閉包是函數式編程的重要的語法結構,通常指能夠讀取其他函數內部變數的函數。很多編程語言只有函數內部的子函數才能讀取局部變數,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上閉包是將函數內部和函數外部連接起來的橋梁。

eval(

m() = eval(n=2: x=>x/n) :  //註意n為局部變數,使用閉包函數訪問

m()(10)     //調用閉包函數,打幾折由內部說了算,結果5

)

 

3.5 函數參數和返回值都是函數(高階閉包函數)

eval(

    g(f) = eval                    //傳入雙參函數
( x = 10 , y = 20 : n => n * f( x, y ) //返回單參函數 ) :
g(func(a,b)=>(a+b)/2)(10) //結果150 )

 

函數式編程的核心在於深入理解函數也是數據或變數的思想,只要你掌握其本質,那麼在各種複雜使用場景中就可以駕輕就熟。

站在函數式編程的頂峰,你會感嘆“會當凌絕頂,一覽眾山小”!

 

聲明:原創文章歡迎轉載,但請註明出處,https://www.cnblogs.com/ShoneSharp。

軟體: S#語言編輯解析運行器,運行環境.NET 4.0,綠色軟體,單個EXE直接運行,無副作用。網盤下載鏈接為https://pan.baidu.com/s/1dF4e08p


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

-Advertisement-
Play Games
更多相關文章
  • 背水一戰 Windows 10 之 控制項(控制項基類 - ContentControl, UserControl, Page): ContentPresenter, ContentControl, UserControl, Page ...
  • 返回總目錄 10 Form Template Method(塑造模板函數) 概要 你有一些子類,其中相應的某些函數以相同的順序執行類似的操作,但各個操作的細節不同。 將這些操作分別放進獨立的函數中,並保持它們都有相同的簽名,於是原函數也就變得相同了,然後將原函數上移至基類。 動機 繼承是避免重覆行為 ...
  • unity中會有像[range(0,1)]這樣的特性寫法,其非常方便的限制了變數範圍但是。我一直很好奇這是怎麼實現的,所以翻了翻其他博主對其的解釋和應用。 一,什麼是特性 有一種解釋我很能接受,特性就像牡蠣附在對象上。其本質也是一種對象,特殊之處在於其編譯時就存在了,也就是在程式運行之前就存在了。 ...
  • 註意:無論【協變】還是【逆變】都能 保證類型安全 ...
  • 今天練習C#的一個功能,就是將一個字元串時行翻轉顯示 如: 翻轉成為: 方法與寫法很多。 方法一: public void Reversal(string input) { string result = ""; for (int i = input.Length - 1; i >= 0; i--) ...
  • 1. 前言 WPF的本地化是個很常見的功能,我做過的WPF程式大部分都實現了本地化(不管最終有沒有用到)。通常本地化有以下幾點需求: 在程式啟動時根據 "CultureInfo.CurrentUICulture" 或配置項顯示對應語言的UI。 在程式運行時可以動態切換UI語言(無需重啟程式)。 製作 ...
  • 變數是指一塊存儲數據的記憶體空間,並且該記憶體區域的數據內容可以發生變化。 變數是必須先聲明後賦值。 基本的語法:數據類型+變數名; 變數名=數據; 簡單的使用技巧:聲明賦值簡寫 Static void Main (srring[] args) { int num1=2,num2=3; string s ...
  • 有的時候,我們要做的事情,就是簡單的重覆某個行為指定的次數。 就比如在這裡重覆輸出100個*號 在這裡,就可以使用一個for迴圈 for(i=0;i<100;i++) //定義一個i 讓它代表次數,重覆的動作就是:輸出*; 重覆次數:100; 重覆後要做的是:輸出*; { Console.Write ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...