從 Racket 入門函數式編程

一直想學學LISP,今天總算開了個頭。現在學習LISP不是爲了立就能夠以用於實際項目的應用,而是爲了學習一下函數式的思惟方式,能夠更加深刻的瞭解計算的本質,能夠更好的用C++, Java, Python等編敲代碼。更況且,這些主流語言都逐漸添加了函數式編程的特徵,C++,Java現在都引入了 Lambda 表達式。假設能夠系統學習一下LISP,相信對本身之後掌握這些語言的新特新特徵,對本身寫JavaScript、Python,對本身瞭解閉包、高階函數、Lambda表達式都會有很是大幫助。言歸正傳,首先推薦三個資源:html

Racket 的誕生與發展

簡介一下Racket的發展,詳見知乎的一個關於Racket的問題回答
1958年,人工智能之父John McCarthy 發明了一種以 Lambda 演算爲基礎的符號處理語言,1960年 McCarthy 發表著名論文 Recursive Functions of Symbolic Expressions and Their Computation by Machine, 今後這樣的語言被命名爲 LSIP (List Processor),其語法被命名爲:符號表達式(S-Expression)。LISP構建在7個函數 [atom car cdr cond cons eq quote] 和2個特型 [lambda label] 之上linux


Lisp誕生之初是爲了純粹的科學研究,代碼運行像數學公式同樣,以人的大腦來演算。直到麥卡錫的學生斯蒂芬·羅素將eval函數在IBM 704機器上實現後,纔開啓了Lisp做爲一種計算機語言的歷史。1962年,第一個完整的Lisp編譯器在MIT誕生,今後以後Lisp以MIT爲中心向全世界傳播。以後十多年,出現了各類Lisp方言。express


1975年,Scheme誕生。Scheme相同誕生與MIT,它的設計哲學是最小極簡主義,它僅僅提供必須的少數幾個原語,所有其它的有用功能都由庫來實現。在極簡主義的設計思想下,Scheme趨於極致的優雅,並做爲計算機教學語言在教育界普遍使用。編程


1984年,Common Lisp誕生。在二十世紀七八十年代,由於Lisp方言過多,社區分裂,不利於lisp整體的發展。從1981年開始,在一個Lisp黑客組織的運做下,通過三年的努力整合後,於1984年推出了Common Lisp。由於Scheme的設計理念和其它Lisp版本號不一樣,因此雖然Common Lisp借鑑了Scheme的一些特色,但沒有把Scheme整合進來。此後Lisp僅剩下兩支方言: Common Lisp 和 Scheme。閉包


從二十世紀九十年代開始,由於C++、Java、C#的興起,Lisp逐漸沒落。直到2005年後,隨着科學計算的升溫,動態語言JavaScript、Python、Ruby的流行,Lisp又漸漸的回到人們的視線。只是在Lisp的傳統陣地教育界,Python做爲強有力的挑戰者對Scheme發起衝鋒;在2008年,MIT放棄了使用Scheme做爲教學語言的SICP(計算機程序的構造和解釋)課程,而啓用Python進行基礎教學。同一時候美國東北大學另立爐竈,其主導的科學計算系統PLT Scheme開始迅猛發展;2010年,PLT Scheme更名爲Racket。近幾年,The Racket Language連續成爲年度最活躍語言站點,並駕齊驅的還有haskell站點。
app


符號表達式 S-Expression

首先說一下S表達式:S-表達式的基本元素是list與atom。list由括號包圍,可包涵不論什麼數量的由空格所分隔的元素,原子是其餘內容。其使用前綴表示法,在Lisp中既用做代碼,也用做數據。如:1+2*3  寫成前綴表達式就是 (+ 1 (* 2 3)) 。函數式編程

  • 長處:easyparse,簡單純粹,不用考慮什麼優先級等,也是實現代碼即數據的前提;
  • 缺點:可讀性不是很是強;

高階函數

高階函數至少知足下列一個條件:函數

  1. 接受一個或多個函數做爲輸入; 
  2. 輸出一個函數;

微積分中的導數就是一個樣例,映射一個函數到還有一個函數。在無類型 lambda 演算中,所有函數都是高階的。在函數式編程中,返回還有一個函數的高階函數被稱爲Curry化的函數。Curry化即把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數而且返回結果的新函數的技術。如 f(x,y)=x+y, 假設給定了 y=1,則就獲得了 g(x)=x+1 這個函數。學習


Lambda 表達式

Racket中有用Lambda表達式來定義匿名函數,《怎樣設計程序》書中給出的使用原則是:假設某個非遞歸函數僅僅需要當參數使用一次,使用Lambda表達式。假設想用Lambda表達式來表達遞歸,就需要引入Y組合子,Y 就是這樣一個操做符,它做用於不論什麼一個 (接受一個函數做爲參數的) 函數 F,就會返回一個函數 X。再把 F 做用於這個函數 X,仍是獲得 X。因此 X 被叫作 F 的不動點(fixed point),即 (Y F) = (F (Y F)) 。ui


惰性求值

惰性求值(Lazy Evaluation),說白了就是某些中間結果不需要被求出來,求出來反而不利於後面的計算也浪費了時間。參見:惰性求值與惰性編程
惰性求值是一個計算機編程中的一個概念,它的目的是要最小化計算機要作的工做。惰性計算的最重要的優勢是它可以構造一個無限的數據類型。使用惰性求值的時候,表達式不在它被綁定到變量以後就立刻求值,而是在該值被取用的時候求值。語句如 x:=expression; (把一個表達式的結果賦值給一個變量)明顯的調用這個表達式並把計算並把結果放置到 x 中,但是先不管實際在 x 中的是什麼,直到經過後面的表達式中到 x 的引用而有了對它的值的需求的時候,然後面表達式自身的求值也可以被延遲,終於爲了生成讓外界看到的某個符號而計算這個高速增加的依賴樹。


閉包

閉包在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。自由變量是在表達式中用於表示一個位置或一些位置的符號,比方 f(x,y) 對 x 求偏導時,y就是自由變量。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。在函數中(嵌套)定義還有一個函數時,假設內部的函數引用了外部的函數的變量,則可能產生閉包。執行時,一旦外部的 函數被執行,一個閉包就造成了,閉包中包括了內部函數的代碼,以及所需外部函數中的變量的引用。當中所引用的變量稱做上值(upvalue)。網上有很是多講 JavaScript 閉包的文章,假設你對 LISP 有系統的瞭解,那麼這個概念天然會很是清楚了。


快排的Racket實現

#lang racket
(define (quick-sort array)
  (cond
    [(empty? array) empty]                                                    ; 快排的思想是分治+遞歸
    [else (append 
           (quick-sort (filter (lambda (x) (< x (first array))) array))       ; 這裏的 array 就是閉包   
           (filter (lambda (x) (= x (first array))) array)
           (quick-sort (filter (lambda (x) (> x (first array))) array)))]))

(quick-sort '(1 3 2 5 3 4 5 0 9 82 4))
;; 執行結果 '(0 1 2 3 3 4 4 5 5 9 82)

經過這個樣例,就可以感覺到基於 lambda 算子的  Racket  語言強大的表達能力了。高階函數、lambda 表達式和閉包的使用使得 Racket 所描寫敘述的快排十分的精煉,這和基於馮諾依曼模型C語言是迥然不容的思惟模式。後面,隨着Racket 學習的進一步深刻,嘗試寫一下解釋器。

相關文章
相關標籤/搜索