Kotlin 函数式编程(Kotlin Functional Programming)
Kotlin?函數式編程
(Kotlin?Functional?Programming)?
? ?陳光劍
1.?函數式概述?6
1.1.?函數式簡史?6
1.2.?函數式編程語言家族?7
1.2.1.?概述?7
1.2.2.?函數式編程語言介紹?8
1.3.?函數式編程的特征?10
1.3.1.?函數是"第一等公民"(First-class?and?higher-order?functions)?10
1.3.2.?沒有"副作用":純函數?11
1.3.3.?不修改狀態?11
1.3.4.?引用透明(Referential?transparency)?11
1.3.5.?只用"表達式",不用"語句"?11
1.4.?函數式編程的優勢?12
1.5.?怎樣進行函數式思維?12
1.5.1.?將運算視為函數的計算?12
1.5.2.?把運算過程寫成嵌套函數調用?12
1.5.3.?真正要掌握的是函數式的思維方式?12
1.6.?案例研究:以集合遍歷為例從命令式重構成函數式?13
1.7.?小結?13
2.?編程范式的轉變?13
2.1.?命令式?13
2.1.1.?什么是命令式編程?13
2.1.2.?從匯編到?C?語言?13
2.2.?聲明式?13
2.2.1.?什么是聲明式編程?13
2.2.2.?SQL?編程語言?13
2.2.3.?函數式編程風格?13
2.3.?編程的本質?13
2.3.1.?命令式:程序=數據結構+算法?13
2.3.2.?函數式:程序=?函數(函數(...))?13
2.4.?函數式接口:從面向對象到函數式?14
2.5.?狀態的改變?14
2.6.?映射的本質?14
2.7.?函數是一等公民?14
2.8.?從遞歸到遞歸?14
2.8.1.?函數調用與?Stack?14
2.9.?從函數到函數?14
2.9.1.?范疇論?14
2.9.2.?函子?14
2.10.?閉包?14
2.11.?Lambda表達式?14
2.12.?柯里化?14
2.13.?案例研究:map?和?reduce的使用?15
2.14.?小結?15
3.?λ演算?15
3.1.?什么是λ?演算?15
3.1.1.?判定性問題?15
3.1.2.?可計算函數?15
3.2.?BNF?范式表達語法?15
3.2.1.?什么是?BNF?范式?15
3.2.2.?λ?演算的?BNF?表達式?15
3.3.?圖靈機與λ?演算?15
3.3.1.?圖靈機與圖靈完備?15
3.3.2.?λ?演算vs.?圖靈機?15
3.4.?λ?演算的歸約策略?17
3.4.1.?α規約?17
3.4.2.?β-歸約?17
3.4.3.?Applicative?17
3.5.?并行與并發?18
3.5.1.?并行是什么?18
3.5.2.?并發是什么?18
3.5.3.?并發程序的設計?18
3.6.?案例研究:使用λ?演算定義自然數Church?整數?18
3.7.?小結?20
4.?Y?組合子?21
4.1.?什么是?Y?組合子?21
4.1.1.?函數不動點?21
4.1.2.?遞歸研究?21
4.2.?Y?組合子推導?21
4.3.?案例研究:多種編程語言實現?Y?組合子?21
4.4.?小結?21
5.?函數式編程中的設計模式?21
5.1.?設計模式是什么?21
5.2.?從OOP設計模式到函數式?21
5.2.1.?從迭代子到高階函數?21
5.2.2.?從策略模式到高階函數?21
5.2.3.?從模板方法到高階函數?21
5.3.?OOP?與函數式的設計模式對比?21
5.4.?代碼的表面積與體積?21
5.5.?案例研究:遞歸遍歷樹狀表格中的元素?21
5.6.?小結?21
6.?函數式元編程?21
6.1.?什么是元編程?22
6.1.1.?簡介?22
6.1.2.?元編程與反射?22
6.2.?類型系統與元編程?23
6.2.1.?什么是類型系統?23
6.2.2.?類型系統與程序設計語言?23
6.3.?運行時元編程與編譯時元編程?23
6.3.1.?泛型與注解?23
6.3.2.?反射?23
6.4.?案例研究:一個簡單的元編程的例子?23
6.5.?小結?23
7.?抽象到極致?23
7.1.?抽象之抽象:抽象到極致?25
7.1.1.?抽象是函數式編程最重要的概念?25
7.1.2.?將抽象推到極致?25
7.2.?函數式金字塔?25
7.3.?案例研究:怎樣把產品需求一步步“編譯”成系統的??25
7.4.?小結?25
8.?領域特定語言?DSL?25
8.1.?DSL?簡介?25
8.1.1.?什么是DSL?25
8.1.2.?通用程序設計語言與?DSL?25
8.2.?Kotlin?生態中的DSL?案例?25
8.2.1.?Gradle??構建腳本?25
8.2.2.?Spring?Bean的新玩法?25
8.2.3.?簡化?Android?開發的Anko?25
8.3.?案例研究:用?Kotlin?實現一個后端Ajax?請求的?DSL?25
8.4.?小結?25
9.?函子與單子?25
9.1.?函子簡介?25
9.1.1.?什么是函子?25
9.1.2.?函子的定義?25
9.1.3.?函子的例子?25
9.2.?單子簡介?26
9.2.1.?單子是什么?26
9.2.2.?組織有序操作?26
9.2.3.?定義任意的控制流?26
9.3.?案例研究:構建一個表格應用解析器?26
9.4.?小結?26
?
1.?函數式概述
1.1.?函數式簡史
?
??起源與基礎:λ演算(lambda?calculus)
??"函數式編程"是一種"編程范式"(programming?paradigm),也就是如何編寫程序的方法論。
??它屬于"結構化編程"的一種,主要思想是把運算過程盡量寫成一系列嵌套的函數調用。
–?在計算機科學中,函數式編程是一種編程范式。函數式將計算視為數學函數的調用,避免了狀態變化和可變數據。它是一種聲明式編程范式,函數式編程用表達式或聲明,而不是語句。
在函數代碼中,函數的輸出值只取決于它的參數,即不依賴于函數輸入的狀態變化,可以使理解程序變得更容易,這是開發函數編程的關鍵動機之一。因此相同入參的函數,總是返回相同的結果。這與命令式編程不同,命令式編程除了函數的參數外,全局程序狀態還會影響函數的結果值。
函數式編程起源于lambda演算,lambda演算是20世紀30年代開發的一個形式化系統,用于研究可計算性、可判定問題(Entscheidungsproblem)、函數定義、函數應用和遞歸。許多函數式編程語言都可以看作是lambda演算的發展豐富。
與函數式不同,命令式編程使用源代碼中的語句更改狀態。最簡單的例子是賦值。命令式編程有子程序,但這些不是數學函數。它們可能有副作用,可能會改變程序的狀態,允許函數沒有返回值。因此,它們缺乏引用透明性,也就是說,相同的語言表達式可以根據執行程序的狀態在不同的時間產生不同的值。
函數式編程在一定程度上只是被學術界重視。然而,支持函數式編程的編程語言,已經慢慢火爆起來,在工業中得到了越來越多的應用,包括常用Lisp、Scheme、?Clojure、Wolfram、?Racket、Erlang、OCaml、Haskell?和?F#?等。
JavaScript是世界上分布最廣泛的語言之一,除了命令式和面向對象的范式外,還具有動態類型函數語言的特性。函數式編程在某些特定領域也取得成功,比如統計中的R、J,?財務分析中的K和Q以及XML的XQuery/XSLT。特定于領域的聲明式語言,如SQL和Lex/Yacc。
現代編程語言基本都是多范式的。比如使用Perl、PHP、C++?11和Kotlin等。一個有趣的例子是Scala——它經常使用函數式編寫,但是由于副作用和可變狀態的存在,使得它處于命令式語言和函數式語言之間的灰色區域。
?
1.2.?函數式編程語言家族
1.2.1.?概述
–?如果我們關注各種語言的發展情況就會發現,所有的主流語言都在進行函數式方面的?擴充。早走一步的Groovy?已經具備了豐富的函數式特性,包括像“記憶”(memoization,指運行時自動緩存函數返回值的能力)這樣的高級特性在內。隨著lambda?塊(也就是高階函數)被納入Java?8,Java?語言也終于披掛上函數式的武器。JavaScript,這種也許算得上使用最為廣泛的語言,本身就擁有不少函數式特性。就連最老成持重的C++?語言,也在2011?年版的語言標準里增加了lambda?塊。
–?在計算機科學短短的發展歷史上,有時候會從技術主流分出一些枝杈,有源于實務界的,也有源于學術界的。例如在20?世紀90?年代個人電腦大發展的時期,第四代編程?語言(4GL)也出現了爆發式的流行,涌現了dBASE、Clipper、FoxPro、Paradox?等不可勝數的新語言。這些語言的賣點之一是比C、Pascal?等第三代語言(3GL)更高層次的抽象。
?
換言之,4GL?下的一行命令,3GL?可能要用很多行才寫得出來,因為4GL?自帶了更豐富的編程環境。像從磁盤讀取流行的數據庫格式這樣的功能,4GL?天生就具備,并不需要使用者特意去實現。
函數式編程也是這樣一根橫生出來的枝杈,是學術界那些樂于為新思路和新范式尋找?表達手段的計算機科學家們的發明。分出來的枝杈偶爾會重新匯入主流,函數式編程當前正好是這種情況。函數式語言不僅在Java?虛擬機(JVM)平臺上迅速地嶄露頭角,例如最有代表性的Scala?和Clojure?語言,.NET?平臺也不例外,F#?已經是堂堂正正的平臺一員。那么,為什么所有的平臺都在擁抱函數式編程呢?
20?世紀80?年代早期,有個編程環境叫作Pecan?Pascal。Pecan?Pascal的獨門絕技是可以在Apple?][?和IBM?PC?上運行相同的Pascal?代碼。為了做到這一點,Pecan?的工程師使用了神秘的“字節碼”(bytecode)。在編譯的時候,開發者寫下的Pascal源代碼會被編譯成這種在“虛擬機”上執行的“字節碼”,而“虛擬機”在每一種運行平臺上都有專門的原生實現。Pecan?Pascal?用起來讓人痛不欲生。就算最簡單的編程習題,編譯出來的代碼都慢得無法忍受。當時的硬件水平還沒有準備好迎接這樣的挑戰。Pecan?Pascal?被淘汰了,但它的架構我們都很熟悉。
十年之后Sun?發布了采用同樣設?計的Java,自動垃圾收集、跨平臺的特性使得它稱霸編程界二十余年。
人生苦短,把時間花在更高層次的抽象上,多考慮怎樣解決復雜的業務場景,少去費心復雜的底層運作。Java?在很大程序上,把程序員從人工管理內存中解放出來(當然,很多時候,你也不得不?JVM?性能調優,就像打地鼠一樣,有些問題始終需要人為地來解決的)。
函數式編程語言讓我們用高階抽象從容取代基本的控制結構,也有著同樣的意義。將瑣碎的細節交托給運行時,令繁冗的實現化作輕巧,這樣的例子比比皆是。
1.2.2.?函數式編程語言介紹
1.2.2.1.?Lisp
??1958年,約翰·麥卡錫(John?McCarthy)在麻省理工學院(MIT)工作時發明發明LISP。用于人工智能領域。Lisp是僅次于Fortran的第二古老的高級編程語言,并且從早期開始就發生了很大變化,并且在其歷史上存在許多方言。?Lisp?有兩個特征,函數式編程,和它是一門面向語言的語言。?今天,最廣為人知的通用Lisp方言是Common?Lisp和Scheme。?Emacs,G2,AutoCad,Igor?Engraver,Yahoo?Store?等大量成功的應用建立在Lisp語言之上。
??Common?LISP?特性
–?它與機器無關
–?它使用迭代設計方法,并且易于擴展。
–?它允許動態更新程序。
–?它提供高級調試。
–?它提供了高級的面向對象編程。
–?它提供了一個方便的宏系統。
–?它提供廣泛的數據類型,如對象,結構,列表,向量,可調整數組,哈希表和符號。
–?它是基于表達的。
–?它提供了面向對象的條件系統。
–?它提供了一個完整的I?/?O庫。
–?它提供了廣泛的控制結構。
??Lisp?實現?Fibonacci?數列函數:
(defun?fib?(n)
??(if?(<=?n?1)
??????1
??????(+?(fib?(-?n?1))
?????????(fib?(-?n?2)))))
(fib?10)
89
?
1.2.2.2.?Clojure
為什么Clojure???為什么還要寫另一種編程語言??基本上是因為我想要一個Lisp。函數式編程。與已建立的JVM平臺生態共生。函數式編程是一件好事。不可變數據?+?一等公民函數。
Clojure是一種動態類型的函數式語言。所有數據結構都是不可變的和持久的,支持遞歸。
Clojure?is?a?functional?programming?language.?It?provides?the?tools?to?avoid?mutable?state,?provides?functions?as?first-class?objects,?and?emphasizes?recursive?iteration?instead?of?side-effect?based?looping.?Clojure?is?impure,?in?that?it?doesn’t?force?your?program?to?be?referentially?transparent,?and?doesn’t?strive?for?'provable'?programs.?The?philosophy?behind?Clojure?is?that?most?parts?of?most?programs?should?be?functional,?and?that?programs?that?are?more?functional?are?more?robust.
user=>?(def?hello?(fn?[]?"Hello?World"))
#'user/hello
user=>?(hello)
"Hello?World"
1.2.2.3.?Haskell
An?advanced,?purely?functional?programming?language
??Haskell?is?lazy
That?means?that?unless?specifically?told?otherwise,?Haskell?won't?execute?functions?and?calculate?things?until?it's?really?forced?to?show?you?a?result.?
??Haskell?is?statically?typed
When?you?compile?your?program,?the?compiler?knows?which?piece?of?code?is?a?number,?which?is?a?string?and?so?on.?That?means?that?a?lot?of?possible?errors?are?caught?at?compile?time.?If?you?try?to?add?together?a?number?and?a?string,?the?compiler?will?whine?at?you.?Haskell?uses?a?very?good?type?system?that?has?type?inference.?That?means?that?you?don't?have?to?explicitly?label?every?piece?of?code?with?a?type?because?the?type?system?can?intelligently?figure?out?a?lot?about?it.
??Haskell?is?elegant?and?concise
Because?it?uses?a?lot?of?high?level?concepts,?Haskell?programs?are?usually?shorter?than?their?imperative?equivalents.?And?shorter?programs?are?easier?to?maintain?than?longer?ones?and?have?less?bugs.
?環境安裝
Run?the?following?in?your?terminal?(as?a?user?other?than?root),?then?follow?the?onscreen?instructions.
curl?https://get-ghcup.haskell.org?-sSf?|?sh
教程
Learn?You?a?Haskell?for?Great?Good!:?http://learnyouahaskell.com/chapters
1.2.2.4.?Erlang
簡介
Erlang是一個結構化,動態類型編程語言,內建并行計算支持。
Erlang是一種通用的面向并發的編程語言,它由瑞典電信設備制造商愛立信所轄的CS-Lab開發,目的是創造一種可以應對大規模并發活動的編程語言和運行環境。Erlang問世于1987年,經過十年的發展,于1998年發布開源版本。
Erlang是運行于虛擬機的解釋性語言,但是現在也包含有烏普薩拉大學高性能Erlang計劃(HiPE)開發的本地代碼編譯器,自R11B-4版本開始,Erlang也開始支持腳本式解釋器。在編程范型上,Erlang屬于多重范型編程語言,涵蓋函數式、并發式及分布式。
教程
Getting?Started?with?Erlang?User's?Guide:???http://erlang.org/doc/gettingstarted/usersguide.html
?
1.2.2.5.?JavaScript
1.2.2.6.?Python
1.2.2.7.?Java?8
1.2.2.8.?Scala
1.2.2.9.?Kotlin
?
1.3.?函數式編程的特征
1.3.1.?函數是"第一等公民"(First-class?and?higher-order?functions)
–?所謂"第一等公民"(first?class),指的是函數與其他數據類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數,傳入另一個函數,或者作為別的函數的返回值。
1.3.2.?沒有"副作用":純函數
純函數(Pure?functions,no?side?effects?(memory?or?I/O))。
所謂"副作用"(side?effect),指的是函數內部與外部互動(最典型的情況,就是修改全局變量的值),產生運算以外的其他結果。
函數式編程強調沒有"副作用",calling?the?pure?function?again?with?the?same?arguments?returns?the?same?result.
1.3.3.?不修改狀態
–?函數式編程只是返回新的值,不修改系統變量。因此,不修改變量,也是它的一個重要特點。
–?在其他類型的語言中,變量往往用來保存"狀態"(state)。不修改變量,意味著狀態不能保存在變量中。函數式編程使用參數保存狀態,最好的例子就是遞歸。
–?由于使用了遞歸,函數式語言的運行速度比較慢,這是歷史上函數式長期不能在業界推廣的主要原因。
1.3.4.?引用透明(Referential?transparency)
–?函數的運行不依賴于外部變量或"狀態",只依賴于輸入的參數,任何時候只要參數相同,引用函數所得到的返回值總是相同的。
–?函數的返回值與系統狀態有關,不同的狀態之下,返回值是不一樣的。這叫"引用不透明",很不利于觀察和理解程序的行為。
1.3.5.?只用"表達式",不用"語句"
–?"表達式"(expression)是一個單純的運算過程,總是有返回值;"語句"(statement)是執行某種操作,沒有返回值。函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,而且都有返回值。
原因是函數式編程的開發動機,一開始就是為了處理運算(computation),不考慮系統的讀寫(I/O)。"語句"屬于對系統的讀寫操作,所以就被排斥在外。
當然,實際應用中,不做I/O是不可能的。因此,編程過程中,函數式編程只要求把I/O限制到最小,不要有不必要的讀寫行為,保持計算過程的單純性。
?
1.4.?函數式編程的優勢
1.5.?怎樣進行函數式思維
1.5.1.?將運算視為函數的計算
計算機,Computer
1.5.2.?把運算過程寫成嵌套函數調用
1.5.3.?真正要掌握的是函數式的思維方式
??越來越多的命令式語言都在增加函數式能力。Java等現代編程語言中出現了越來越多的函數式特性(Java?8?),函數式編程的語法只是表象,?而我們真正需要掌握的是函數式編程的新思維。
函數式編程思想通過改換視角,讓我們站在了另一個抽象層次上,把編程問題看得更加清晰。
設計模式和代碼重用,在函數式語境下是怎樣的表現形態?
??函數式編程思維,脫離特定的語言特性,關注各種OOP語言的共同實踐做法,展示如何通過函數式語言解決問題。例如,如何利用函數式語言,通過高階函數、Lambda,?DSL?等完成代碼重用。
?
1.6.?案例研究:以集合遍歷為例從命令式重構成函數式
1.7.?小結
2.?編程范式的轉變
2.1.?命令式
2.1.1.?什么是命令式編程
2.1.2.?從匯編到?C?語言
2.2.?聲明式
2.2.1.?什么是聲明式編程
2.2.2.?SQL?編程語言
2.2.3.?函數式編程風格
2.3.?編程的本質
2.3.1.?命令式:程序=數據結構+算法
數據結構
函數式的數據結構
面向對象編程的數據結構
算法
函數式的算法
面向對象編程中的”方法“
2.3.2.?函數式:程序=?函數(函數(...))
一切皆是映射,映射即流,流即函數。
函數就是來描述事物的運動變化的。
?
2.4.?函數式接口:從面向對象到函數式
2.5.?狀態的改變
–?對比OOP,FP的核心就是一切都是函數,也就是說”活“的不再是對象,而是函數。
–?函數本質是x到f(x)的變換,所以FP的元素就兩種,一種是函數(”pure?function“的概念,表現為命名句柄或表達式,所以獨立性->并發性),一種是狀態(immutable,因為只要發生變換,就通過函數表現)。?
–?函數的獨立性,導致了我們沒法記錄函數內部運行的狀態,怎么解決呢,把狀態放在參數里唄,然后用遞歸來搞定。
2.6.?映射的本質
–?一切皆是映射
2.7.?函數是一等公民
??函數是一等公民,?函數可以當做參數傳遞給另外一個函數,?函數也可以作為另外一個函數的返回值。
2.8.?從遞歸到遞歸
2.8.1.?函數調用與?Stack
2.9.?從函數到函數
2.9.1.?范疇論
2.9.2.?函子
2.10.?閉包
2.11.?Lambda表達式
2.12.?柯里化
??表達式(1?+?2)?*?3?-?4
–?subtract(multiply(add(1,2),?3),?4)
–?鏈式調用:add(1,2).multiply(3).subtract(4)
2.13.?案例研究:map?和?reduce的使用
2.14.?小結
3.?λ演算
3.1.?什么是λ?演算
3.1.1.?判定性問題
3.1.2.?可計算函數
λ演算,λ(Lambda(大寫Λ,小寫λ)演算是一套用于研究函數定義、函數應用和遞歸的形式系統。它由?Alonzo?Church?和?Stephen?Cole?Kleene?在?20?世紀三十年代引入,Church?運用?lambda?演算在?1936?年給出?判定性問題?(Entscheidungsproblem)?的一個否定的答案。這種演算可以用來清晰地定義什么是一個可計算函數。關于兩個?lambda?演算表達式是否等價的命題無法通過一個通用的算法來解決,這是不可判定性能夠證明的頭一個問題,甚至還在停機問題之先。
3.2.?BNF?范式表達語法
3.2.1.?什么是?BNF?范式
3.2.2.?λ?演算的?BNF?表達式
?
3.3.?圖靈機與λ?演算
3.3.1.?圖靈機與圖靈完備
3.3.2.?λ?演算vs.?圖靈機
λ?演算可以被稱為最小的通用程序設計語言。它包括一條變換規則?(變量替換)?和一條函數定義方式,λ演算之通用在于,任何一個可計算函數都能用這種形式來表達和求值。因而,它是等價于圖靈機的。盡管如此,λ演算強調的是變換規則的運用,而非實現它們的具體機器??梢哉J為這是一種更接近軟件而非硬件的方式。它一個數理邏輯形式系統,使用變量代入和置換來研究基于函數定義和應用的計算。希臘字母λ被用來在λ演算模型中表示將一個變量綁定在一個函數中。
λ演算可以是有類型的也可以是無類型的,僅僅當輸入的的數據類型對于有類型的λ演算函數來說是可以接受的時,有類型的λ演算函數才能被使用。λ演算模型在數學,物理學,語言學和計算機科學等不同領域有著廣泛的應用。它在編程語言的理論發展上起到了很重要的作用,并對函數式編程起到了很大的影響,甚至可以說函數式編程就是對λ演算模型的一種實現。同時,它也是范疇論的當前研究對象之一。
λ演算模型最初的形式系統在1935年被?Stephen?Kleene?和?J.?B.?Rosser提出的Kleene–Rosser悖論證明為是前后矛盾的,接著,在1936年,Church單獨出版了λ演算模型中的和純計算有關的部分,也就是如今被稱為的無類型λ演算。在1940年,他提出了一個弱化計算,但是邏輯自洽的形式系統,如今被稱之為簡單類型λ演算。
在20世紀60年代之前,λ演算和編程語言之間的關系被厘清之前,λ演算模型一直都僅僅是一個理論上的形式系統,多虧了Montague和其他的幾位語言學家在自然語言的語義方面的研究,λ演算開始在語言學和計算機科學的研究中占有一席之地。
?
Peter?Landin1965年在論文?A?Correspondence?between?ALGOL?60?and?Church's?Lambda-notation中指出的一樣,面向過程的程序設計語言在λ演算模型的體系中是可以理解的,因為它提供了基本的過程抽象和程序應用機制。
匿名函數
以Lisp中的乘方函數為例,它可以使用lambda表達式表達為:
(lambda?(x)?(*?x?x))
以上是一個可以用作頭等函數的例子。在這個表達式中,lambda創造了一個匿名函數并給予了它一個參數列表(x),雖然它只有一個參數,而(*?x?x)則是函數的主體。這個函數在Haskell中的實現是完全一樣的。匿名函數有時候也被叫做lambda表達式。
再舉個例子,Pascal和其它的命令式語言?長期以來都支持將通過函數指針的機制來將子程序?作為參數傳遞到其它子程序里面去。然而,函數指針并不是?函數成為頭等函數類型的充分條件,因為一個頭等數據類型必須要能夠在運行時創建出一個它的實例。而支持運行時創建函數的語言有Smalltalk,Javascript,和最近的Scala,Eiffel,C#和C++11等。
3.4.?λ?演算的歸約策略
3.4.1.?α規約
3.4.2.?β-歸約
3.4.3.?Applicative?
在編程語言的理論研究中,求值策略(Evaluation?strategy)是一組用來確定程序設計語言中的表達式求值的規則。求值策略主要規定了在什么時候和用什么樣的順序給函數的實際參數求值,何時把參數代換入函數內,和用怎樣的形式來進行代換。通常,人們使用λ演算模型中的歸約策略來建模求值策略。
無論一個表達式是否為標準狀態,將這個這個表達式化為標準型所需要的工作量很大程度上依賴于歸約策略的使用。而歸約策略的不同又和函數式編程中的及早求值還有惰性求值之間的不同有關。
β-歸約?(β-reduction)
任何參數在任何時候都可以被歸約,其實就是沒有任何的歸約策略,天知道會發生什么。
應用次序?(Applicative?order)
最右邊,最內部的表達式總是首先被歸約,直觀上可以知道,這意味著函數的參數總是在函數調用之前就被歸約了。應用次序總是企圖用標準形式去調用函數,即便在很多時候這是不可能的。?大多數的程序設計語言(包括Lisp,ML和命令式語言C和Java等)都被描述為嚴格類型語言,意思是使用了不正確形式參數的函數是形式不正確的。它們在實際上就是使用了應用次序和傳值調用歸約,但通常被成為及早求值策略。
3.正常次序?(Normal?order)?最左邊,最外部的表達式總是首先被歸約,這也就意味著無論什么時候,參數都是再被歸約之前就被替換進了抽象的函數體里面了。
4.傳名調用?(Call?by?name)?和正常次序一樣,但是不會在抽象的函數體中再進行歸約,比如說,λx.(λx.x)x在這個策略中是正常形式,?雖然它包含了可歸約的表達式(λx.x)x
5.傳值調用?只有最外部的表達式被歸約:一個表達式僅僅當它的右邊已經被規約為一個值了才會被歸約
6.傳需求調用?“傳需求調用”和傳名調用類似,如果函數的實參被求值了,這個值就會被存儲起來已備未來使用。它產生的結果和傳名調用一樣;但是如果函數的這個實參被調用了多次,那么傳需求調用可以提高程序運行效率。它在現實語境中也被叫做惰性求值。
3.5.?并行與并發
3.5.1.?并行是什么
3.5.2.?并發是什么
3.5.3.?并發程序的設計
函數式編程在一開始就是面向并發處理的,這也得益于lambda的性質,lambda演算的Church-Rosser性質意味著歸約(β歸約)可以以任何順序進行,甚至是并行來進行。這意味著各種不同的非確定性歸約策略都是相近的。然而,lambda演算并不提供任何直接的并行結構。一個人可以添加像Futures結構體這樣的并發結構體到lambda演算中去。相關的進程代數已經為了進程通信和并發而被研究了出來。
在λ-演算的基礎上,發展起來的π-演算、χ-演算,成為近年來的并發程序的理論工具之一,許多經典的并發程序模型就是以π-演算為框架的。
3.6.?案例研究:使用λ?演算定義自然數Church?整數
在?lambda?演算中有許多方式都可以定義自然數,但最常見的還是Church?整數,下面是它們的定義:
0?=?λ?f.?λ?x.?x
1?=?λ?f.?λ?x.?f?x
2?=?λ?f.?λ?x.?f?(f?x)
3?=?λ?f.?λ?x.?f?(f?(f?x))
以此類推。直觀地說,lambda?演算中的數字?n?就是一個把函數?f?作為參數并以?f?的?n?次冪為返回值的函數。換句話說,Church?整數是一個高階函數?--?以單一參數函數?f?為參數,返回另一個單一參數的函數。
(注意在?Church?原來的?lambda?演算中,lambda?表達式的形式參數在函數體中至少出現一次,這使得我們無法像上面那樣定義?0)?在?Church?整數定義的基礎上,我們可以定義一個后繼函數,它以?n?為參數,返回?n?+?1:
SUCC?=?λ?n.?λ?f.?λ?x.?f?(n?f?x)
加法是這樣定義的:
PLUS?=?λ?m.?λ?n.?λ?f.?λ?x.?m?f?(n?f?x)
PLUS?可以被看作以兩個自然數為參數的函數,它返回的也是一個自然數。你可以試試驗證
PLUS?2?3?與?5
是否等價。乘法可以這樣定義:
MULT?=?λ?m.?λ?n.?m?(PLUS?n)?0,
即?m?乘以?n?等于在零的基礎上?n?次加?m。另一種方式是
MULT?=?λ?m.?λ?n.?λ?f.?m?(n?f)
正整數?n?的前驅元?(predecessesor)?PRED?n?=?n?-?1?要復雜一些:
PRED?=?λ?n.?λ?f.?λ?x.?n?(λ?g.?λ?h.?h?(g?f))?(λ?u.?x)?(λ?u.?u)
或者
PRED?=?λ?n.?n?(λ?g.?λ?k.?(g?1)?(λ?u.?PLUS?(g?k)?1)?k)?(λ?l.?0)?0
注意?(g?1)?(λ?u.?PLUS?(g?k)?1)?k?表示的是,當?g(1)?是零時,表達式的值是?k,否則是?g(k)?+?1。
邏輯與斷言
習慣上,下述兩個定義?(稱為?Church?布爾值)?被用作?TRUE?和?FALSE?這樣的布爾值:
TRUE?=?λ?u.?λ?v.?u
FALSE?=?λ?u.?λ?v.?v
斷言是指返回布爾值的函數。最基本的一個斷言?ISZERO,當且僅當其參數為零時返回真:
ISZERO?=?λ?n.?n?(λ?x.?FALSE)?TRUE
斷言的運用與上述?TRUE?和?FALSE?的定義,使得?"if-then-else"?這類語句很容易用?lambda?演算寫出。
遞歸
遞歸是一種以函數自身迭代自身變元的算法,一般是通過函數自身來定義函數的方式實現。表面看來?lambda?演算不允許遞歸,其實這是一種對遞歸的誤解。考慮階乘函數?f(n)?一般這樣遞歸地定義:
f(n)?=?1,?若?n?=?0;?n·f(n-1),?若?n>0.
λ語言示例
FACT?=?λ?n.?n?(λ?u.?MULT?n?(FACT?(PRED?n)))?1
用?Y-組合子?在?λ語言?中合法地定義:
FACT?=?Y?(λ?g.?λ?n.?n?(λ?u.?MULT?n?(g?(PRED?n)))?1)
Y?=?λ?f.?((λ?x.?f?(x?x))?(λ?x.?f?(x?x)))
?
3.7.?小結
?
?
4.?Y?組合子
4.1.?什么是?Y?組合子
4.1.1.?函數不動點
4.1.2.?遞歸研究
4.2.?Y?組合子推導
4.3.?案例研究:多種編程語言實現?Y?組合子
4.4.?小結
5.?函數式編程中的設計模式
5.1.?設計模式是什么
5.2.?從OOP設計模式到函數式
5.2.1.?從迭代子到高階函數
5.2.2.?從策略模式到高階函數
5.2.3.?從模板方法到高階函數
5.3.?OOP?與函數式的設計模式對比
5.4.?代碼的表面積與體積
5.5.?案例研究:遞歸遍歷樹狀表格中的元素
5.6.?小結
6.?函數式元編程
?
基于函數式語言的元編程系統,討論元編程系統特別是同構系統的語言特點。從程序反射的角度分析元編程系統對程序設計語言在自我表示、自我分析和控制等方面的要求。以?MetaML?和?Template?Haskell?為例論述在函數式語言中為了支持元編程需要擴展的機制,包括語法、語義、類型系統、安全的變量使用等,以及它們的實現方案、各方案的特點。最后總結一些元編程系統的共同點,并預測未來的發展趨勢。
6.1.?什么是元編程
6.1.1.?簡介
元編程(Metaprogramming)是指某類計算機程序的編寫,這類計算機程序編寫或者操縱其他程序(或者自身)作為它們的數據,或者在運行時完成部分本應在編譯時完成的工作。很多情況下與手工編寫全部代碼相比工作效率更高。編寫元程序的語言稱之為元語言,被操作的語言稱之為目標語言。一門語言同時也是自身的元語言的能力稱之為反射。
?
?
6.1.2.?元編程與反射
?
反射是促進元編程的一種很有價值的語言特性。把編程語言自身作為頭等對象(如Lisp或Rebol)也很有用。支持泛型編程的語言也使用元編程能力。
元編程通常有兩種方式起作用。一種方式是通過應用程序接口(API)來暴露運行時引擎的內部信息。另一種方法是動態執行包含編程命令的字符串。因此,“程序能編寫程序”。雖然兩種方法都能用,但大多數方法主要靠其中一種。
?
最常用的元編程工具是編譯器,把高級語言轉換為匯編語言或機器語言。更靈活的方法是在程序中嵌入解釋器直接處理程序數據。有一些實現例如為Object?Pascal編寫的RemObject's?Pascal?Script。
另一個很常用的元編程例子是lex和yacc,用來生成詞法分析器和語法分析器。Yacc通常用作編譯器的編譯器,生成一個把高級語言轉換為機器語言的工具。
quine是一種源代碼等于輸出的特殊的元程序。
面向語言的程序設計是一種強烈關注元編程的編程風格,通過領域特定語言來實現。
?
6.2.?類型系統與元編程
6.2.1.?什么是類型系統
6.2.2.?類型系統與程序設計語言
6.3.?運行時元編程與編譯時元編程
6.3.1.?泛型與注解
6.3.2.?反射
6.4.?案例研究:一個簡單的元編程的例子
?
6.5.?小結
?
7.?抽象到極致
什么是functional?programming?
FP并沒有明確的定義,只能通過個人(淺顯的)理解來回答了:
函數式編程是指一種編程范式,其first-class-value是函數,并有如下properties:
1.?因為函數為first-class-value,所以函數可以當參數和返回值傳遞
(這就產生了lambda表達式/匿名函數)
2.?immutable,也可叫purity或?referentially?transparent
referentially?transparent可由以下兩條規則概括:
An?expression?e?is?referentially?transparent?if?for?all?programs?p,?every?occurrence?of?e?in?p?can?be?replaced?with?the?result?of?evaluating?e?without?changing?the?result?of?evaluating?p.
A?function?f?is?pure?if?the?expression?f(x)?is?referentially?transparent?for?all?referentially?transparent?x.
3.?支持函數式編程的語言(或稱函數式語言)至少要實現/支持以上properties
(沒有顯式支持以上特性的語言,也可通過函數指針等來做非語言層面的支持)
(函數式編程源于lambda?calculus,這點樓上說的很清楚了;也正是lamdba?calculi?才使得其具有以上特性,不然就不可做規約與代換了)
其它的如Generalizing?Functions,ADT,Pattern?Matching,Lazy?Evaluation,CPS...都可視為上面特性的擴展/附加,也是FP及type?theory不斷發展的產物,為了更加類型安全,為了更好的Functional?Composition(函數/計算的組合)
到后面各語言引入各種Side?Effect機制,也是為了更好的函數組合;category?theory和abstract?algebra里面的一些東西(如:Functor,?Monad...)也被拿過來用于實現更好的FP(并且first-class-value?也慢慢轉變為了type)
函數式編程的思想就是上面所說的Functional?Composition。
?
7.1.?抽象之抽象:抽象到極致
7.1.1.?抽象是函數式編程最重要的概念
7.1.2.?將抽象推到極致
7.2.?函數式金字塔
7.3.?案例研究:怎樣把產品需求一步步“編譯”成系統的?
7.4.?小結
8.?領域特定語言?DSL
8.1.?DSL?簡介
8.1.1.?什么是DSL
8.1.2.?通用程序設計語言與?DSL
8.2.?Kotlin?生態中的DSL?案例
8.2.1.?Gradle??構建腳本
8.2.2.?Spring?Bean的新玩法
8.2.3.?簡化?Android?開發的Anko
8.3.?案例研究:用?Kotlin?實現一個后端Ajax?請求的?DSL
8.4.?小結
9.?函子與單子
總結
以上是生活随笔為你收集整理的Kotlin 函数式编程(Kotlin Functional Programming)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 勒索病毒尝试解决方法
- 下一篇: pgsql删除表中所有数据_pg数据库