first-class functions:函數是一等公民

  前些陣子在讀《javascript編程模式》一書時,發現了一個比較讓我疑惑的說法:

圖片

  什麼叫函數是一等「對象」?
  針對js這個語言,我深受「萬物皆對象」這句話的影響,或許僅僅是對字面意思的理解,在js中,根據原型繼承的機制,幾乎全部的對象都是繼承自Object構造函數所指向的原型對象(即Object.prototype),照這麼說,應該是「始祖對象」Object.prototype纔是這門語言的一等對象啊,因而百度一下"函數是一等對象",根本找不到這個說法,惟一的關鍵字,匹配也是我看的這本書,偶爾發現有人提出這個問題,可是直接被pass了,緣由是連回答問題的「大神」們,也壓根不知道這個玩意。
  難道是是這本書說錯了,或者說是這本書做者根本不出名?
  呵呵,其實都不是,今天我在wiki裏面意外地讀到這麼一個短語:「 first-class functions」,怎麼翻譯,蘿蔔青菜,各有所愛,大體的意思也就是那本書裏面說起到的「函數是一等對象」,查看英文版原著:
  「Functions As First-Class Objects In JavaScript, functions are first-class objects. They can be stored in variables, passed into other functions as arguments, passed out of functions as return values, and constructed at run-time. These features provide a great deal of flexibility and expressiveness when dealing with functions.As you will see throughout the book, these features are the foundation around which you will build a classically object-oriented framework.」
  顯然,關鍵詞語first-class是對Function最確切的形容,既然肯定了這句話的存在性,那麼存在即合理,接下來,我就只須要解決最根本的問題了:爲何函數是一等「對象」?
  一樣,針對這個名詞在國內這方面的非同一性,我必須得追溯英文了,既然這樣,那就查wiki吧:
  因而,我在wiki百科全書裏面查找到這樣的原文描述:
 "In  computer science, a  programming language is said to have  first-class functions if it treats  functions as  first-class citizens."
  顯然,對於函數是一等「對象」的說法,自己就是源於程序自己的,也就是說當一個編程語言自己就是把函數當成一等公民的時候,那麼咱們就稱呼這個語言擁有「first-class function」,咋一看,這尼瑪不就等於沒說麼?其實否則,間接地,它是在告訴咱們:在不一樣編程語言裏面,函數(對象)是處於不一樣地位的,怎麼去理解這句話,固然要繼續閱讀下去,咱們繼續看原文:
  "Specifically, this means that the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures. [1] [2] Some programming language theorists require support for  anonymous functions as well. [3] In languages with first-class functions, the  names of functions do not have any special status, they are treated like ordinary  variables with a  function type. [4] The term was coined by  Christopher Strachey in the context of 「functions as first-class citizens」 in the mid-1960s. [5]"
  從上面語句中,咱們大致能夠得到這樣的信息:
  1.這種語言(即把函數當作一等對象的編程語言),可以實現將函數做爲參數傳遞給另一個函數。
  2.它函數做爲一種返回值(或者對象結構),在另一個函數執行時return。
  3.分派函數成爲變量,或者將函數做爲一種數據結構進行存儲。
  4.在通常語言裏面,函數是必須有名字的(例如C++,C等),可是這種語言甚至支持匿名函數的存在,主要緣由就是這種語言把函數做爲了一種最基本的數據類型(function type),就像1,2,3同樣,這些整型數字,咱們沒有必要爲它們取名字。
  5.咱們知道了這個「functions as first-class citizens」是個專業的「term」(術語),是在60年代中期由 Christopher Strachey提出來的。
  既然這樣,在這種語言裏面,函數的做用甚至超過基本數據類型自己,可是它又擁有最基本數據類型一樣的待遇,那麼咱們徹底能夠認爲,這個函數在這種語言裏面的身份與地位是至關重要的了。到此爲止,咱們大概也就瞭解了爲何稱「函數是一等的」的說法,固然咱們要注意這是針對某種語言來講的,而不是全部語言的共性。
  在wiki中,當談及到函數式編程的時候,它又引入一個新名詞「 higher-order function」,什麼意思,我沒有管他,英語的解釋千奇百怪,當沒有官方的解釋的時候,英文自己就是最好的解釋,對此,「 higher-order function」遵循兩個最基本原則:
  1.take one or more functions as an input.
  2.output a function
  輸入一個或多個函數(對象)和輸出一個函數(對象)," higher-order function"其實是函數式編程的一個約束範例,真實的例子,即是map function,參見原文的map函數的描述:
  "A simple example of a higher-ordered function is the  map function, which takes as its arguments a function and a list, and returns the list formed by applying the function to each member of the list. For a language to support map, it must support passing a function as an argument."
  頗有意思的函數,就好像C語言的main函數似的(固然有本質的區別),它好像有一種把全部函數集合在一塊兒,再綜合調用的意思,可是它實現的功能是遠遠強大於C的main函數的,這裏我也不便多說,須要本身去體會。
