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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

函数式编程语言:LISP/Scheme 小语种简介

發(fā)布時(shí)間:2025/3/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 函数式编程语言:LISP/Scheme 小语种简介 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1、概述

自從裘宗燕教授翻譯了《計(jì)算機(jī)程序的構(gòu)造和解釋》(Structure and Intepretation of Computer Programs,SICP)第二版之后,這本MIT計(jì)算機(jī)系的編程入門教材開始越來越多地受到中國(guó)開發(fā)者的關(guān)注。同時(shí)受到關(guān)注的,還有它所介紹的函數(shù)式編程(Functional Programming),以及其中范例所使用的Scheme語言。

時(shí)光倒轉(zhuǎn)到30年前,1975年,Bill Gates和Paul Allen寫出了那個(gè)傳奇的BASIC版本——他們后來賣給MITS、換來“第一桶金”的那個(gè)BASIC版本。同一年,Gerald Sussman——他正是SICP的作者——?jiǎng)?chuàng)造了Scheme語言。其實(shí)Scheme并不是一種新鮮的語言,準(zhǔn)確地說,它只是LISP的一個(gè)變體、一種方言。早在1958年,John McCarthy就開始研究一種“用于處理列表數(shù)據(jù)”的語言——這也是LISP名字的由來(LISt Processing)。“列表處理”乍看上去是一個(gè)相當(dāng)特定的問題,但實(shí)際上這類問題有著深遠(yuǎn)而重要的內(nèi)涵,稍后我們就會(huì)看到這里的故事。

?

2、Scheme 語言特性與實(shí)例介紹

與LISP的其他方言相比,Scheme最大的特點(diǎn)或許在于:它是可以被編譯成機(jī)器碼的。也就是說,它的運(yùn)行效率更高。除此之外,Scheme在語言層面上可說是中規(guī)中矩。LISP素來奉仰的哲學(xué)思想是“微核心+高擴(kuò)展性”,Scheme也將這一特點(diǎn)發(fā)揮到了極致。Scheme內(nèi)置的關(guān)鍵字(keyword)少得可憐,就連大于小于、加減乘除等操作都是以函數(shù)的形式出現(xiàn)。甚至可以夸張一點(diǎn)說,只要有define關(guān)鍵字與括號(hào),就可以寫出所有程序。不過,這種風(fēng)格的一個(gè)副作用是會(huì)在程序中出現(xiàn)大量的括號(hào),所以也有人把LISP戲稱為“一大堆煩人的、教人看不懂的括號(hào)”(Lots of Irritating, Spurious Parentheses)。譬如說,下面的程序是用來求一個(gè)值的平方:

(define (square x)

??????(* x x))

(display (square 3))

對(duì)于我們這些從C語言開始入門、習(xí)慣了過程式編程(相對(duì)于“函數(shù)式編程”而言)的程序員,初接觸LISP/Scheme之時(shí),受到的第一個(gè)觸動(dòng)大概就是:Scheme不區(qū)分?jǐn)?shù)據(jù)與操作。仍然以“求平方”的例子,“x的平方”可以表述為“以1為基數(shù),將‘乘以x’計(jì)算兩次”。如果用C++語言,這個(gè)邏輯可以這樣實(shí)現(xiàn):

int square(int x) {

??????return 1 * x * x;

}

而在Scheme中,我們還可以這樣實(shí)現(xiàn):

(define (twice func base arg)

??????(func base (func base arg)))

(define (square x)

??????(twice * 1 x))

這種實(shí)現(xiàn)的特點(diǎn)在哪里?最大的特點(diǎn)就是:一個(gè)操作(乘法運(yùn)算)被當(dāng)作參數(shù)傳遞。按照程序設(shè)計(jì)的“黑話”,如果一個(gè)程序單元可以被作為參數(shù)和返回值傳遞,那么這個(gè)單元就被稱為“一等公民(first class)。在C/C++/Java等語言中,雖然也可以通過函數(shù)指針、functor等形式傳遞“操作”,但畢竟是經(jīng)過了包裝;而在Scheme中,可以將另一個(gè)函數(shù)直接作為參數(shù)傳入函數(shù),也可以作為返回值傳回另一個(gè)函數(shù),函數(shù)(也即“操作”)完全被作為一等公民對(duì)待

這樣做的好處是什么?在上面的例子中,我們把“兩次執(zhí)行某操作”的邏輯也抽象出來,得到了twice函數(shù)。如果我們想要實(shí)現(xiàn)“以0為基數(shù)將加法操作執(zhí)行兩遍”(也就是“乘以2”),只需要這樣寫:

(define (double x)

??????(twice + 0 x))

這里的twice函數(shù)是“對(duì)別的函數(shù)進(jìn)行操作的函數(shù)”,它的結(jié)果取決于傳入什么函數(shù)給它作為參數(shù)。像這種“函數(shù)的函數(shù)”,在函數(shù)式編程的術(shù)語中被稱為“高階函數(shù)”(high-order)。能夠很自然地實(shí)現(xiàn)高階函數(shù),是Scheme的第二個(gè)重要特點(diǎn)。在前面已經(jīng)提到過,LISP這個(gè)名字代表“列表處理”,其實(shí)處理列表數(shù)據(jù)的能力正是來自對(duì)高階函數(shù)的運(yùn)用。譬如說,我們有下面這樣一個(gè)列表:

{1, 2, 3, 4, 5}

針對(duì)這個(gè)列表,我們要做兩件事:

1.??????將每個(gè)元素翻倍,得到新的列表:{2, 4, 6, 8, 10}

2.??????對(duì)每個(gè)元素求平方,得到新的列表:{1, 4, 9, 16, 25}

用Java語言,我們可以這樣實(shí)現(xiàn):

List<int> doubleList(List<int> src) {

??????List<int> dist = new ArrayList<int>();

??????for(int item : src) {

????????????dist.add(item * 2);

}

return dist;

}

List<int> squareList(List<int> src) {

??????List<int> dist = new ArrayList<int>();

??????for(int item : src) {

????????????dist.add(item * item);

}

return dist;

}

問題一目了然:除了加粗的兩行代碼之外,這兩個(gè)方法幾乎是完全重復(fù)的。細(xì)想之下,其實(shí)這兩個(gè)方法做的事情非常相似:遍歷一個(gè)列表,按照“某種規(guī)則”將原列表的每個(gè)元素映射到新的列表中。因?yàn)檫@個(gè)“某種規(guī)則”是針對(duì)元素的映射操作,所以為了抽象這種列表操作,我們必須實(shí)現(xiàn)一個(gè)高階函數(shù),將實(shí)際的映射操作以參數(shù)的形式傳入。于是,在能夠方便實(shí)現(xiàn)高階函數(shù)的Scheme中,以上邏輯實(shí)現(xiàn)起來相當(dāng)容易:

(define (double-list src)

??????(map double src))

(define (square-list src)

??????(map square src))

由于高階函數(shù)在操作邏輯抽象方面的強(qiáng)大與便利,很多人開始尋求在“主流”的過程式語言中將操作當(dāng)作一等公民對(duì)待,進(jìn)而實(shí)現(xiàn)高階函數(shù)。譬如說,C#以delegate的形式允許將方法作為參數(shù)或返回值傳遞,并且在List<T>類型中加入了find等高階操作;Java世界的FunctionalJ(http://functionalj.sourceforge.net/)則在Java5提供的泛型基礎(chǔ)上提供了filter、map等常用的列表操作。前面的例子如果用FunctionalJ來實(shí)現(xiàn),就可以寫成:

// double and square are Function instances

List<int> doubleList(List<int> src) {

??????return Functions.map(double, src);

}

List<int> squareList(List<int> src) {

return Functions.map(square, src);

}

?

3、優(yōu)勢(shì)與不足

