日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

函数式编程常用术语

發布時間:2024/3/13 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 函数式编程常用术语 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??近年來函數式編程這種概念漸漸流行起來,尤其是在React/Vuejs這兩個前端框架的推動下,函數式編程就像股新思潮一般瞬間席卷整個技術圈。雖然博主接觸到的前端技術并不算深入,可這并不妨礙我們通過類似概念的延伸來理解這種概念。首先,函數式編程是一種編程范式,而我們所熟悉的常見編程范式則有命令式編程(Imperative Programmming)函數式編程(Functional Programming)邏輯式編程(Logic Programming)聲明式編程(Declarative Programming)響應式編程(Reactive Programming)等。現代編程語言 在發展過程中實際上都在借鑒不同的編程范式,比如Lisp和Haskell 是最經典的函數式編程語言,而SmartTalk、C++和Java則是最經典的命令式編程語言。微軟的C#語言最早主要借鑒Java語言,在其引入lambda和LINQ特性以后,使得C#開始具備實施函數式編程的基礎,而最新的Java8同樣開始強化lambda這一特性,為什么lambda會如此重要呢?這或許要從函數式編程的基本術語開始說起。

什么是函數式編程?

??我們提到函數式編程是一種編程范式,它的基本思想是將計算機運算當作是數學中的函數,同時避免了狀態和變量的概念。一個直觀的理解是,在函數式編程中面向數據,函數是第一等公民,而我們傳統的命令式編程中面向過程,類是第一等公民。為什么我們反復提到lambda呢?因為函數式編程中最重要的基礎是lambda演算(Lambda Calculus),并且lambda演算的函數可以接受函數作為參數和返回值,這聽起來和數學有關,的確函數式編程是面向數學的抽象,任何計算機運算在這里都被抽象為表達式求值,簡而言之,函數式程序即為一個表達式。值得一提的是,函數式編程是圖靈完備的,這再次說明數學和計算機技術是緊密聯系在一起的。雖然在博主心目中認為,圖靈這位天縱英才的英國數學家,是真正的計算機鼻祖,但歷史從來都喜歡開玩笑的,因為現代計算機是以馮.諾依曼體系為基礎的,而這一體系天生就是面向過程即命令式的,在這套體系下計算機的運算實則是硬件的一種抽象,命令式程序實際上是一組指令集。因此,函數式程序目前依然需要編譯為該體系下的計算機指令來執行,這聽起來略顯遺憾,可這對我們來說并不重要,下面讓我們來一窺函數式編程的真容:

squares = map(lambda x: x * x, [0, 1, 2, 3, 4]) print squares

這是使用Python編寫的函數式編程風格的代碼,或許看到這樣的代碼,我們內心是完全崩潰的,可是它實現得其實是這樣一個功能,即將集合{0, 1, 2, 3, 4}中的每個元素進行平方操作,然后返回一個新的集合。如果使用命令式編程,我們注定無法使用如此簡單的代碼實現這個功能。而這個功能在.NET中其實是一個Select的功能:

int[] array = new int[]{0, 1, 2, 3, 4}; int[] result = array.Select(m => m * m).ToArray();

這就是函數式編程的魅力,我們所做的事情都是由一個個函數來完成的,這個函數定義了輸入和輸出,而我們只需要將數據作為參數傳遞給函數,函數會返回我們期望的結果。好了,下面再看一個例子:

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4]) print sum

即使我們從來沒有了解過函數式編程,從命名我們依然可以看出這是一個對集合中的元素求和的功能實現,這就是規范命名的重要性。幸運的是.NET中同樣有類似的擴展方法,我喜歡Linq,我喜歡lambda:

int[] array = new int[]{0, 1, 2, 3, 4}; int result = array.Sum();

考慮到博主寫不出更復雜的函數式編程的代碼示例,這里不再列舉更多的函數式編程風格的代碼,可是我們從直觀上來理解函數式編程,就會發現函數式編程同lambda密不可分,函數在這里扮演著重要的角色。好了,下面我們來了解下函數式編程中的常用術語。

函數式編程的常用術語

??函數式編程首先是一種編程范式,這意味著它和面向對象編程一樣,都是一種編程的思想。而函數式編程最基本的兩個特性就是不可變數據和表達式求值。基于兩個基礎特性,我們延伸出了各種函數式編程的相關概念,而這些概念就是函數式編程的常用術語。常用的函數式編程術語有高階函數、柯里化/局部調用、惰性求值,遞歸等。在了解這些概念前,我們先來理解,什么是函數式編程的不可變性。不可變性,意味著在函數式編程中沒有變量的概念,即操作不會改變原有的值而是修改新產生的值。舉一個基本的例子,.NET中IEnumerable接口提供了大量的如Select、Where等擴展方法,而這些擴展方法同樣會返回IEnumerable類型,并且這些擴展方法不會改變原來的集合,所有的修改都是作用在一個新的集合上,這就是函數式編程的不可變性。實現不可變性的前提是純函數,即函數不會產生副作用。一個更為生動的例子是,如果我們嘗試對一個由匿名類型組成的集合進行修改,會被提示該匿名類型的屬性為只讀屬性,這意味著數據是不可改變的,如果我們要堅持對數據進行“修改”,唯一的方法就是調用一個函數。