固然,針對於C語言實現函數做爲參數傳遞,惟一的實現辦法估計也是指針吧,往下讀的時候,便找到這樣的代碼:
  1. passing functions as arguments將函數做爲參數傳入的實現方式:
1 void map(int (*f)(int), int x[], size_t n) {
2     for (int i = 0; i < n; i++) 
3         x[i] = (*f)(x[i]);
4 }

注意:C語言採用指針類型來實現。javascript

  2. Anonymous and nested functions匿名函數和內嵌函數的實現:
1 int f(int x) { 
2     return 3 * x + 1;
3 } 
4 int main() { 
5      int l[] = {1, 2, 3, 4, 5};
6      map(f, l, 5);
7 }
  注意:這裏,C語言並無匿名函數的支持,因此只能將函數名f,傳入map函數裏面。
  3. Non-local  variables and closures局部變量和閉包:
 1 typedef struct { 
 2     int (*f)(int, int, int);
 3     int *a; 
 4     int *b;
 5 } closure_t; 
 6 void map(closure_t *closure, int x[], size_t n) { 
 7     for (int i = 0; i < n; ++i) 
 8         x[i] = (*closure->f)(*closure->a, *closure->b, x[i]);
 9 } 
10 int f(int a, int b, int x) { 
11     return a * x + b;
12 }
13 void main() { 
14     int l[] = {1, 2, 3, 4, 5}; 
15     int a = 3; 
16     int b = 1; 
17     closure_t closure = {f, &a, &b};
18     map(&closure, l, 5);
19 }
  對於這裏,稍微停頓一下,我之前曾經談過閉包的一些定義,有句話叫作:「函數內部的函數即爲閉包」,其實這我想估計只是針對javascript來講的,那麼對於C語言來講,C語言裏面是不能在函數內部定義函數的,頂多在函數內部調用函數(而不是嵌套定義),對此,我查了一下官方一點的閉包的描述:
    「在 計算機科學中, 閉包(Closure)是 詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。」
    回過頭來,那麼對於wiki上對這段程序的描述則是這樣的:
    「Also note that the  map is now specialized to functions referring to two  ints outside of their environment. This can be set up more generally, but requires more  boilerplate code. If  f would have been a  nested function we would still have run into the same problem and this is the reason they are not supported in C.」
    由此本質上,C語言是不支持內嵌函數的,然而在 C語言中,存在一些模擬閉包的庫,支持 回調函數callback的庫有時在註冊時須要兩個參數:一個函數指針,一個獨立的 void*指針用以保存用戶數據。這樣的作法容許回調函數恢復其調用時的狀態。這樣的慣用法在功能上相似於閉包,但語法上有所不一樣。
    4. returning functions as results將函數做爲結果返回,這裏沒有C語言的實現方案,只有一段英文解釋:   
  "When returning a function, we are in fact returning its closure. In the C example any local variables captured by the closure will go out of scope once we return from the function that builds the closure. Forcing the closure at a later point will result in undefined behaviour, possibly corrupting the stack. This is known as the  upwards funarg problem."   
  「當返回一個函數自己時,咱們其實是返回了這個閉包(由於閉包就是一種函數),在這個C語言示例裏面,一旦咱們返回那些閉包,那麼那些閉包所約束的局部變量將會溢出它的做用域,強行返回一個‘later point’將會致使一個未定義行爲,頗有可能就是堆棧溢出,這就是所謂的‘ upwards funarg   problem
  這是我對這句話的拙劣的翻譯,拋開各類專業性的問題,顯然它是在告訴咱們C語言對這種機制的實現是不完善的。換一個角度來講,C語言或許在構造的時候就天生不是實現閉包的最佳語言,它是不太適合進行函數式編程模式的。    這裏,又有來自於wiki的一個總結:    
  "The C family allowed both passing functions as arguments and returning them as results, but avoided any problems by not supporting nested functions. (The gcc compiler does allows them as an extension.) As the usefulness of returning functions primarily lies in the ability to return nested functions that have captured non-local variables, instead of top-level functions, these language are generally not considered to have first-class functions."
  這裏面大體的意思是這樣的,C語言家族裏面,它是能夠實現函數的參數傳入和函數類型的返回(大體都是用指針來模擬實現的),可是因爲內嵌函數的不支持性(除了gcc編譯器對其的擴展,ps:多是一個特例吧),而且針對返回的函數必定要是有意義的這一層面來講,返回內嵌函數顯然是意義斐然的,因此C語言是不具備一等函數(first-class function)的,這裏我能夠補充一個javacript的例子:
