zhcn 頭等函式；zhtw 一級函式；（firstclass function；第一級函式）是指在程式設計語言中，函式被當作頭等公民。這意味著，函式可以作為別的函式的引數、函式的返回值，賦值給變數或儲存在資料結構中。 有人主張應包括支援匿名函式（函式字面量，function literals）。Programming language pragmatics by Michael Lee Scott section 112 Functional Programming 在這樣的語言中，函式的名字沒有特殊含義，它們被當作具有函式型別的普通的變數對待。1960 年代中期，克里斯托弗斯特雷奇在 functions as firstclass citizens 中提出這一概念。 also on 20100216 zhcn 頭等函式；zhtw 一級函式； 是函式式程式設計所必須的。通常要使用高階函式。map 函式就是一個高階函式，其實參是一個函式及一個 list，返回結果是把作為引數的函式作用於 list 的每個元素後的結果形成的 list。 把函式作為函式引數與函式返回值會遇到特別的困難。特別是存在與、匿名函式。歷史上，這被稱作。Joel Moses The Function of FUNCTION in LISP or Why the FUNARG Problem Should be Called the Environment Problem MIT AI Memo 199 1970 早期的指令式程式語言，或者不支援函式作為結果型別（如 ALGOL 60 Pascal），或者忽略巢狀函式與非區域性變數（如 C 語言）。早期的函式式語言 Lisp 採取了動態作用域方法，把非區域性變數繫結到函式執行點最近的變數定義。Scheme 語言支援詞法作用域的 zhcn 頭等函式；zhtw 一級函式；，把對函式的引用繫結到閉包（closure）而不是函式指標，這使得垃圾收整合為必須。 概念 在這一節，比較把函式視作頭等公民的典型的函式式語言 Haskell 與把函式視作二等公民的指令式程式設計的 C 語言的有關概念。 高階函式：函式作為實參傳遞 具有函式引數的函式，稱為高階函式。函式式語言如 Haskell： map a b a b map f map f xxs f x map f xs 函式不是頭等公民的程式設計語言可以使用函式指標或 delegate，實現函式作為引數。C 語言例子 void map int fint int x size_t n for int i 0 i n i x i f x i 匿名與巢狀函式 對於支援匿名函式的語言 main map x 3 x 1 1 2 3 4 5 對於不支援匿名函式的語言，必須把函式繫結到一個名字上 int f int x return 3 x 1 int main int list 1 2 3 4 5 map f list 5 非區域性變數與閉包 一旦有了匿名函式與巢狀函式，引用函式體之外的變數（非區域性變數）就很自然了： main let a 3 b 1 in map x a x b 1 2 3 4 5 如果函式只能用函式指標表示，如何把函式體之外的值傳遞給函式就是個問題。可以手工建立一個閉包，但顯然這不能算作頭等函式： typedef struct int fint int int int a int b closure_t void map closure_t closure int x size_t n for int i 0 i n i x i closurefclosurea closureb x i int f int a int b int x return a x b void main int l 1 2 3 4 5 int a 3 int b 1 closure_t closure f a b map closure l 5 注意這裡的 map 是特化為使用當前環境外的兩個 int。即使 f 是個巢狀函式，仍然要面對同樣問題，這也是 C 語言不支援巢狀函式的理由。If you try to call the nested function through its address after the containing function has exited all hell will break loose GNU Compiler Collection Nested Functions 高階函式：返回函式作為結果 返回結果為函式時，實際上返回的是該函式的閉包。對於 C 語言，函式退出時其區域性變數也退出了各自的作用域，這使得構建閉包變得困難。這被稱為。 函式賦值給變數 把函式賦值給變數面臨著把函式當作返回結果一樣的困難：構建該函式的閉包： f Integer Integer f let a 3 b 1 in map x a x b map x b x a 函式的相等 判斷兩個函式是否相等，有不同的判據：Andrew W Appel 1995 Intensional Equality for Continuations 外延相等：兩個函式 f 與 g 如果是外延相等，當它們對同一輸入有相等的輸出。即 x f x g x 決定外延相等通常是不可判定問題，甚至在函式的定義域是有限時也不可行。 以數學上的函式來舉例：RR 的函式 f xx² 和 g xx 是外延相等的。 內涵相等：判斷兩個函式 f 與 g 是否相等，可以比較二者編譯後的結果。 例：上面的 f x 和 g x 不是內涵相等的，因為其定義式以及運算過程不同。 引用相等（Reference equality） 由於外延相等與內涵相等都不切實際，大多數語言支援用兩個函式的引用是否同一來判斷。函式或閉包繫結到獨一的識別符號（通常為其記憶體地址），根據其識別符號確定相等。兩個分開定義但具有同樣內容的函式被判斷為不等。 引用相等破壞了，因此純函式式語言如 Haskell 不採用這個方法。而另一方面，非純函式式的語言（如 C）也只能對函式 閉包物件與 空 判斷是否相等、即物件是否有引用到某函式上，而不定義兩個函式 閉包物件是否相等。 型別論 對於型別論，函式型別接受值型別 A 並返回值型別 B 可寫為 A B 或 BA。根據柯里 霍華德對應，函式型別可對應於邏輯蘊涵，lambda 抽象對應於 discharging hypothetical assumptions，函式呼叫對應於肯定前件推理規則。型別論還使用頭等函式建模關聯陣列與類似的資料結構。 對於範疇論，頭等函式對應於設定。例如，簡單型別 λ 演算 對應於笛卡兒閉範疇 CCC 的內部語言。 語言支援 函式式程式設計語言，如 Scheme、ML、Haskell、F、Scala，都具有完整的頭等函式。Lisp 作為最早的函式式語言在當初設計時對頭等函式各方面還沒有適當的理解，導致了採用動態作用域。後來的 Common Lisp 已經改為使用詞法作用域的頭等函式。 許多指令碼語言，如 Perl、Python、PHP、Lua、TclTk、JavaScript、Io，有頭等函式。 指令式程式設計語言，Algol 及 Pascal 族系、C 族系，與現代有垃圾收集的語言非常不同。Algol 族系允許巢狀函式與高階函式作為引數，但不允許函式作為返回值（除了 Algol 68）。因為當時還不清楚如何處理內嵌函式作為返回值時的非區域性變數問題（Algol 68 對此會產生執行期錯誤）。 C 族系允許函式作為引數與函式作為返回值，但由於不支援巢狀函式而避開了相關問題。因為返回巢狀函式並捕獲所使用的非區域性變數被認為才是真正有用，因此 C 族系不被認為有頭等函式。 現代指令式程式語言由於有垃圾收集功能而使得頭等函式成為可能。很多語言的後續版本開始支援頭等函式，如 C 20，Apple 公司的 C、C 與 ObjectiveC 的 Block 擴充套件。C11 開始支援了匿名函式與閉包。 語言 語言 高階函式 高階函式 非區域性變數 非區域性變數 非區域性變數 註釋 語言 語言 實參 返回結果 巢狀函式 匿名函式 閉包 註釋 Algol 家族 ALGOL 60 有函式型別 Algol 家族 ALGOL 68 有函式型別 Algol 家族 Pascal 有函式型別 Algol 家族 Oberon 有函式型別 Algol 家族 Delphi C 家族 C 有函式指標 C 家族 C Doc No 1968 V Samko J Willcock J Järvi D Gregor A Lumsdaine February 26 2006 Lambda expressions and closures for C 有函式指標、函式物件。使用 stdbind 可顯式部份應用。 C 家族 C 有 delegate 20 及 lambda 表示式 30 C 家族 Go C 家族 ObjectiveC 有函式指標 C 家族 Java 有匿名內部類 C 家族 Limbo C 家族 Newsqueak C 家族 Rust 函式式語言 Lisp 見下 函式式語言 Scheme 函式式語言 Clojure 函式式語言 ML 函式式語言 Haskell 函式式語言 Scala 指令碼語言 JavaScript 使用者程式碼在 ES3 上有部份應用 指令碼語言 PHP 使用者程式碼可有部份應用 指令碼語言 Perl 見下 指令碼語言 Python 見下 指令碼語言 Ruby 見下 其他語言 Io 其他語言 Maple 其他語言 Mathematica 其他語言 MATLAB 新的函式可自動產生部份應用 其他語言 Smalltalk 透過庫可有部份應用 其他語言 Fortran 其他語言 Swift 其他語言 Ada C C11 閉包可捕獲非區域性變數透過傳引用（不擴充套件非區域性變數的生存期）或者傳值的方式。 Java Java 8 閉包僅能捕獲 immutable effectively final 區域性變數。Java 沒有函式型別。Java 8 的匿名函式從上下文推導其型別，且必須是 functional interface （只有一個方法的介面）。 Lisp 詞法作用域 Lisp 的變種支援閉包。動態作用域的變種不支援閉包，需要特別構造閉包。 Common Lisp，函式名字空間的函式的識別符號不能用於頭等函式的值的引用。必須要特殊運算子 function 來獲取函式的值，如：function foo 得到一個函式物件。foo 是一個快捷表示。若想應用這樣的函式物件，必須用 funcall 函式：funcall foo bar baz Perl Perl 5 只允許匿名函式被巢狀。 Python Python 的匿名函式只允許表示式作為函式體。 從 25 版，使用 functoolspartial 來顯式部分應用，或從 28 版的 operatormethodcaller Ruby 普通函式的識別符號不能用作值或傳遞。必須透過 Method 或 Proc 物件來獲取頭等資料。呼叫這種函式物件的語法不同於呼叫普通函式的語法。 巢狀方法定義並不實際巢狀作用域。 用 curry 顯式 currying 操作 註釋 參考文獻 Functional Languages and HigherOrder Functions CSE5317CSE4305 Design and Construction of Compilers University of Texas at Arlington 參閲 eval 排除了頭等函式後的形式化 編譯器遞迴測試 外部連結 Firstclass functions on Rosetta Code Higher order functions at Category 資料型別 Category 函數語言程式設計 Category 程式設計語言理論 Category 子程式