高階函數(Higer-Order-Function)

??高階函數是指函數自身能夠接受函數,并返回函數的一種函數。這個概念聽起來好像非常復雜的樣子,其實在我們使用Linq的時候,我們就是在使用高階函數啦。這里介紹三個非常有名的高階函數,即Map、Filter和Fold,這三個函數在Linq中分別對應于Select、Where和Sum。我們可以通過下面的例子來理解:

  • Map函數需要一個元素集合和一個訪問該元素集合中每一個元素的函數,該函數將生成一個新的元素集合,并返回這個新的元素集合。通過C#中的迭代器可以惰性實現Map函數:
IEnumerable<R> Map<T,R>(Func<T,R> func, IEnumerable<T> list) {foreach(T item in list)yield return func(item); }
  • Filter函數需要一個元素集合和一個篩選該元素結合的函數,該函數將從原始元素集合中篩選中符合條件的元素,然后組成一個新的元素集合,并返回這個新的元素集合。通過C#中的Predicate委托類型,我們可以寫出下面的代碼:
IEnumerable<T> Filter<T>(Predicate<T> predicate, IEnumerable<T> list) {foreach(T item in list){if(predicate(item))yield return item;} }
  • Fold函數實際上代表了一系列函數,而最重要的兩個例子是左折疊和右折疊,這里我們選擇相對簡單地左折疊來實現累加的功能,它需要一個元素集合,一個累加函數和一個初始值,我們一起來看下面的代碼實現:
R Fold<T,R>(Func<R,T,R> func, IEnumerable<T> list, R startValue = default(R)) {R result = startValue;foreach(T item in list)result = func(result, item); return result; }

相信現在大家應該理解什么是高階函數了,這種聽起來非常數學的名詞,當我們嘗試用代碼來描述的時候會發現非常簡單。相信大家都經歷過學生時代,臨近期末考試的時候死記硬背名詞解釋的情形,其實可以用簡潔的東西描述清楚的概念,為什么需要用這種方式來理解呢?為什么我這里選擇了C#中的委托來編寫這些示例代碼呢?自然是同樣的道理啦,因為我們都知道,在C#中委托是一種類似函數指針的概念,因為當我們需要傳入和返回一個函數的時候,選擇委托這種特殊的類型可謂是恰如其分啦,這樣并不會影響我們去理解高階函數。

柯里化(Curring)/局部套用

??柯里化(Curring)得名于數學家Haskell Curry,你的確沒有看錯,這位偉大的數學家不僅創造了Haskell這門函數式編程語言,而且提出了局部套用(Currin)這種概念。所謂局部套用,就是指不管函數中有多少個參數,都可以函數視為函數類的成員,而這些函數只有一個形參,局部套用和部分應用息息相關,尤其是部分應用是保證函數模塊化的兩個重要技術之一(部分應用和組合(Composition)是保證函數模塊化的兩個重要技術)。眾所周知,在C#中一個函數一旦完成定義,那么它的參數列表就是確定的,即相對靜態。它不能像Python和Lua一樣去動態改變參數列表,雖然我們可以通過缺省參數來減少參數的個數,可是在大多數情況下,我們都需要在調用函數前準備好所有參數,而局部套用所做的事情與這個理念截然相反,它的目標是用非完全的參數列表去調用函數。我們來一起看下面這個例子:

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

這是一個由匿名方法定義的委托類型,顯然我們需要在調用這個方法前準備好兩個參數x和y,這意味著C#不允許我們在改變參數列表的情況下調用這個方法。而通過局部套用:

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

實際上在這里兩個參數x和y的順序對最終結果沒有任何影響,我們這樣寫僅僅是為了符合人類正常的認知習慣,而此時我們注意到我們在調用curriedAdd時會發生質的的變化:

//x和y同時被傳入add add(x,y) //x和y可以不同時被傳入curriedAdd curriedAdd(x)(y);

而如果我們將這里的函數用Lambda表達式來表示,則會發現:

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

至此,對一般的局部套用,存在:

Func<...> f = (part1, part2, part3, ...) => ... 可轉換為: Func<...> cf = part1 => part2 => part3 ... => ...

則稱后者為前者的局部套用形式。

惰性求值