1 var outer = function(){
2     var innerNum = 123;
3     return function(){
4         console.log(++innerNum);
5     };
6 }();
7 outer();
  這裏面外層的匿名函數的內部便有一個閉包函數,而且它引用了innerNum,咱們在外層的函數定義完成以後便即時調用它自身,此時便返回了該結構裏面的匿名函數,那麼最終outer其實是指向了內部的匿名函數,而且它有一個很厲害的功能——使用局部變量innerNum(這個變量,外部是沒法訪問的),這即是C語言不可能,或者說還沒有作到的東西,也是javascript最優美的特性之一,我這裏講的很膚淺,若是你對這個程序有興趣,能夠copy到瀏覽器的控制檯裏面試試結果。
  下面列出了一張對於該特性的語言支持狀況的表格:
Language Higher-order functions Non-local variables Partial application Notes
Arguments Results Nested functions Anonymous functions Closures
Algol family ALGOL 60 Yes No Yes No No No Have function types.
ALGOL 68 Yes Yes[9] Yes Yes No No
Pascal Yes No Yes No No No
Oberon Yes Non-nested only Yes No No No
C family C Yes Yes No No No No Has function pointers.
C++ Yes Yes No C++11[10] C++11[10] No Has function pointers, function objects. (Also, see below.)
C# Yes Yes No 2.0 / 3.0 2.0 No Has delegates (2.0) and lambda expressions (3.0).
Objective-C Yes Yes No 2.0 + Blocks[11] 2.0 + Blocks No Has function pointers.
Java Partial Partial No No 8 No Has anonymous inner classes.
Functional languages Lisp Syntax Syntax Yes Yes Common Lisp No (see below)
Scheme Yes Yes Yes Yes Yes SRFI 26[12]  
Clojure Yes Yes Yes Yes Yes Yes  
ML Yes Yes Yes Yes Yes Yes  
Haskell Yes Yes Yes Yes Yes Yes  
Scala Yes Yes Yes Yes Yes Yes  
Scripting languages JavaScript Yes Yes Yes Yes Yes ECMAScript 5 Partial application possible with user-land code on ES3 [13]
PHP Yes Yes Unscoped 5.3 5.3 No (see below)
Perl Yes Yes 6 Yes Yes 6[14]  
Python Yes Yes Yes Partial Yes 2.5[15] (see below)
Ruby Syntax Syntax Unscoped Yes Yes 1.9 (see below)
Other languages Mathematica Yes Yes Yes Yes Yes No  
Smalltalk Yes Yes Yes Yes Yes Partial Partial application possible through library.
Fortran Yes Yes Yes No No No
  此外,咱們從中看見了java在這個方面表現並不出色,或許在之後的版本會改變,可是針對某種語言,我並非在說明java的很差,反而我以爲這樣作是對的,由於java 的先天性的面向對象和語言結構的嚴謹,當一個語言在某一方面表現優秀的時候,天然會專一某一個領域,就像python同樣。每一種語言都有他本身魅力,不能輕視每一種語言的存在性價值,回到我所學習的javascript這門語言上來看,我覺的她是優美的,但也是年輕的,年輕天然是有活力的,固然也避免不了一些錯誤與糟粕。隨着HTML5和Ajax技術的發展,她的地位也從當初的玩具語言上升到一種」跨平臺式「(這個須要定義來考證)的主流語言,我之因此這樣描述是由於瀏覽器的存在維持了她的生命,現在她又以一種藤蔓式的生長姿態在向各個方向蔓延,好比CommentJs,Node.js,甚至微軟將JSscript做爲了win8應用開發的一種主要語言,現在的人們,不得不認可她的地位愈來愈明顯。愛也好,恨也罷,我以爲不要爲某種語言所綁定了,而是要昇華出來,咱們仍然是本身,不要覺得本身天生是寫什麼語言的料,因而其餘語言不屑一顧,我能夠去熱愛javascript,並不表明我要摒棄C#,java,C,C++這些一樣優秀的語言。   現在個人學習宗旨就是一個,學習其餘語言的機制與思想,借鑑過來,運用在我如今使用的語言上,個人學習目的便達到了,沒有必要去擔憂這個東西到時候面試會不會出,這個東西徹底沒有必要了解?不要被別人的思想所左右,普遍閱讀,咱們專精的不是語言,而是本身的興趣與目的,相信你也認同個人。
相關文章
相關標籤/搜索