“不區(qū)分?jǐn)?shù)據(jù)與操作”這句話說起來很簡(jiǎn)單,其實(shí)背后蘊(yùn)含著一個(gè)重要的哲學(xué)問題,即“什么是時(shí)間”的問題。按照過程式編程的理念,“時(shí)間”是操作內(nèi)部的一個(gè)變量,程序以局部變量的形式記錄系統(tǒng)在各個(gè)時(shí)間點(diǎn)的瞬時(shí)狀態(tài);而按照函數(shù)式編程的理念,“時(shí)間”是操作外部的變量,以參數(shù)的形式傳入函數(shù),函數(shù)內(nèi)部則沒有局部狀態(tài),更沒有賦值操作。或者更簡(jiǎn)單一點(diǎn)說,在任何時(shí)候用同樣的參數(shù)調(diào)用同一個(gè)函數(shù),必定會(huì)得到同樣的結(jié)果。這種性質(zhì)被稱為“引用透明”。如果操作不具備引用透明性,就不能將它作為參數(shù)或返回值傳遞,因?yàn)檎{(diào)用環(huán)境和順序都可能改變高階函數(shù)的結(jié)果。

具備引用透明性的程序還有一項(xiàng)額外的好處:它們天生地具有線程安全性。不論有多少條線程、以什么順序訪問,只要程序具有引用透明性,就不需要額外的線程同步機(jī)制來保證結(jié)果正確。在同時(shí)面向眾多用戶的服務(wù)器端應(yīng)用、尤其是web應(yīng)用中,這一點(diǎn)顯得特別重要。Rod Johnson在他的《J2EE Development without EJB》一書中提倡“無狀態(tài)的Java服務(wù)器端應(yīng)用”,企業(yè)應(yīng)用的開發(fā)者們也從函數(shù)式編程的思想中受益匪淺。

據(jù)說LISP當(dāng)初的發(fā)明頗有些無心插柳的味道:McCarthy只實(shí)現(xiàn)了一個(gè)基于lambda運(yùn)算的抽象語法就把它扔到一邊,而他的學(xué)生們卻發(fā)現(xiàn)在這樣極簡(jiǎn)的語法中寫程序別有一番樂趣。有人說計(jì)算機(jī)科學(xué)家是一群喜歡歸約的人,LISP的發(fā)明卻是從實(shí)踐的角度證明:基本上所有的程序結(jié)構(gòu)都可以歸約為lambda運(yùn)算。按照Alonzo Church發(fā)明的丘奇代數(shù)(Church Calculus)理論,一切可以有效計(jì)算的函數(shù)——包括定值函數(shù)——都可以用lambda運(yùn)算來定義。譬如說,數(shù)據(jù)“0”和操作“加1”可以用lambda運(yùn)算定義如下:

(define zero (lambda (f) (lambda (x) x)))

(define (add-1 n)
? (lambda (f) (lambda (x) (f ((n f) x)))))

在此基礎(chǔ)上,就可以用lambda運(yùn)算定義整個(gè)自然數(shù)系。這是一個(gè)極端的例子。在別的很多地方,LISP/Scheme也能夠以類似的方式剖析我們習(xí)以為常的概念,讓我們獲得更加深入的洞見。譬如在SICP第二章“構(gòu)造數(shù)據(jù)抽象”中,我們親眼看到:平時(shí)所說的“面向過程編程”與“面向?qū)ο缶幊?/span>”,在很大程度上無非是使用同一組lambda運(yùn)算的不同語法糖而已。熟悉面向?qū)ο蟮某绦騿T在學(xué)習(xí)Scheme時(shí),常常會(huì)因?yàn)榭缭搅恕皵?shù)據(jù)”與“操作”的鴻溝而獲得一些全新的理解。再加上Scheme的語法極其簡(jiǎn)單,最常用的關(guān)鍵字大概不超過5個(gè),所以作為教學(xué)語言有著得天獨(dú)厚的優(yōu)勢(shì)——不少學(xué)校采用Java作為大學(xué)生的編程入門語言,當(dāng)你看著這些可憐的學(xué)生在兩個(gè)月之后還在與“匿名內(nèi)部類”之類詭異的語法和“IO流裝飾器”之類復(fù)雜的類庫糾纏不清時(shí),你不難理解我的意思。

但這種簡(jiǎn)單性也成為Scheme在企業(yè)應(yīng)用中推廣的最大障礙:企業(yè)應(yīng)用需要的不是許多種優(yōu)雅的可能性,而是一種可行的解決方案。雖然PLT等Scheme實(shí)現(xiàn)版本提供了XML、servlet等工具庫,但過于靈活的語法、最佳實(shí)踐的缺乏、以及沒有大廠商的支持,都讓Scheme終于無法成為企業(yè)應(yīng)用的主流。不過,盡管幾乎沒有真正的應(yīng)用,來自函數(shù)式編程的理念還是啟發(fā)著企業(yè)應(yīng)用的開發(fā)者們。譬如WebWork2.2引入的continuation特性,就是來自函數(shù)式編程的概念。

最后——但并非最不重要的——應(yīng)該說明:雖然很少在企業(yè)應(yīng)用中見到蹤影,但LISP/Scheme在科學(xué)計(jì)算、人工智能、數(shù)學(xué)建模等領(lǐng)域的運(yùn)用非常廣泛,所以把它稱作“小語種”多少有些不太公平。整體而言,LISP/Scheme強(qiáng)于算法邏輯的編寫,而不善于I/O操作。我們當(dāng)然可以說這是它失意于企業(yè)應(yīng)用領(lǐng)域的原因,但又何嘗不可以是它的結(jié)果呢?

總結(jié)

以上是生活随笔為你收集整理的函数式编程语言:LISP/Scheme 小语种简介的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。