??我們在前文中曾經提到過,在函數式編程中函數是第一等公民,而這里的函數更接近數學意義上的函數,即將函數視為一個可以對表達式求值的純函數,所以我們這里自然而然地就提到了惰性求值。首先,博主這里想說說求值策略這個問題,求值策略通常有嚴格求值和非嚴格求值兩種,而對C#語言來講,它在大多數情況下使用嚴格求值策略,即參數在傳遞給函數前求值。與之相對應的,我們將參數在傳遞給函數前不進行求值或者延遲求值的這種情況,稱為非嚴格求值策略。一個經典的例子是C#中的“短路”效應:

bool isTrue = (10 < 5) && (MyCheck())

因為在這里表達式的第一部分返回值為false,因此在實際調用中第二部分根本不會執行,因為無論第二部分返回true還是false,實際上對整個表達式的結果都不會產生影響。這是一個非常經典的非嚴格求值的例子,同樣的,布爾運算中的”||”運算符,同樣存在這個問題。所以,至此我們可以領會到惰性求值的優點,即使程序的執行效率更好,尤其是在避免高昂運算代價的時候,我們要牢記:懶惰是程序員的一種美德,使用更簡潔的代碼來滿足需求,是一名游戲程序員的永恒追求。我們可以聯想那些在代碼片段中優先return的場景,這大概勉強可以用這種理論來解釋吧!例如我們強大的Linq,原諒我如此執著于舉Linq的例子,Linq的一個特點是當數據需要被使用的時候開始計算,即數據是延遲加載的,而在此之前我們所有對數據的操作,從某種意義上來講,更像是定義了一系列函數,這好像和數據庫中的事務非常相近啦,其實這就是在告訴我們,懶惰是一種美德啊,哈哈!

函數式編程的利弊探討

??好了,現在讓我們從函數式編程的各種術語中解放出來,高屋建瓴般地從更高的層面上探討下函數式編程的利弊。當你討論一種東西的利弊時,一種習慣性的做法是找一種東西來和它作比較,如果Windows和Linux、SQL和NoSQ、面向對象和函數式…等等,我們常常關注一件事物的利弊,而非去尋找哪一個是最好。可惜自以為是的人類,常常以此來自我設限,劃分各自的陣營,這當真是件無聊的事情,就像我一直不喜歡SQL和正則表達式,所以我就去了解數據庫的設計、模式匹配相關內容,最終感覺頗有一番收獲,我想這是我們真正的目的吧!好了,下面我們說說函數式編程有哪些優缺點?首先,函數式編程極大地改善了程序的模塊化程度,高階函數、遞歸和惰性求值讓程序充分函數化,函數式讓編程可以以一種聲明式的風格來增強程序語義。當然,函數式編程的缺點是,我們這個現實世界本來就不是純粹的,函數式編程強調的數據不可變性,意味著我們無法去模擬事物狀態變化,因此我們不能為了追求無副作用、無鎖而忽視現實,這個世界上總有些骯臟的問題,無法讓我們用純函數的思維去解決,這個時候我們不能說要讓設計去適應這個世界,任何技術或者框架的誕生歸根到底是為了解決問題,而函數式編程或者是面向對象編程,本質都是一種編程思想,我們最終是為了解決問題,就像這個世界有時候并不是面向對象的,我們用面向對象來描述這個世界,或許僅僅是我們自己的理解,這個世界到底是什么樣子的,大概只有上帝會知道吧!

本文小結

??本文主要對函數式編程及其常見術語進行了簡要討論,主要根據《C#函數式程序設計》一書整理并輔以博主的理解而成。首先,函數式編程中強調無狀態、不可變性,認為函數是一等公民,并且在函數式編程中每一個函數都是一個純函數,它是數學概念咋計算機領域的一種延伸,和馮.諾依曼計算機體系不同,函數式編程的核心思想是以lambda演算為基礎的表達式求值,并且函數式編程強調無副作用。本文對函數式編程中的常見術語如高階函數、局部套用/柯里化、惰性求值等結合C#語言進行了簡單分析。或許對我們而言,函數式編程是一個新鮮事物,可正如我們第一次接觸面向對象編程時一樣,我們并不知道這樣一種編程思想會持續到今天。我不認為函數式編程會徹底替代面向對象編程,就像Web開發無法徹底替換原生開發一樣,函數式編程會作為面向對象的一種延伸和補充,所以本文對函數式編程的理解實際上是非常膚淺的,可這個世界本來就是在不斷變化的,希望我們可以在恰當的場景下去權衡選擇什么樣的技術,對這個世界而言,我們永遠都是探索者,或許永遠都不存在完全能滿足現實場景的編程范式吧!

總結

以上是生活随笔為你收集整理的函数式编程常用术语的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。