Scheme
編程範型 | 多範式:函數式, 指令式, 元程式設計 |
---|---|
語言家族 | Lisp |
設計者 | 小蓋伊·史提爾和傑拉德·傑伊·薩斯曼 |
釋出時間 | 1975年 |
目前版本 |
|
型態系統 | 強型別,動態型別 |
作用域 | 詞法 |
副檔名 | .scm .ss |
網站 | www |
主要實作產品 | |
Bigloo, BONES[2], Chez, Chibi[3], Chicken, Cyclone[4], Foment[5], Gambit, Gauche, Guile, IronScheme, Kawa, Larceny, LIPS[6], Loko[7], MIT/GNU Scheme, Mosh[8], Picrin[9], Rapid[10], s7[11], S9fES[12], Sagittarius[13], Scheme 48, SCM, STklos, TinyScheme, TR7[14] | |
衍生副語言 | |
femtolisp[15], Husk[16], Racket, SIOD, Swift LispKit[17], T | |
啟發語言 | |
ALGOL, Lisp, MDL | |
影響語言 | |
Clojure, Common Lisp, Dylan, EuLisp, Haskell, Hop, ISLISP, JavaScript, Julia, Lua, R, Racket, Ruby, Rust, S, Scala |
Scheme是一種函數式程式設計語言,是Lisp的兩種主要方言之一,不同於與之並列的Common Lisp,Scheme遵循極簡主義哲學,以一個小型語言核心作為標準,加上各種強力語言工具(語法糖)來擴充語言本身[19]。Scheme是第一個使用靜態作用域的Lisp方言,也是第一個引入頭等續體和「乾淨宏」的程式語言。
簡介
在1975年,麻省理工學院的傑拉德·傑伊·薩斯曼與蓋伊·史提爾二世,開發出了Scheme語言最初版本,隨後兩人通過發表「λ論文集」而不斷對它進行完善和推廣。Scheme與λ演算關係十分密切,故將小寫字母「λ」用作標誌。
麻省理工學院與其他一些院校,曾採用Scheme教授電腦科學入門課程。著名的入門教材《電腦程式的構造和解釋》(SICP),利用Scheme來詮釋程式設計[20]。Scheme有眾多實現可視為一個主要優勢[21],然而不同實現之間的差異成為了它的一個劣勢,Scheme掌控委員會聲稱,它是「世上最不可移植的程式語言」,並且是一個「程式語言家族」而非一個單一的語言[22]。
歷史
起源
Scheme起源於1958年由約翰·麥卡錫提出的Lisp語言。麥卡錫通過Lisp證明了,經由幾個簡單的算子,與用作匿名函數的借鑒自阿隆佐·邱奇的λ表示法[23],就可以構建出圖靈完備的系統。麥卡錫提出的S-表達式,可以將程式與數據用相同的結構儲存,這被稱為同像性。Scheme的語法即來自S-表達式,這一特性使得在Scheme中實現自循環直譯器變得非常簡單[24]。
在1973年,麻省理工學院的Carl Hewitt提出的一種叫做演員模型的計算模型[25],並用Lisp開發當時叫做Planner-73的新語言來實現它[26]。傑拉德·薩斯曼與蓋伊·史提爾為了理解演員模型,決定在Maclisp工作環境中實現一個微型Lisp直譯器,並接着增加建立演員和傳送訊息的機制[27]。兩人很快發現了演員模型與λ演算之間的相似性,所謂「演員」就是彼得·蘭丁提出的閉包[28],而Joel Moses在1970年已將它介入Lisp用來解決函數參數問題[29];故而實現演員的關鍵,是將詞法作用域介入到Lisp中[30]。
在1975年,基於對λ演算表達能力的理解[31],傑拉德·薩斯曼與蓋伊·史提爾開發出了新型Lisp直譯器[32],並將在其上完成的微縮版的演員實現命名為「Schemer」,後因ITS作業系統檔名的字元數限制而改為Scheme[33]。儘管Hewitt指出了Scheme不能表達特定類型的原語演員[34],Scheme直譯器本身採用的簡約的語法和語意,很快贏得廣泛接受。
在1978年,蓋伊·史提爾二世和傑拉德·傑伊·薩斯曼發表了《修訂的Scheme報告:一種LISP方言》[35],從而完善了Scheme的功能,並正式將其確立為Lisp的一種主要方言。在1998年,二人在總結Scheme歷史時指出,簡單而強大的λ演算,使得Scheme最終得以實現極度的精簡化[36]。
λ論文集
「λ論文集」是傑拉德·薩斯曼與蓋伊·史提爾撰寫的關於Scheme的一系列論文,最早作為麻省理工學院的內部備忘錄發表[37]。通常認定λ論文集包括:
- 1975年: Scheme: An Interpreter for Extended Lambda Calculus.[38]
- 1976年: Lambda: The Ultimate Imperative.[39]
- 1976年: Lambda: The Ultimate Declarative.[40]
- 1977年: Debunking the 'Expensive Procedure Call' Myth, or, Procedure Call Implementations Considered Harmful, or, Lambda: The Ultimate GOTO.[41]
- 1978年: The Art of the Interpreter or, the Modularity Complex (Parts Zero, One, and Two).[42]
- 1978年: RABBIT: A Compiler for SCHEME.[43]
- 1979年: Design of LISP-based Processors, or SCHEME: A Dialect of LISP, or Finite Memories Considered Harmful, or LAMBDA: The Ultimate Opcode.[44]
- 1980年: Compiler Optimization Based on Viewing LAMBDA as RENAME + GOTO. AI: An MIT Perspective.[45]
- 1980年: Design of a Lisp-based Processor. CACM. 23. 11.[46]
標準化
Scheme的業界標準,是由專門的掌控委員會發表的《第n次修訂的演算法語言Scheme報告》(Revisedn Report on the Algorithmic Language Scheme),這種標題形式參照了ALGOL 60標準文件的標題[47]。Scheme曾經由IEEE標準化為IEEE 1178–1990,它於2019年11月07日停用[48]。
1998年通過的R5RS是現在被普遍接受的標準[49]。2007年通過的R6RS[50],做出了很大的變動[51],Scheme社區中有用戶指責它在堆積華而不實的功能[52][53]。Scheme掌控委員會決定在R7RS中,將Scheme分化為兩個獨立而相容的語言[54]:一個是2013年通過的R7RS-small[55],它為教學場合提供對IEEE/R5RS標準的及時更新,R7RS-small目前已經有了一些實現支援[56];而另一個是作為標準庫合集的R7RS-Large[57],它包含了各種提議被接納並進入凍結狀態的Scheme實現要求(SRFI),以此為實際編程場合提供對R6RS標準的繼續完善,Larceny和Chibi[3]提供了R7RS-Large過渡草案紅色版(即數據結構部份)的全部SRFI的實現。
命名法
在正式場合比如Scheme標準的命名法中,提及一個λ表達式或原始過程時,偏好使用詞語「過程」而非「函數」。在一般使用中,詞語「過程」和「函數」是可互換使用的。過程應用有時被正式稱呼為「組合」(combination)。
同其他Lisp一樣,在Scheme中使用術語「thunk」,來提及沒有實際參數的過程。術語「適當」(proper)尾遞歸,稱謂所有Scheme實現的一個性質,它們都進行尾遞歸最佳化,從而支援無限數目的活躍尾遞歸。
基礎特徵
Scheme大體上是一個函數式程式語言,並支援其他程式設計範式。它同其他Lisp程式語言家族語言共用了很多特徵。它的非常簡單的語法基於Lisp的S-表達式:即圓括號包圍的列表,其中的字首是算符,而隨後那些的是實際參數。故而Scheme程式由巢狀的列表的序列構成。列表也是Scheme中的主要數據結構,這導致了在原始碼和數據格式之間的緊密等價性,即同像性。Scheme程式可以輕易的動態建立和求值Scheme代碼片段。
Scheme與其他Lisp方言的列表,都是基於最基礎的數據結構有序對(pair)。Scheme從它的Lisp先輩繼承了一組豐富的列表處理原始運算,比如cons
、car
和cdr
。Scheme的變數都使用動態強型別系統,Scheme中的過程都是頭等物件,即頭等函數,因此過程可以作為值賦值給變數,或作為實際參數傳遞給過程。
本章主要集中於語言的創新特徵,它們使得Scheme區別於其他Lisp方言。除非專門指出,這裏描述的特徵都有關於R5RS標準。在本章例子中使用「===> 结果
」表示法,來指示求值在緊前行上的表達式的結果。這同於R5RS中使用的約定。本章節描述的特徵使得Scheme不同於在它之前的其他程式語言。Scheme的這些方面強烈的影響了Scheme語言的所有產品,並共通於始自1975年最初的λ論文,特別是自從R2RS以來[58],所有版本的Scheme程式語言。
極簡主義
Scheme的簡約性,使它成為具備同級別功能的程式語言中最易於實現的語言,這得益於使用λ演算來從更原始的形式匯出多數的語言語法[59]。例如在R5RS標準中,定義了23個基於S-表達式的語法構造,其中14個被歸類為衍生形式或庫形式,它們可以被寫為涉及原則上為lambdad的更基礎形式的宏。正如R5RS(§3.1)所說:「最基礎的變數繫結構造是lambda表達式,因為所有其他的變數繫結構造,都可以依據lambda表達式來做出解釋」[49]。
- 基礎形式:
define
、lambda
、quote
、if
、define-syntax
、let-syntax
、letrec-syntax
、syntax-rules
、set!
- 衍生形式:
do
、let
、let*
、letrec
、cond
、case
、and
、or
、begin
、命名let
、delay
、unquote
、unquote-splicing
、quasiquote
下面是一個宏的例子,它將let
實現為使用lambda
來進行變數繫結的一個表達式:
(define-syntax let
(syntax-rules ()
((let ((var expr) ...) body ...)
((lambda (var ...) body ...) expr ...))))
這裏的(let ((var expr) ...) body ...)
稱為「模式」,模式中起始的let
被稱為「關鍵字」,syntax-rules ()
的空括號中可添入作為輔助關鍵字的叫做「文字」的識別碼,var
、expr
和body
稱為「模式變數」,...
是稱為「省略號」的識別碼,它表示所跟隨的子模式可以匹配零或多個輸入中的元素;而((lambda (var ...) body ...) expr ...)
稱為「模板」。使用上述定義的let
,一個Scheme實現可以將(let ((a 1)(b 2)) (+ b a))
,重寫為((lambda (a b) (+ b a)) 1 2)
,這將實現任務縮減為編碼過程實例化的工作。
詞法作用域
不像更早期的Lisp比如Maclisp[60],Scheme像多數現代語言一樣,採用了詞法作用域[61]:在一個程式單元中所有可能的變數繫結,都可以通過閱讀這個程式單元來分析出來,而不需要考慮到它可能在其中被呼叫的那些上下文。這對比於動態作用域,它是早期Lisp方言的特徵,因為在當時的編譯器和直譯器中,用來實現詞法作用域演算法的原始的文字替換方法,關聯着處理代價。在動態作用域的Lisp中,對一個過程內的自由變數的參照,依賴於這個呼叫的上下文,完全有可能參照到這個過程外部的相當不同的繫結。
λ演算
邱奇的λ演算的數學表示法,啟發了Lisp使用lambda
作為介入一個過程的關鍵字,並影響了Lisp中涉及到使用高階函數的函數式程式設計技術的發展。但是早期的Lisp由於對自由變數的處理方式[62],而不適合表達λ演算[31]。
一個正式的λ系統,擁有一些公理和一個完備的推理規則。這有助於使用數理邏輯和工具來進行分析。在這個系統中,演算可以被看作直接的演繹。λ演算的語法,來自用x, y, z, ...
、圓括號、空格、點號和符號λ形成的遞歸表達式[63]。λ演算的功能包括:首先,充當強力的數理邏輯的起點。其次,它可以縮減編程者在考慮實現細節上的要求,因為它可以用於模擬機器求值。最後,λ演算建立了一個堅實的元理論[64]。
在Scheme中,lambda
關鍵字被用於定義匿名過程,並且使用define
基礎形式來定義命名過程,即將一個lambda
過程繫結到指名的全域變數[65]。在Scheme中,(define (foo x) (+ x 1))
與(define foo (lambda (x) (+ x 1)))
,在語法上是等同的。例如有序對可以表示為[66]:
(define (cons x y)
(lambda (m) (m x y)))
(define (car z)
(z (lambda (p q) p)))
(define (cdr z)
(z (lambda (p q) q)))
這樣定義出來的cons
、car
和cdr
,滿足恆等式(car (cons x y))
等於x
,和(cdr (cons x y))
等於y
。甚至遞歸也可以表示為利用λ演算的Y組合子[67]。
詞法作用域的介入[61],通過在某些形式的λ表示法,和在工作程式語言中它們的實際表達之間做出等價,解決了早期Lisp的有關問題。Sussman和Steele展示了,通過將λ表達式用作「控制結構和環境修改器」,而非簡單的過程實例化,可以用這個新語言優雅的匯出其他程式語言包括ALGOL和Fortran的,所有指令式和聲明式語意,和其他Lisp的動態作用域[68]。他們在第一篇λ論文中,與對Scheme的首次描述一起,介入了續體傳遞風格[69],並在後續的論文中,他們推進演示了在這種實際使用中體現出的λ演算的原生能力。
過程應用中的求值次序
Scheme採用了傳值呼叫,但不同於多數Lisp規定了過程實際參數的求值次序,Scheme不規定求值次序。對比於其他Lisp,Scheme表達式在算符位置(第一個專案)上可以是一個表達式,只要在算符位置上的表達式的結果是一個過程,這種表示就是完全合法的[70]。
在Scheme中,在算符和實際參數碼置上的這些表達式的求值次序,可以在逐個呼叫的基礎上由實現來選擇,而唯一的約束是:「運算子和運算數表達式的任何並行求值的效果,被約束為一致於某種順序次序的求值。」(R5RS sec. 4.1.3)[49]
(let
((ev (lambda (n)
(display "Evaluating ")
(display (if (procedure? n) "procedure" n))
(newline)
n)))
((ev +) (ev 1) (ev 2)))
ev
是一個過程,它描述傳遞給它的實際參數,接着返回這個實際參數的值。在呼叫過程+
來加1
和2
之中,表達式(ev +)
、(ev 1)
和(ev 2)
可以按任何次序求值,只要效果上它們不像是並列求值的。因此在上述例子被執行的時候,標準Scheme可以按任何次序來顯示前三行:
Evaluating procedure
Evaluating 1
Evaluating 2
3
然而一行的文字不可以同另一行的文字產生交錯,因為這會違反順序求值約束。
塊結構
Scheme的代碼塊結構,不同於早先Lisp的語言構造[71]。自從R2RS開始[58],Scheme中的塊,是通過三個「繫結構造」來實現的:let
、let*
和letrec
。例如,下列構造建立一個塊,其中叫做var
的符號被繫結到數10
:
(define var "goose")
;; 这里任何到var的引用都会被绑定到"goose"
(let ((var 10))
;; 语句走到这里时,任何到var的引用都会绑定到10
)
;; 这里任何到var的引用都会绑定到"goose"
依據編程者的需要,塊可以巢狀來建立任意複雜的塊結構。使用塊結構來建立局部繫結,減輕了不然可能發生的命名空間衝突。
let
的變體之一let*
,允許繫結參照在前面相同構造中定義的變數,例如:
(let*
((var1 10)
(var2 (+ var1 12)))
;; 但是var1的定义不可以引用var2
)
let
的另一個變體letrec
,被設計用來確使互遞歸過程可繫結彼此。
;; 将侯世达雌雄序列计算为有序对的列表
(define (hofstadter-male-female n)
(letrec
((female (lambda (n)
(if (= n 0)
1
(- n (male (female (- n 1)))))))
(male (lambda (n)
(if (= n 0)
0
(- n (female (male (- n 1))))))))
(let loop ((i 0))
(if (> i n)
'()
(cons (cons (female i) (male i))
(loop (+ i 1)))))))
(hofstadter-male-female 8)
===> ((1 . 0) (1 . 0) (2 . 1) (2 . 2) (3 . 2) (3 . 3) (4 . 4) (5 . 4) (5 . 5))
在一個單一的letrec
中的所有過程,可以通過名字參照其他的過程,還有在相同的letrec
中此前定義的變數的值,但是它們不可以參照在相同的letrec
中此後定義的變數的值。
let
的變體「命名let
」形式,在let
關鍵字之後有一個識別碼。它將這些let
變數繫結到一個過程的實際參數,這個過程的名字由這個識別碼給出,它的過程體是let
形式的主體。這個過程體可以通過呼叫這個過程達成想要的重複。命名let
被廣泛用於實現迭代。
一個簡單的計數器例子:
(let loop ((n 1))
(if (> n 10)
'()
(cons n
(loop (+ n 1)))))
===> (1 2 3 4 5 6 7 8 9 10)
就像Scheme中的任何過程一樣,在命名let
中建立的這個過程是頭等對象。
適當尾遞歸
Scheme擁有一個迭代構造do
[72],但是在Scheme中更推崇的慣用法,是使用尾遞歸來表達迭代[73]。遵循標準的Scheme實現被要求最佳化尾遞歸,從而支援無限數量的活躍尾遞歸(R5RS sec. 3.5[49]),這個性質被Scheme報告描述為「適當」(proper)尾遞歸,它使得Scheme編程者可以安全的使用遞歸結構來書寫迭代演算法,這在很多時候是更符合直覺的。尾遞歸過程和命名let
形式,提供對使用尾遞歸的迭代的支援。
;; 建造从0到9的平方的列表:
;; 注意:loop简单的就是用作标签的一个任意符号。任何符号都行。
(define (list-of-squares n)
(let loop ((i n) (res '()))
(if (< i 0)
res
(loop (- i 1) (cons (* i i) res)))))
(list-of-squares 9)
===> (0 1 4 9 16 25 36 49 64 81)
頭等續體
在Scheme中續體是頭等對象[74]。自從R2RS開始[58],Scheme提供了過程call-with-current-continuation
,簡寫為call/cc
,它擷取當前續體,將其包裝成逃脫(escape)過程,再繫結到編程者提供的一個過程的唯一形式參數上。如果逃脫過程在後來某個時候被呼叫,它會放棄在後來時候正生效的任何續體,轉而使用在建立這個逃脫過程之時生效的那個續體。逃脫過程與原本呼叫call/cc
的續體,接受相同數目的實際參數;除了用call-with-values
建立的續體,所有續體恰好接受一個值(R5RS sec. 6.4)[49]。
頭等續體使得編程者能夠建立非局部控制結構比如迭代器、協程和回溯。續體可以被用來模擬指令式程式語言中return陳述式的行為。下列函數find-first
接受函數func
和列表lst
,返回lst
中的第一個元素x
,它使得(func x)
返回真。
(define (find-first func lst)
(call/cc
(lambda (return)
(for-each
(lambda (x)
(if (func x)
(return x)))
lst)
#f)))
(find-first integer? '(1/2 3/4 5.6 7 8/9 10 11))
===> 7
(find-first zero? '(1 2 3 4))
===> #f
David Madore在1999年提出的陰陽謎題[75],展示了Scheme可以將續體當作頭等對象處理,繫結它們到變數,和把它們作為給過程的實際參數來傳遞[76]。
統一的命名空間
對比於Common Lisp,在Scheme中所有的數據和過程共用一個共同的命名空間,而Common Lisp中有函數和數據分離的命名空間,使得一個函數和一個變數可以有相同的名字,並且將一個函數作為值參照時要求特殊的表示法。這有時叫做「Lisp1與Lisp2」差異,二者分別稱謂Scheme的統一的命名空間,和Common Lisp的分立的命名空間[77]。
在Scheme中, 可以使用操縱和繫結數據的原始運算來繫結過程。沒有等價於Common Lisp的defun
和#'
的原始運算。
;; 变量绑定到一个数:
(define f 10)
f
===> 10
;; 变化(改变绑定值)
(set! f (+ f f 6))
f
===> 26
;; 将一个过程赋值给相同的变量:
(set! f (lambda (n) (+ n 12)))
(f 6)
===> 18
;; 将一个表达式的结果赋值给相同的变量:
(set! f (f 1))
f
===> 13
;; 函数式编程:
(apply + '(1 2 3 4 5 6))
===> 21
(set! f (lambda (n) (+ n 100)))
(map f '(1 2 3))
===> (101 102 103)
實現標準
本章歸檔多年來做出的給與Scheme特定特徵的設計決定,它們不是最初設計的直接產物。
註釋
直到R5RS標準,在Scheme中的標準註釋是分號,它使得這行餘下部份對Scheme不可見。許多實現支援可替代的約定,允許註釋擴充為多於一行,而R6RS標準允許其中的兩種:一個完整的S-表達式,可以通過前導#;
而變成一個註釋(介入於SRFI 62[78]),和通過用#|
和|#
圍繞文字,產生的「多行註釋」或「塊註釋」。
在布林表達式中非布林值的處理
在多數Lisp方言包括Common Lisp中,布林表達式中的值NIL
按照慣例被求值為值假。在Scheme中,自從1991年的IEEE標準[48],除了#f
的所有的值,包括在Scheme中寫為'()
的NIL
的等價者,在布林表達式中求值為值真(R5RS sec. 6.3.1)[49]。
在多數Lisp中表示布林值真的常數是T
,而在Scheme中它是#t
。
乾淨宏
Scheme的語法可以輕易的通過宏系統來擴充[79]。R5RS標準介入了強力的乾淨宏系統[80],它允許編程者使用一種簡單的模式匹配子語言,向語言增加的新的語法構造(R5RS sec 4.3)[49]。在此前的R4RS標準中,乾淨宏系統已經作為「進階」系統,和「低階」宏系統一起被編排入標準的附錄中[81],二者都被當作對Scheme的擴充而非語言的本質部份。
乾淨宏的實現,也叫做syntax-rules
, 被要求遵守語言的其餘部份的詞法作用域。這是通過針對宏展開的特殊命名和作用域規則來確保的,從而避免在其他程式語言的宏系統中可能出現的常見編程錯誤。R6RS規定了更加複雜的變換系統syntax-case
,它已經作為對R5RS Scheme的一個語言擴充而能夠獲得到有一段時間了。例如:
;; 定义一个宏来实现“if”的具有多个表达式的变体
;; 有真分支而无假分支
(define-syntax when
(syntax-rules ()
((when pred exp exps ...)
(if pred (begin exp exps ...)))))
宏和過程的呼叫看起來非常相似,二者都是S-表達式,但是它們被不同的對待。在編譯器遇到程式中的一個S-表達式的時候,它首先檢視這個符號是否被定義為在當前詞法範圍內的語法關鍵字。如果是這樣,它接着嘗試展開這個宏,將在這個S-表達式尾部的專案當作實際參數,而不用編譯代碼來求值它們,遞歸的重複這個處理過程直到沒有餘留的宏呼叫。如果它不是一個語法關鍵字,編譯器編譯代碼來求值在這個S-表達式尾部的實際參數,並接着求值在這個S-表達式頭部的符號所表示的變數,把它作為過程來呼叫,並把最終的尾部表達式作為實際參數傳遞給它。
多數Scheme實現還提供額外的宏系統。其中最流行是語法閉包、顯式重新命名宏和define-macro
,它是類似於Common Lisp中提供的defmacro
系統的非乾淨宏。
不能指定一個宏是否為乾淨的,是宏系統的一個缺點。可作為替代的展開模型比如作用域集合,提供一種潛在的解決方案[82]。
延遲求值
自從R2RS開始[58],Scheme通過delay
形式和過程force
支援延遲求值。
(define a 10)
(define eval-aplus2 (delay (+ a 2)))
(set! a 20)
(force eval-aplus2)
===> 22
(define eval-aplus50 (delay (+ a 50)))
(let ((a 8))
(force eval-aplus50))
===> 70
(set! a 100)
(force eval-aplus2)
===> 22
delay
原始運算,產生叫做promise的一個對象,它在將來的某個時間點上被詢問從而遞交結果值,promise的原本定義的文字內容會被留存,並且在第一次使用force
之後它的值也會被留存。promise永遠只求值一次。它們可以被用來實現進階的惰性求值構造比如串流[83]。
在R5RS中,給出了delay
和force
的推薦實現,將promise實現為沒有實際參數的一個過程(thunk),並使用記憶化來確保它永遠只求值一次,與呼叫force
的次數無關(R5RS sec. 6.4)[49]。在R6RS標準中,它們不再是原始運算,轉而作為R5RS相容庫的一部份提供(rnrs r5rs (6))。
SRFI 41確使表達有限和無限序列能夠具有非凡的經濟性。例如,這是使用在SRFI 41中的函數定義的一個斐波那契數列[83]:
;; 定义斐波那契序列
(define fibs
(stream-cons 0
(stream-cons 1
(stream-map +
fibs
(stream-cdr fibs)))))
;; 计算这个序列的第一百个数
(stream-ref fibs 99)
===> 218922995834555169026
eval及其執行環境
R5RS標準之前,在其他Lisp中無處不在的eval
過程,在Scheme中沒有等價者。在第一篇λ論文中,曾經將evaluate
描述為「類似於LISP函數EVAL」[84],但在具有詞法作用域的Scheme中,對這個表達式在哪個環境中求值存在困惑。例如,不明確求值下列表達式的結果應當是5
還是6
[85]:
(let ((name '+))
(let ((+ *))
(evaluate (list name 2 3))))
在求值實際參數name
的時候,在外層環境中找到了它的定義;在求值結果表達式(+ 2 3)
的時候,如果在外層環境中求值,結果是兩個運算數的總和;如果在內層環境中求值,這裏符號+
已經被繫結到過程*
的值,結果是兩個運算數的乘積。為此在1978年的最初修訂報告中,將evaluate
替代為enclose
,它接受分別為代碼和執行環境的兩個實際參數[86]。由於各種技術和實際原因,第二次、第三次和第四次修訂報告省略了任何eval
的等價者[85]。
R5RS通過規定三個返迴環境的過程,並提供接受一個S-表達式和一個環境的過程eval
,它在提供的這個環境內求值這個表達式(R5RS sec. 6.5)[49],解決了這個困惑。R6RS通過提供叫做environment
的一個過程進行了擴充,編程者通過它可以精確的指定將哪個對象匯入到求值環境之內。
通過現代Scheme(通常相容於R5RS)來求值表達式expr
,你需要定義如下這樣的一個函數evaluate
:
(define (evaluate expr)
(eval expr (interaction-environment)))
interaction-environment
是直譯器的全域環境。
數值塔
Scheme規定了數值資料類型的相較完全的集合,包括複數和有理數類型,這在Scheme中叫做「數值塔」(R5RS sec. 6.2[49])。標準對它們作抽象處理,而不委以任何特定的內部表示。
數可以有精確性質素。一個精確數只能從涉及其他精確數的精確運算產生,不精確是傳染的。標準規定任何兩個實現對產生精確數的所有運算都必須產生等價的結果。
R5RS標準規定了過程exact->inexact
和inexact->exact
,它們可以用於改變一個數的精確性。inexact->exact
產生「在數值上最接近實際參數的精確數」。exact->inexact
產生「在數值上最接近實際參數的不精確數」。R6RS標準在主報告中省略了這些過程,而是在標準庫中將它們規定為R5RS相容過程(rnrs r5rs (6))。
在R5RS標準中,不要求Scheme實現去實現整個數值塔,而是它們必須實現「符合實現用途和Scheme語言精神二者的連貫子集」(R5RS sec. 6.2.3)[49]。新的R6RS標準要求實現整個數值塔,並且「精確整數對象和精確有理數對象實際上有無限的大小和精度,並且對於實現特定過程...所以它們在得到精確實際參數時總是返回精確結果」(R6RS sec. 3.4, sec. 11.7.1)[50]。
在支援精確有理複數的實現中的精確算術例子:
;; 三个有理实数和两个有理复数的总和
(define x (+ 1/3 1/4 -1/5 -1/3i 405/50+2/3i))
x
===> 509/60+1/3i
;; 检查精确性
(exact? x)
===> #t
在既不支援精確有理數也不支援複數卻接受有理數表示法的實數的實現中相同算術的例子:
;; 四个有理实数的总和
(define xr (+ 1/3 1/4 -1/5 405/50))
;; 两个有理实数的总和
(define xi (+ -1/3 2/3))
xr
===> 8.48333333333333
xi
===> 0.333333333333333
;; 检查精确性
(exact? xr)
===> #f
(exact? xi)
===> #f
二者實現都符合R5RS標準,但是第二個實現不符合R6RS,因為它沒有實現完整的數值塔。
原始資料類型不相交
在Scheme中原始資料類型是不相交的。對於任何Scheme對象,下列謂詞中只有一個可以為真:boolean?
、pair?
、symbol?
、number?
、char?
、>string?
、vector?
、port?
和procedure?
(R5RS sec 3.2)[49]。
作為對比,在數值資料類型之內,對數值值是可以有交疊的。例如,一個整數值可以同時滿足如下謂詞:integer?
、rational?
、real?
、complex?
和number?
(R5RS sec 6.2)[49]。
等價謂詞
Scheme在任意對象之間有三種不同類型的等價,它們通過三種不同的「等價謂詞」來指示,即測試等式的關係算符eq?
、eqv?
和equal?
:
eq?
求值為#f
,除非它的實際參數列示在主記憶體中相同的對象;eqv?
大體上同於eq?
,但是特殊處理原始對象(比如字元和數),使得表示相同值的數eqv?
為真,即使它們不參照相同的對象;equal?
比較數據結構比如列表、向量和字串,來確定它們是否有全等的結構並且其內容eqv?
為真(R5RS sec. 6.1)[49]。
在Scheme中還存在依賴於類型的等價運算:string=?
和string-ci=?
比較兩個字串(後者進行不依賴大小寫比較);char=?
和char-ci=?
比較字元;=
比較數[49]。
輸入/輸出
Scheme的輸入和輸出基於了「埠」資料類型(R5RS sec 6.6)[49]。R5RS定義了兩個預設埠,可通過過程current-input-port
和current-output-port
來訪問,它們對應於Unix概念的標準輸入和標準輸出。多數實現還提供current-error-port
。標準通過標準過程比如with-input-from-file
和with-output-to-file
,支援輸入和標準輸出的重新導向。多數實現提供有重新導向能力的字串埠,使用在SRFI 6中描述的過程[87],確使很多常規的輸入-輸出運算能在字串緩衝區上進行而非在檔案上。R6RS標準規定了更多複雜和有能力的埠過程和很多新的埠類型。
下面的例子是使用嚴格的R5RS Scheme書寫的。
例子1,預設輸出到current-output-port
:
(let ((hello0 (lambda() (display "Hello world") (newline))))
(hello0))
例子2,類似例子1但對輸出過程使用可選的埠參數的例子:
(let ((hello1 (lambda (p) (display "Hello world" p) (newline p))))
(hello1 (current-output-port)))
類似例子1,但是輸出被重新導向到一個新建檔案:
;; NB: with-output-to-file is an optional procedure in R5RS
(let ((hello0 (lambda () (display "Hello world") (newline))))
(with-output-to-file "helloworldoutputfile" hello0))
類似例子2,但是具有顯式的檔案打開和埠關閉來傳送輸出到檔案:
(let ((hello1 (lambda (p) (display "Hello world" p) (newline p)))
(output-port (open-output-file "helloworldoutputfile")))
(hello1 output-port)
(close-output-port output-port))
類似例子2,但是使用call-with-output-file
來傳送輸出到一個檔案:
(let ((hello1 (lambda (p) (display "Hello world" p) (newline p))))
(call-with-output-file "helloworldoutputfile" hello1))
對輸入提供了類似的過程。R5RS Scheme提供了謂詞input-port?
和output-port?
。對於字元輸入和輸出提供了write-char
、read-char
、peek-char
和char-ready?
。為了書寫和閱讀Scheme表達式,Scheme提供了read
和write
。在讀運算時,如果輸入埠到達了檔案的結束處,則返回的結果是end-of-file
對象,並且這可以使用謂詞eof-object?
來測試。
除了標準之外,SRFI 28定義了一個基本的格式化過程,類似Common Lisp的format
並以此命名[88]。
標準過程的重定義
在Scheme中,過程被繫結到變數[65]。在R5RS中語言標準正式的授權編程者可以變更內建過程的變數繫結,在效果上重定義它們(R5RS "Language changes")[49]。例如,通過重定義+
可以將它擴充為同接受數一樣接受字串:
(set! +
(let ((original+ +))
(lambda args
(apply
(if (or (null? args) (string? (car args)))
string-append
original+)
args))))
(+ 1 2 3)
===> 6
(+ "1" "2" "3")
===> "123"
在R6RS中所有繫結,包括標準過程,都屬於某個庫,並且所有匯出的繫結都是不可變的(R6RS sec 7.1)[50]。因此,禁止通過變更進行標準過程的重定義。轉而,可以在標準過程的名字下匯入不同的過程,這在效果上類似於重定義。
標準形式和過程概述
Scheme語言正式定義於1998年的R5RS和2007年R6RS標準之中。它們描述了標準「形式」,即關鍵字和隨同的語法,它們提供語言的控制結構,和執行通常任務的標準過程。
在標準Scheme中,從一個資料類型轉換到另一個資料類型的過程,在它們的名字中包含字串->
,謂詞結束於一個?
,而改變已經分配了數據的值的過程結束於一個!
。Scheme編程者通常跟從這些命名約定。
標準形式
本表格描述Scheme中的標準形式。某些形式出現在不止一行之中,因為它們不能被輕易的歸類入語言中的一個單一功能。
在表格中標記了「L」的形式被歸類為標準中的衍生的「庫」形式,並且在實踐中通常被實現為使用了更基礎形式的宏,這使得實現任務比在其他語言中要容易許多。
用途 | 形式 |
---|---|
定義 | define |
繫結構造 | lambda, do (L), let (L), let* (L), letrec (L) |
條件求值 | if, cond (L), case (L), and (L), or (L) |
順序求值 | begin (*) |
迭代 | lambda, do (L), 命名let (L) |
語法擴充 | define-syntax, let-syntax, letrec-syntax, syntax-rules (R5RS), syntax-case (R6RS) |
引述 | quote('), unquote(,), quasiquote(`), unquote-splicing(,@) |
賦值 | set! |
延遲求值 | delay (L) |
注意begin
在R5RS中被定義為庫語法,但是展開者需要知道它來完成分片功能。在R6RS中它不再是庫語法。
標準過程
下面兩個表格描述在R5RS Scheme中的標準過程。R6RS更加具有可延伸性而如此總結是不實際的。
某些過程出現在不止一行之中,因為它們不能輕易的分類入語言的一個單一功能。
用途 | 過程 |
---|---|
構造 | vector, make-vector, make-string, list |
等價謂詞 | eq?, eqv?, equal?, string=?, string-ci=?, char=?, char-ci=? |
類型轉換 | vector->list, list->vector, number->string, string->number, symbol->string, string->symbol, char->integer, integer->char, string->list, list->string |
數值 | 參見獨立表格 |
字串 | string?, make-string, string, string-length, string-ref, string-set!, string=?, string-ci=?, string<? string-ci<?, string<=? string-ci<=?, string>? string-ci>?, string>=? string-ci>=?, substring, string-append, string->list, list->string, string-copy, string-fill! |
字元 | char?, char=?, char-ci=?, char<? char-ci<?, char<=? char-ci<=?, char>? char-ci>?, char>=? char-ci>=?, char-alphabetic?, char-numeric?, char-whitespace?, char-upper-case?, char-lower-case?, char->integer, integer->char, char-upcase, char-downcase |
向量 | make-vector, vector, vector?, vector-length, vector-ref, vector-set!, vector->list, list->vector, vector-fill! |
符號 | symbol->string, string->symbol, symbol? |
有序對和列表 | pair?, cons, car, cdr, set-car!, set-cdr!, null?, list?, list, length, append, reverse, list-tail, list-ref, memq. memv. member, assq, assv, assoc, list->vector, vector->list, list->string, string->list |
辨識謂詞 | boolean?, pair?, symbol?, number?, char?, string?, vector?, port?, procedure? |
續體 | call-with-current-continuation (call/cc), values, call-with-values, dynamic-wind |
環境 | eval, scheme-report-environment, null-environment, interaction-environment (可選) |
輸入/輸出 | display, newline, read, write, read-char, write-char, peek-char, char-ready?, eof-object? open-input-file, open-output-file, close-input-port, close-output-port, input-port?, output-port?, current-input-port, current-output-port, call-with-input-file, call-with-output-file, with-input-from-file(可選), with-output-to-file(可選) |
系統介面 | load (可選), transcript-on (可選), transcript-off (可選) |
延遲求值 | force |
函數式程式設計 | procedure?, apply, map, for-each |
布林運算 | boolean? not |
在其名字中包含-ci
的字串和字元過程,在它們的實際參數之間進行不依賴大小寫的比較:相同字元的大寫和小寫版本被認為是相等的。
用途 | 過程 |
---|---|
基本算術算符 | +, -, *, /, abs, quotient, remainder, modulo, gcd, lcm, expt, sqrt |
有理數 | numerator, denominator, rational?, rationalize |
近似 | floor, ceiling, truncate, round |
精確性 | inexact->exact, exact->inexact, exact?, inexact? |
不等式 | <, <= , >, >=, = |
雜項謂詞 | zero?, negative?, positive? odd? even? |
極大和極小 | max, min |
三角 | sin, cos, tan, asin, acos, atan |
指數 | exp, log |
複數 | make-rectangular, make-polar, real-part, imag-part, magnitude, angle, complex? |
輸入-輸出 | number->string, string->number |
類型謂詞 | integer?, rational?, real?, complex?, number? |
接受多於二個實際參數的-
和/
在R5RS中被定義了但留作為可選的。
Scheme實現要求
由於Scheme的極簡主義,很多常見過程和語法形式不是由標準定義的。 為了保持核心語言很小並促進擴充的標準化,Scheme社區擁有「Scheme實現要求」(SRFI)流程,通過對擴充提案的仔細研討來定義擴充庫。這提升了代碼可移植性。很多SRFI被幾乎所有Scheme實現支援。
在不同的實現中具有相當廣泛支援的SRFI包括[89]:
- 0: 基於特徵的條件展開構造
- 1: 列表庫
- 4: 同質數值向量資料類型
- 6: 基本字串埠
- 8: 接收、繫結到多個值
- 9: 定義記錄類型
- 13: 字串庫
- 14: 字元集庫
- 16: 可變元數過程的語法
- 17: 廣義
set!
- 18: 多線程支援
- 19: 時間資料類型和過程
- 25: 多維陣列原語
- 26: 不用柯里化的特殊化形式參數的表示法
- 27: 亂數位的來源
- 28: 基本格式化字串
- 29: 本地化
- 30: 巢狀的多行註釋
- 31: 遞歸求值的特殊形式
- 37:
args-fold
:程式實際參數處理器 - 39: 形式參數對象
- 41: 串流
- 42: 及早推導式
- 43: 向量庫
- 45: 表達迭代式惰性演算法的原語
- 60: 作為位元的整數
- 61: 更一般性的
cond
子句 - 66: 八位組向量
- 67: 比較過程
實作
Scheme的精簡設計,使得程式語言設計人士與愛好者,特別鍾愛研究它,故而它有不斷湧現的新實作,而活躍開發的實作也在持續跟進語言標準更新[90]。儘管Scheme有眾多實現是它的一個主要長處[21],但是由於Scheme沒有庫函數標準,造成了很多不可互通的實作互相競爭,試圖使用Scheme編寫既複雜又便於移植的程式,往往比較困難[22]。R6RS和R7RS-Large,在核心語言之外制定了庫函數標準,使得編譯器開發者和貢獻者可以實現Scheme的可移植庫。
幾乎所有Scheme實作都有傳承自Lisp的「讀取﹣求值﹣輸出循環」互動模式,一些Scheme實作亦可作為編譯器。很多用C語言及衍生語言寫成的軟件,都利用Scheme作為手稿語言,很多嵌入式系統語言即是基於Scheme。Chez Scheme和Larceny等實現,可以將Scheme程式編譯為本機二進制碼。Gambit和Chicken等實現,可將Scheme程式編譯為C代碼,Bigloo還能編譯成JVM位元組碼或者.Net位元組碼。
一些實現支援額外的特徵。比如Kawa提供與Java類別的整合,而Scheme到C編譯器通常易於使用C寫成的外部庫,甚至允許將實際C代碼嵌入到Scheme原始碼中。另一個例子是用java書寫的Pvts,它提供了支援Scheme學習的一組可視工具。
教科書
實際用處
很多著名的電腦科學院校都利用Scheme來教授入門級課程。以下為一些最為著名的教授Scheme的學校:
- 麻省理工學院是Scheme與SICP的誕生地。直到2008年為止,麻省理工學院的入門課程6.001即是用Scheme來教授的[92]。儘管現在Scheme已經不再被用於入門課程,麻省理工學院到目前為止還在教授SICP[93]。
- 柏克萊加州大學的入門課程61A到2010年為止利用Scheme與SICP教授入門課程,並利用Scheme來實作Logo,另一個基於Lisp的程式語言[94]。自2011年起,61A改用Python來教授SICP[95]。
- 西北大學的入門課程CS2500利用Scheme來教授另一本著名的教材《程式設計方法》。
- 印第安那大學的入門課程C211利用Scheme來教授。
- 耶魯大學
- 萊斯大學
- 香港科技大學
- 北京大學
- ProgramByDesign項目在美國超過600所高中教授Scheme語言。
- 滑鐵盧大學數學系(包括電腦科學系)的入門課程CS115、CS116、CS135利用Scheme來教授。
- 雲林科技大學
自由軟件影像處理程式GIMP利用Scheme為嵌入式指令碼語言[96]。GNOME中有到核心庫的一個GNU的擴充語言Guile包裝器[97]。在2012年出現的Julia所採用的語法解析器,是用Scheme方言Femtolisp實現的。
參見
註釋
- ^ https://small.r7rs.org/.
- ^ BONES - A simple Scheme compiler for x86_64 systems. [2022-11-14]. (原始內容存檔於2022-12-05).
- ^ 3.0 3.1 Alex Shinn. Chibi-Scheme is a very small library intended for use as an extension and scripting language in C programs. [2022-11-13]. (原始內容存檔於2022-12-23).
- ^ Cyclone Scheme is a brand-new compiler that allows real-world application development using the R7RS Scheme Language standard. [2022-11-13]. (原始內容存檔於2022-09-27).
- ^ Foment is an implementation of R7RS Scheme. [2022-11-13]. (原始內容存檔於2022-11-13).
- ^ LIPS is poweful Scheme based lisp interpreter written in JavaScript. [2021-11-09]. (原始內容存檔於2022-04-23).
- ^ Loko Scheme, an optimizing Scheme compiler. [2022-11-13]. (原始內容存檔於2022-12-06).
- ^ Mosh is a free and fast interpreter for Scheme as specified in the R7RS & R6RS. [2022-11-13]. (原始內容存檔於2022-12-06).
- ^ Picrin is a lightweight R7RS scheme implementation written in pure C89. [2022-11-13]. (原始內容存檔於2022-12-06).
- ^ Rapid Scheme expands and evaluates a Scheme program as described by the R7RS. [2022-11-13]. (原始內容存檔於2022-11-28).
- ^ s7 is a Scheme interpreter intended as an extension language for other applications. [2022-11-14]. (原始內容存檔於2022-12-16).
- ^ S9fES is a mature, portable, and comprehensible interpreter for R4RS Scheme. [2022-11-14]. (原始內容存檔於2022-12-05).
- ^ Sagittarius Scheme - R6RS/R7RS Scheme system. [2022-11-13]. (原始內容存檔於2022-12-24).
- ^ TR7: tiny R7RS-small scheme interpreter. [2023-12-17]. (原始內容存檔於2023-12-17).
- ^ femtolisp - a lightweight, robust, scheme-like lisp implementation. [2022-11-14]. (原始內容存檔於2022-12-22).
- ^ Husk is a dialect of Scheme written in Haskell that implements a superset of the R5RS standard and a large portion of the R7RS-small language. [2022-11-13]. (原始內容存檔於2022-11-23).
- ^ Swift LispKit is a framework for building Lisp-based extension and scripting languages for macOS and iOS applications. [2022-11-13]. (原始內容存檔於2022-12-09).
- ^ R7RS-small archive. [2021-11-10]. (原始內容存檔於2023-01-01).
- ^ The Scheme Programming Language. MIT. [2022-05-04]. (原始內容存檔於2022-04-12).
- ^ Harold Abelson and Gerald Jay Sussman with Julie Sussman. Structure and Interpretation of Computer Programs. Cambridge, MA: MIT Press. 1996 [2011]. ISBN 0-262-01153-0. (原始內容存檔於2018-03-09) (英語).
- ^ 21.0 21.1 scheme-faq-standards - What Scheme implementations are there?. Community Scheme Wiki. [2021-11-09]. (原始內容存檔於2010-02-18).
- ^ 22.0 22.1 Will Clinger, Marc Feeley, Chris Hanson, Jonathan Rees and Olin Shivers. Scheme Steering Committee Position Statement. Scheme Steering Committee. 2009-08-20 [2011-12-19]. (原始內容存檔於2009-08-26).
Scheme has the unhappy distinction of being the world's most unportable programming language. It is almost misleading to call Scheme a "programming language;" it would be more accurate to characterise Scheme as a family of dialects, all loosely related by the common features of lexical scope, dynamic typing, list structure, higher-order functions, proper tail-recursion, garbage collection, macros, and (some form of) s-expression based lexical syntax.
- ^ John McCarthy. History of Lisp (PDF). Artificial Intelligence Laboratory, Stanford University. 12 February 1979 [2022-11-17]. (原始內容存檔 (PDF)於2020-11-07).
To use functions as arguments, one needs a notation for functions, and it seemed natural to use the λ-notation of Church (1941). I didn’t understand the rest of his book, so I wasn’t tempted to try to implement his more general mechanism for defining functions. Church used higher order functionals instead of using conditional expressions. Conditional expressions are much more readily implemented on computers.
David Turner. Some History of Functional Programming Languages (PDF). [2021-11-10]. (原始內容 (PDF)存檔於2020-04-15).LISP was not based on the lambda calculus, despite using the word 「LAMBDA」 to denote functions. At the time he invented LISP, McCarthy was aware of (Church 1941) but had not studied it. The theoretical model behind LISP was Kleene’s theory of first order recursive functions. (McCarthy made these statements, or very similar ones, in a contribution from the floor at the 1982 ACM symposium on LISP and functional programming in Pittsburgh. No written version of this exists, as far as know.)
- ^ A meta-circular interpreter of a subset of Scheme. [2022-11-14]. (原始內容存檔於2022-11-14).
- ^ Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
At about this time, Carl Hewitt was developing his theory of actors as a model of computation. This model was object-oriented and strongly influenced by Smalltalk. Every object was a computationally active entity capable of receiving and reacting to messages. The objects were called actors, and the messages themselves were also actors. Every computational entity was an actor and message-passing was the only means of interaction. An actor could have arbitrarily many acquaintances; that is, it could 「know about」 other actors and send them messages or send acquaintances as (parts of) messages. Hewitt then went on to model many traditional control structures as patterns of message-passing among actors. Functional interactions were modeled with the use of continuations; one might send the actor named 「factorial」 a message containing two other actors: the number 5 and another actor to which to send the eventually computed value (presumably 120). While Conniver made control points into first-class data, the actors model went to the logical extreme by making everything be first-class data.
- ^
Carl Hewitt, Peter Bishop, Irene Greif, Brian Smith, Todd Matson, Richard Steiger. Actor induction and meta-evaluation. 1973 [2022-11-15]. (原始內容存檔於2022-11-15). 「For some years we have been constructing a series of languages to express our evolving understanding of the above semantic issues. The latest of these
is called PLANNER-73. ……
We shall assume that the reader is familiar with advanced pattern matching languages such as SNOBOL4, CONVERT, PLANNER-71, QA4, and POPLER.
We shall use(%A M%)
to indicate sending the message M to the actor A. We shall use[s1 s2 … sn]
to denote the finite squence s1, s2, … sn. ……
Identifiers can be created by the prefix operator=
. ……
An expression(=> pattern body)
is an abbreviation for(receive(message pattern) body)
where receive is a more general actor that is capable of bindinq elements of the action in addition to the message.
Evaluating
(%(=> pattern body) the-message%)
, i.e. sending
(=> pattern body) the-message
,
will attempt to match the-message against pattern, If the-message is not of the form specified by pattern, then the actor isNOT-APPLICABLE
to the-message. If the-message matches pattern, the body is evaluated.
……
Conceptually at least a new actor is created every time a message is sent. Consider sending to a target T a message M and a continuation C.
(send T (message M [#continuation C]))
The transmission
(%T M%)
is an abbreviation for the above where C is defaulted to be the caller. If target T is the following:(receive (message the-body [#continuation =the-continuation]) the-body)
then the the-body is evaluated in an environment where the-message is bound to M and the-continuation is bound to C.
……
Many actors who are executing in parallel can share the same continuation. They can all send a message[“return”]
to the same continuation. 」
Irene Greif, Carl Hewitt. Actor semantics of PLANNER-73. 1975 [2022-11-15]. (原始內容存檔於2022-11-15). - ^
Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
Hewitt and his students developed and implemented in Lisp a new language to make concrete the actor model of computation. This language was first called Planner-73 but the name was later changed to PLASMA (PLAnner-like System Modeled on Actors).
It was at this point that we (Sussman and Steele) put our heads together. (Steele had just become a graduate student at MIT, but he had been following this progression of language designs because he had had a part-time job at MIT Project MAC as one of the maintainers of MacLisp.) We wanted to better understand Hewitt’s actors model but were having trouble relating the actors model and its unusual terminology to familiar programming notions. We decided to construct a toy implementation of an actor language so that we could play with it. Using MacLisp as a working environment, we wrote a tiny Lisp interpreter and then added mechanisms for creating actors and sending messages. We wanted to better understand Hewitt’s actors model but were having trouble relating the actors model and its unusual terminology to familiar programming notions. We decided to construct a toy implementation of an actor language so that we could play with it. Using MacLisp as a working environment, we wrote a tiny Lisp interpreter and then added mechanisms for creating actors and sending messages. - ^ 彼得·蘭丁. The mechanical evaluation of expressions (PDF). 1964 [2022-11-17]. (原始內容存檔 (PDF)於2022-11-16).
Also we represent the value of a λ-expression by a bundle of information called a "closure," comprising the λ-expression and the environment relative to which it was evaluated. We must therefore arrange that such a bundle is correctly interpreted whenever it has to be applied to some argument. More precisely:
a closure has
an environment part which is a list whose two items are:
⑴ an environment
⑵ an identifier or list of identifiers,
and a control part which consists of a list whose sole item is an AE.
The value relative toE
of a λ-expressionX
is represented by the closure denoted by
constructclosure((E, bvX), unitlist(bodyX))
- ^
Joel Moses. The Function of FUNCTION in LISP, or Why the FUNARG Problem Should Be Called the Environment Problem (pdf). June 1970 [2009-10-27]. AI Memo 199. (原始內容存檔於2010-05-23).
The use of free variables is a very powerful computational idea. In many situations it is possible to avoid using free variables by supplying a function with additional arguments. This can be quite cumbersome. ……
When a function uses free variables or when it calls functions which use them, then the value of this function is not completely determined by its arguments, since the value might depend on the values of the free variables. Thus in order to completely specify a computation, one has to specify a function and its arguments in addition to an environment in which the values of the free variables can be determined. ……
An important point which must be realized about functional arguments (abbreviated FUNARGs) is that two different environments are involved in such cases. The first environment is the one which is in effect when then FUNARG is bound as an argument. We shall call this the binding environment. The second environment is the one that is in effect when the FUNARG is activated as a function. We shall call this the activation environment. ……
Consider now what it would require for the system to restore the binding environment for functional arguments. It would require knowing where in the stack or "alist" the binding environment exists through some pointer to it. Supplying such pointer is a functionFUNCTION
in LISP. That is when one transmits a functional argumentf
which is to be evaluated in its binding environment, then one usesFUNCTION(f)
.FUNCTION
will prevent its argumentf
from being evaluated, just asQUOTE
would. The result ofFUNCTION
will be a structure which not only contains a reference to the functionf
, but also contains a pointer to the binding environment. Thus at the FUNARG's activation time we will be able to use the current place in the stack up to the point where the binding environment exists, thus skipping over any values in the activation environment which differ from those in the binding environment. ……
The points we have made so far are:
1) Free variables in function definitions require that one must have an environment in order to be able to evaluate a function.
2) When one uses functional arguments, the the question arises as to which environment one chooses in order to evaluate the function, the binding environment or the activation environment.
3) When one returns functions (which require the values of free variables for their evaluation) then one must, in general, save the binding environment and thus create a stack which is, in fact, a tree. ……
LISP allows the user to choose between the binding environment and the activation environment by allowing a function to be bound withFUNCTION
or withQUOTE
.QUOTE
will force the activation environment to be used in evaluating the function. A useful metaphor for the difference betweenFUNCTION
andQUOTE
in LISP is to think ofQUOTE
as a porous or an open covering of the function since free variables escape to the current environment.FUNCTION
acts as a closed or nonporous covering (hence the term "closure" used by Landin). Thus we talk of "open" Lambda expressions (functions in LISP are usually Lambda expressions) and "closed" Lambda expressions. ……
My interest in the environment problem began while Landin, who had a deep understanding of the problem, visited MIT during 1966-67. I then realized the correspondence between the FUNARG lists which are the results of the evaluation of "closed" Lambda expressions in LISP and ISWIM's Lambda Closures. - ^
Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
Sussman had just been studying Algol. He suggested starting with a lexically scoped dialect of Lisp, because that seemed necessary to model the way names could refer to acquaintances in PLASMA. Lexical scoping would allow actors and functions to be created by almost identical mechanisms. Evaluating a form beginning with the word
lambda
would capture the current variable-lookup environment and create a closure; evaluating a form beginning with the wordalpha
would also capture the current environment but create an actor. Message passing could be expressed syntactically in the same way as function invocation. The difference between an actor and a function would be detected in the part of the interpreter traditionally known asapply
. A function would return a value, but an actor would never return; instead, it would typically invoke a continuation, another actor that it knew about. Our interpreter also provided the necessary primitives for implementing the internal behavior of primitive actors, such as an addition operator that could accept two numbers and a continuation actor. - ^ 31.0 31.1 Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
This led us to three important ideas:
• First, we realized that all the patterns of control structure that Hewitt had described in terms of actors could equally well be described by the λ-calculus. ……
• Second, we realized that the λ-calculus — a small, simple formalism — could serve as the core of a powerful and expressive programming language. (Lisp had adopted the λ-notation for functions but had failed to support the appropriate behavior for free variables. The original theoretical core of Lisp was recursion equations, not the λ-calculus.) ……
• Third, we realized that in our quest for the 「ultimate AI language」 we had come full circle. As the MIT school had struggled to find more and more powerful ways to express and manipulate control structure to support heuristic search, we had progressed from Lisp to CONVERT to Planner to Conniver to PLASMA to a simple variation of Lisp! - ^ Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
Inspired by ACTORS [Greif and Hewitt][Smith and Hewitt], we have implemented an interpreter for a LISP-like language, SCHEME, based on the lambda calculus [Church], but extended for side effects, multiprocessing, and process synchronization. The purpose of this implementation is tutorial. We wish to:
⑴ alleviate the confusion caused by Micro-PLANNER, CONNIVER, etc. by clarifying the embedding of non-recursive control structures in a recursive host language like LISP.
⑵ explain how to use these control structures, independent of such issues as pattern matching and data base manipulation.
⑶ have a simple concrete experimental domain for certain issues of programming semantics and style.
LISP code for the SCHEME interpreter by Gerald Jay Sussman and Guy Lewis Steele, Jr. [2022-11-14]. (原始內容存檔於2022-11-14). - ^ Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
We were very pleased with this toy actor implementation and named it 「Schemer」 because we thought it might become another AI language in the tradition of Planner and Conniver. However, the ITS operating system had a 6-character limitation on file names and so the name was truncated to simply SCHEME and that name stuck.
- ^ Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
We concluded that actors and closures were effectively the same concept. (Hewitt later agreed with this, but noted that two types of primitive actors in his theory, namely cells (which have modifiable state) and synchronizers (which enforce exclusive access), cannot be expressed as closures in a lexically scoped pure Lisp without adding equivalent primitive extensions.)
Carl Hewitt. ActorScript: Industrial strength integration of local and nonlocal concurrency for Client-cloud Computing. 2009 [2022-12-23]. (原始內容存檔於2022-12-23).In summary, Sussman and Steele [1975] mistakenly concluded 「we discovered that the 'Actors' and the lambda expressions were identical in implementation.」 The actual situation is that the λ-calculus is capable of expressing some kinds of sequential and parallel control structures but, in general, not the concurrency expressed in the Actor model. On the other hand, the Actor model is capable of expressing everything in the λ-calculus and more [Hewitt 2008f].
- ^ Guy L. Steele Jr., Gerald J. Sussman. The Revised Report on SCHEME: A Dialect of LISP (PDF). 1978 [2022-11-18]. (原始內容存檔 (PDF)於2022-12-12).
SCHEME is a dialect of LISP. It is an expression-oriented, applicative order, interpreter-based language which allows one to manipulate programs as data. It differs from most current dialects of LISP in that it closes all lambda-expressions in the environment of their definition or declaration, rather than in the execution environment. This has the consequence that variables are normally lexically scoped, as in ALGOL. However, in contrast with ALGOL, SCHEME treats procedures as a first-class data type. They can be the values of variables, the returned values of procedures, and components of data structures. Another difference from LISP is that SCHEME is implemented in such a way that tail-recursions execute without net growth of the interpreter stack. The effect of this is that a procedure call behaves like a
GOTO
and thus procedure calls can be used to implement iterations, as in PLASMA. - ^
Gerald J. Sussman, Guy L. Steele Jr. The First Report on Scheme Revisited. 1998 [2021-11-07]. (原始內容存檔於2022-04-06).
We were actually trying to build something complicated and discovered, serendipitously, that we had accidentally designed something that met all our goals but was much simpler than we had intended.
We thought that Scheme would turn out to be the next in a long series of programming languages that had been designed at MIT over the course of 17 years. ……
In this process we learned some great lessons. The λ-calculus can be seen as a universal glue by which compound constructs can be built inductively from simpler ones and a set of primitives. …… The essence of the matter is that the λ-calculus provides a way of abstracting entities of any particular type and then combining such abstractions with other entities to make new entities of that same type, which are instances of the abstractions. …… Given the right primitives, the λ-calculus can serve as a foundation for the abstraction and instantiation of virtually any kind of complex conceptual structure.
We also were struck by the great importance of names as a means of reference. …… Naming seems to correspond to some important cognitive mechanism that is, if not innate, then at least extremely well-developed in our culture. The λ-calculus embodies a provably coherent theory of naming. - ^ The Original 'Lambda Papers' by Guy Steele and Gerald Sussman. [2021-11-07]. (原始內容存檔於2022-01-12).
- ^ Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
- ^ Gerald J. Sussman, Guy L. Steele Jr. Lambda: The Ultimate Imperative. 1976 [2021-11-11]. (原始內容存檔於2022-04-10).
- ^ Guy L. Steele Jr. LAMBDA: The Ultimate Declarative. 1976 [2021-11-10]. (原始內容存檔於2022-04-09).
- ^ Guy L. Steele Jr. Debunking the 'Expensive Procedure Call' Myth, or, Procedure Call Implementations Considered Harmful, or, Lambda: The Ultimate GOTO. 1977 [2021-11-07]. (原始內容存檔於2022-05-09).
- ^ Gerald J. Sussman, Guy L. Steele Jr. The Art of the Interpreter of the Modularity Complex (Parts Zero, One, and Two). 1978 [2021-11-07]. (原始內容存檔於2021-11-07).
- ^ Guy L. Steele Jr. RABBIT: A Compiler for SCHEME. 1978 [2021-11-07]. (原始內容存檔於2021-11-08).
Rabbit Scheme: Old Scheme implementation in InterLisp. [2022-11-15]. (原始內容存檔於2021-12-03). - ^ Gerald J. Sussman, Guy L. Steele Jr. Design of LISP-based Processors, or SCHEME: A Dielectric LISP, or Finite Memories Considered Harmful, or LAMBDA: The Ultimate Opcode. 1979 [2021-11-07]. (原始內容存檔於2021-11-07).
- ^ Guy L. Steele Jr. Compiler Optimization Based on Viewing LAMBDA as RENAME + GOTO. Patrick Henry Winston, Richard H. Brown (編). Artificial Intelligence: An MIT Perspective, Volume 2: Understanding Vision/Manipulation/Computer Design/Symbol Manipulation. 1979: 399–432 [2022-12-07]. (原始內容存檔於2022-12-07).
- ^ Gerald J. Sussman, Guy L. Steele Jr. Design of a LISP-based microprocessor. 1980 [2021-11-07]. (原始內容存檔於2021-11-08).
- ^ J.W. Backus; F.L. Bauer; J.Green; C. Katz; J. McCarthy P. Naur; et al. Revised Report on the Algorithmic Language Algol 60. Numerische Mathematik, Communications of the ACM, and Journal of the British Computer Society. January–April 1960 [2012-08-09]. (原始內容存檔於2007-06-25).
- ^ 48.0 48.1 IEEE 1178-1990 - IEEE Standard for the Scheme Programming Language. [2022-01-14]. (原始內容存檔於2021-03-04).
- ^ 49.00 49.01 49.02 49.03 49.04 49.05 49.06 49.07 49.08 49.09 49.10 49.11 49.12 49.13 49.14 49.15 49.16 Richard Kelsey, William Clinger, Jonathan Rees; et al. Revised5 Report on the Algorithmic Language Scheme. Higher-Order and Symbolic Computation. August 1998, 11 (1): 7–105 [2007-01-04]. doi:10.1023/A:1010051815785. (原始內容存檔於2007-01-05) (英語).
Scheme is a statically scoped and properly tail-recursive dialect of the Lisp programming language invented by Guy Lewis Steele Jr. and Gerald Jay Sussman. It was designed to have an exceptionally clear and simple semantics and few different ways to form expressions. A wide variety of programming paradigms, including imperative, functional, and message passing styles, find convenient expression in Scheme.
- ^ 50.0 50.1 50.2 Michael Sperber, R. Kent Dybvig, Matthew Flatt, Anton Van Straaten; et al. Revised6 Report on the Algorithmic Language Scheme. Scheme Steering Committee. August 2007 [2009-10-20]. (原始內容存檔於2013-06-25).
This report is accompanied by a report describing standard libraries; references to this document are identified by designations such as 「library section」 or 「library chapter」. It is also accompanied by a report containing non-normative appendices. A fourth report gives some historical background and rationales for many aspects of the language and its libraries.
- ^ Revised6 Report on the Algorithmic Language Scheme, Appendix E: language changes. Scheme Steering Committee. 2007-09-26 [2011-12-19]. (原始內容存檔於2010-04-09).
- ^ R6RS Electorate. Scheme Steering Committee. 2007 [2009-10-20]. (原始內容存檔於2008-12-04).
- ^ Marc Feeley (compilation). Implementors' intentions concerning R6RS. Scheme Steering Committee, r6rs-discuss mailing list. 2007-10-26 [2009-10-20]. (原始內容存檔於2008-08-20).
- ^ R7RS Home Page. [2022-11-13]. (原始內容存檔於2022-12-17).
- ^ Alex Shinn, John Cowan, Arthur A. Gleckler; et al. The Revised7 Report on the Algorithmic Language Scheme (PDF). 2022-09-06 [2022-11-13]. (原始內容存檔 (PDF)於2022-11-13).
R7RS-small archive. [2021-11-10]. (原始內容存檔於2023-01-01). - ^ Implementations support all or part of R7RS-small. [2022-11-13]. (原始內容存檔於2022-11-13).
- ^ R7RS-Large Interim Red Edition. 2020-03-17 [2022-11-13]. (原始內容存檔於2022-11-13).
The Revised7 Report on the Algorithmic Language Scheme (「R7RS」) intentionally describes a small language, suitable for implementing on reduced hardware, or for experiments in programming language design and implementation.
However, Scheme is used for practical programming, and there a minimal language is not a help. Additional data structures and programming idioms make the programmer’s job easier (if they are well-designed!). Therefore, as part of the R7RS design process, it was agreed that a subsequent report would appear on 「R7RS-Large」, a collection of Scheme libraries that are useful across a wide variety of problems.
The decision of what was to appear in R7RS-Large was left to the community, and a strategy was adopted: proposals were published as SRFIs (Scheme Requests For Implementation, http://srfi.schemers.org), and the community would then vote on which would be adopted.
As a result, R7RS-Large will be developed in several stages, depending upon the support from the community for each proposal. At each stage, several SRFIs are frozen, and implementers and users may then rely upon those libraries being in the final product. (In a few cases, a library might be revisited, with a subsequent stage adopting a completely backward-compatible new version of that library; this has already happened with generators.)
When this process has completed, the resulting interim reports will be collated and reorganized into a final document. - ^ 58.0 58.1 58.2 58.3 William Clinger. The Revised Revised Report on Scheme or An Uncommon Lisp (PDF). 1985 [2022-11-17]. (原始內容存檔 (PDF)於2022-10-17).
Data and procedures and the values they amass, Higher-order functions to combine and mix and match, Objects with their local state, the message they pass, A property, a package, the control of point for a catch - In the Lambda Order they are all first-class. One thing to name them all, one things to define them, one thing to place them in environments and bind them, in the Lambda Order they are all first-class. ……
Scheme shares with Common Lisp the goal of a core language common to several implementations. Scheme differs from Common Lisp in its emphasis upon simplicity and function over compatibility with older dialects of Lisp. - ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978.
System-Provided Extensions - Some standard syntactic extension are provided by convenience in ordinary programming. They are distinguished from other magic words in that they are semantically defined in terms of others rather then being primitive {Note FEXPRs Are Okay by Us}. For expository purposes they are described here pattern-matching/production-rules kind of language. takes advantage of the definition of list notation:
(A B C) = (A . (B . (C . NIL)))
. Thus the pattern(x . r)
matches(A B C)
, withx = A
andr = (B C)
. the ordering of the "productions" is significant; the first one which matches is to be used. - ^ Kent M. Pitman. The Revised Maclisp Manual. 1983, 2007 [2022-12-16]. (原始內容存檔於2022-12-16).
In the interpreter 「variables」 are implemented as atomic symbols which possess 「shallow-bound」 value cells. The continual manipulation of value cells would decrease the efficiency of compiled code, so the compiler defines two types of variables: 「special variables」 and 「local variables.」 Special variables are identical to variables in the interpreter.
Local variables are more like the variables in commonly-used algebraic programming languages such as Algol or PL/I. A local variable no longer has an associated atomic symbol; thus it can only be referred to from the text of the same function that bound it. The compiler creates local variables forPROG
variables,DO
variables, andLAMBDA
variables, unless directed otherwise. The compiled code stores local variables in machine registers or in locations within a stack.
The principal difference between local variables and special variables is in the way a binding of a variable is compiled. (A binding has to be compiled when aPROG
,DO
, orLAMBDA
expression is compiled, and for the entry to a function which hasLAMBDA
variables to be bound to its arguments.) If the variable to be bound has been declared to be special, the binding is compiled as code to imitate the way the interpreter binds variables: the value of the atomic symbol is saved and a new value is stored into its value cell. If the variable to be bound has not been declared special, the binding is compiled as the declaration of a new local variable. Code is generated to store the value to which the variable is to be bound into the register or stack-location assigned to the new local variable. This runs considerably faster than a special binding.
Although a special variable is associated with an atomic symbol which has the name of the variable, the name of a local variable appears only in the input file - in compiled code there is no connection between local variables and atomic symbols. Because this is so, a local variable in one function may not be used as a 「free variable」 in another function since there is no way for the location of the variable to be communicated between the two functions.
When the usage of a variable in a program to be compiled does not conform to these rules, i.e. it is somewhere used as a 「free variable,」 the variable must be declared special. There are two common cases in which this occurs. One is where a 「global」 variable is being used, i.e. a variable which isSETQ
'ed by many functions but is never bound. The other is where two functions cooperate, one binding a variable and then calling the other one which uses that variable as a free variable. - ^ 61.0 61.1
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
There are several important consequences of closing every lambda expression in the environment from which it is passed (i.e., in its "lexical" or "static" environment).
First, the axioms of lambda calculus are automatically preserved. Thus, referential transparency is enforced. This in turn implies that there are no "fluid" variable bindings (as there are in standard stack implementations of LISP such as MacLISP).
Second, the upward funarg problem [Moses] requires that the environment structure be potentially tree-like.
Finally, the environment at any point in a computation can never be deeper than the lexical depth of the expression being evaluated at that time; i.e., the environment contains bindings only for variables bound in lambdas lexically surrounding the expression being evaluated. This is true even if recursive functions are involved. ……Furthermore, it is not even necessary to scan the environment for the variable, since its value must be in a known position relative to the top of the environment structure; this position can be computed by a compiler at compile time on the basis of lexical scope. - ^
John McCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, Michael I. Levin. LISP 1.5 Programmer's Manual (PDF) 2nd. MIT Press. 1985 [1962] [2021-10-25]. ISBN 0-262-13011-4. (原始內容 (PDF)存檔於2021-03-02).
The variables in a lambda expression are dummy or bound variables because systematically changing them does not alter the meaning of the expression. Thus
λ[[u;v];v2+u]
means the same thing asλ[[x;y];y2+x]
.
We shall sometimes use expressions in which a variable is not bound by a lambda. For example, in the function of two variablesλ[[x;y];xn+yn]
the variablen
is not bound. This is called a free variable. It may be regarded as a parameter. Unlessn
has been given a value before trying to compute with this function, the value of the function must be undefined. ……
When the compiler is called upon to compile a function, it looks for anEXPR
orFEXPR
on the property list of the function name. The compiler then translates this S-expression into an S-expression that represents a subroutine in the LISP Assembly Language (LAP). LAP then proceeds to assemble this program into binary program space. Thus anEXPR
, or anFEXPR
, has been changed to aSUBR
or anFSUBR
, respectively. ……
Free variables in compiled functions must be declared before the function is compiled. ……
A variable is bound in a particular function when it occurs in a list of bound variables following the wordLAMBDA
orPROG
. Any variable that is not bound is free. ……
When a variable is used free, it must have been bound by a higher level function. If a program is being run interpretively, and a free variable is used without having bee bound on a higher level, error diagnostic *A 8* will occur.
If the program is being run complied, the diagnostic may not occur, and the variable may have value NIL.
There are three types of variables in compiled functions: ordinary variables,SPECIAL
variables, andCOMMON
variables.SPECIAL
andCOMMON
variables must be declared before compiling. Any variable that is not declared will be considered an ordinary variable.
When functions are translated into subroutines, the concept of a variable is translated into a location where an argument is stored. If the variable is an ordinary one, then a storage location for it is set up on the push-down list. Other functions cannot find this private cell, making it impossible to use it as a free variable.SPECIAL
variables have the indicatorSPECIAL
on their property lists Following the indicator there is a pointer to a fixed cell. When this variable is bound, the old value is saved on the push-down list, and the current value is stored in theSPECLAL
cell. When it is no longer bound, the old value must be restored. When a function uses this variable free, then the quantity in theSPECIAL
cell is picked up.SPECIAL
variables are declared by using the pseudo-functionspecial[a]
, wherea
is a list of variable names. ……SPECIAL
variables are inexpensive and will allow free communication among compiled functions. They do not increase run time significantly.SPECIAL
variables cannot be communicated between the interpreter and compiled functions.COMMON
variables have the flagCOMMON
on their property lists; however, this is only used to inform the compiler that they areCOMMON
, and is not needed at run time.COMMON
variables are bound on an a-list by the compiled functions. When they are to be evaluated,eval
is given this a-list. This happens at run time.
The use ofCOMMON
variables will slow down any compiled function using them. However, they do provide complete communication between interpreted and compiled functions.COMMON
variables are declared bycommon[a]
, wherea
is a list of variable names. ……
LISP is designed in such a way that all functions for which the possibility of recursion can exist are in fact recursive. This requires that all temporary stored results related to the computation that is in progress be set aside when a piece of coding is to be used recursively, and that they be later restored. This is done automatically and need not be programmed explicitly.
All saving of temporary results in LISP is performed on a linear block of storage called the push-down list. Each set of stored data that is moved onto the push-down list is in a block labeled with its size and the name of the subroutine from which it came. Since it is in the nature of recursion that the first block to be saved is always the last block to be restored, it is possible to keep the push-down list compact.
Joel Moses. The Function of FUNCTION in LISP, or Why the FUNARG Problem Should Be Called the Environment Problem (pdf). June 1970 [2009-10-27]. AI Memo 199. (原始內容存檔於2010-05-23).There are several ways in which to maintain the values of free variables (and, thus the computational environment) in order to handle cases like the one above. All of the techniques used involve some increase in time. One approach makes it easy to access the value of a free variable is local. This approach is favored in recent implementations of LISP. We shall call it the "shallow access" approach.
Another approach is to make it relatively easy to enter and leave a block in which a free variable is bound, but relatively expensive to access a free variable. This approach is used in the classical "alist" implementation of LISP. Several Algol systems have opted for a similar approach. We shall call this the "deep access" approach. Both of these approaches allow one to modify the value of the variable as well as access it. The access time is approximately the same as modification time in both approaches.
Let us consider the "shallow access" approach first. By "shallow access" we mean that the value of a free variable can be obtained in a single fetch from memory. Since the current value may be stored in a difficult-to-determine location up in the stack, a special cell for its current value is used. In many recent LISP implementations this special values cell is stored as a property of the atom structure, and is unique to the free variable. In order to maintain the proper value in the special cell, extra work must be done every time a function or a block is entered in which the variable is an argument or is local. On entering such a function or block one usually saves the old value which is in the special cell on the stack alone with sufficient information to allow one to know from where the stack value came. On leaving such a function or block one must remember to store the value saved in the stack in the special cell.
The "deep access" approach forces one to search upward in the stack for the most recent value of the free variable. Such a search may be quite deep and slow if the free variable was bond vary far up in the stack. However, this approach has the advantage of avoiding the need for an extra special value cell. In addition, we shall see later that this approach sometimes allows one to use free variables with greater ease and flexibility than the "shallow access" approach. ……
Certain LISP systems use "deep access" in interpreted functions and "shallow access" in compiled functions. if free variables in compiled functions are declared to beCOMMON
, their values will be stored on the "alist". In this manner one can solve the environment problem. The cost is a very slow interpreter and "deep access" to free variables. - ^ van Tonder, André. A Lambda Calculus for Quantum Computation. SIAM Journal on Computing. 1 January 2004, 33 (5): 1109–1135. S2CID 613571. arXiv:quant-ph/0307150 . doi:10.1137/S0097539703432165.
- ^ Niehren, J.; Schwinghammer, J.; Smolka, G. A concurrent lambda calculus with futures (PDF). Theoretical Computer Science. November 2006, 364 (3): 338–356 [2021-11-07]. doi:10.1016/j.tcs.2006.08.016. (原始內容 (PDF)存檔於2022-04-08).
- ^ 65.0 65.1
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
DEFINE
- This is analogous to the MacLISPDEFUN
primitive (but note that theLAMBDA
must appear explicitly!). It is used for defining a function in the "global environment" permanently, as opposed toLABELS
(see below), which is used for temporary definitions in a local environment.DEFINE
takes a name and a lambda expression; it closes the lambda expression in the global environment and stores the closure in the LISP value cell of the name (which is a LISP atom). ……LABELS
- We have decided not to use the traditionalLABEL
primitive in this interpreter because it is difficult to define several mutually recursive functions using onlyLABEL
. The solution, which Hewitt [Smith and Hewitt] also uses, is to adopt an ALGOL-esque block syntax:
(LABELS <function definition list> <expression>)
This has the effect of evaluating the expression in an environment where all the functions are defined as specified by the definitions list. Furthermore, the functions are themselves closed in that environment, and not in the outer environment; this allows the functions to call themselves and each other recursively.
Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978 [2021-11-07]. (原始內容存檔於2022-04-06).Atoms which are not atomic symbols (identifiers) evaluate to themselves. Typical examples of such atoms are numbers, arrays, and strings (character arrays). Symbols are treated as identifiers or variables. They may be lexically bound by lambda-expressions. There is a global environment containing values initially have as their values primitive operations such as, for example,
CAR
,CONS
, andPLUS
. SCHEME differs from most LISP systems in that the atomCAR
is not itself an operation (in the sense of being an invocable object, e.g. a valid first argument toAPPLY
), but only has one as a value when considered as an identifier. - ^ Structure and Interpretation of Computer Programs - 2.1.3 What Is Meant by Data?. [2024-01-13]. (原始內容存檔於2024-02-26).
- ^ y-combinator. [2024-01-06]. (原始內容存檔於2024-01-06).
- ^
Gerald J. Sussman, Guy L. Steele Jr.. Lambda: The Ultimate Imperative. 維基文庫. 1976 (英文).
Lambda calculus (and related languages, such as "pure LISP") is often used for modelling the applicative constructs of programming languages. However, they are generally thought of as inappropriate for modelling imperative constructs. …… we show how imperative constructs may be modelled by applicative SCHEME constructs. ……
Up to now we have thought of SCHEME’sLAMBDA
expressions as functions, and of a combination such as(G (F X Y))
as meaning 「apply the functionF
to the values ofX
andY
, and return a value so that the functionG
can be applied and return a value ...」 But notice that we have seldom usedLAMBDA
expressions as functions. Rather, we have used them as control structures and environment modifiers. For example, consider the expression:
(BLOCK (PRINT 3) (PRINT 4))
This is defined to be an abbreviation for:
((LAMBDA (DUMMY) (PRINT 4)) (PRINT 3))
We do not care about the value of thisBLOCK
expression; it follows that we do not care about the value of the(LAMBDA (DUMMY) ...)
. We are not usingLAMBDA
as a function at all.
It is possible to write useful programs in terms ofLAMBDA
expressions in which we never care about the value of any lambda expression. We have already demonstrated how one could represent any 「FORTRAN」 program in these terms: all one needs isPROG
(withGO
andSETQ
), andPRINT
to get the answers out. The ultimate generalization of this imperative programming style is continuation-passing. ……
…… we will consider the problem of dynamically scoped, or "fluid", variables. These do not exist in ALGOL, but are typical of many LISP implementations, ECL, and APL. We will see that fluid variables may be modelled in more than one way, and that one of these is closely related to continuation-passing. - ^
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
It is always possible, ……to perform any calculation in this way: rather than reducing to its value, it reduces to an application of a continuation to its value (cf. [Fischer]). That is, in this continuation-passing programming style, a function always "returns" its result by "sending" it to another function.
- ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978.
Non-atomic forms are divided by the evaluator into two classes: combinations and "magic (special) forms". …… Magic forms are recognized by then presence of a "magic (reserved) word" in the
car
position of the form. All non-atomic forms which are not magic forms are considered to be combinations. The system has a small initial set of magic words; there is also a mechanism for creating new ones {Note FUNCALL is a Pain}.
A combination is considered to be a list of subforms. These subforms are all evaluated. The first value mub be a procedure; it is applied to the other values to get the value of the combination. There are four important points here:
(1) the procedure Position is always evaluated just like any other position. (This is why the primitive operators are the values of global identifiers.)
(2) the procedure is never "re-evaluated"; if the first subform fails to evaluate to a applicable procedure, it is an error. Thus, unlike most LISP systems, SCHEME always evaluates the first subform of a combination exactly once.
(3) the arguments are all completely evaluated before the procedure is applied; that is, SCHEME, like most LISP systems, is an applicative-order language. Many SCHEME programs exploit this fact.
(4) the argument forms (and procedure form) may in principle be evaluated in any order. This is unlike the usual LISP left-to-right order. (All SCHEME interpreters implemented so far have in fact performed left-to-right evaluation, but we do not wish programs to depend on this fact. Indeed, there are some reasons why a clever interpreter might want to evaluate them right-to-left, e.g. to get things on a stack in the correct order.) - ^
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
BLOCK
- This is like the MacLISPPROGN
, but arranges to evaluate its last argument without an extra net control frame (explained later), so that the last argument may involved in an iteration. Note that in SCHEME, unlike MacLISP, the body of aLAMBDA
expression is not an implicitPROGN
.
Gerald J. Sussman, Guy L. Steele Jr.. Lambda: The Ultimate Imperative. 維基文庫. 1976 (英文).At this point we are almost in a position to model the most general form of compound statement. In LISP, this is called the "
PROG
feature". In addition to statement sequencing andGO TO
statements, one can return a value from aPROG
by using theRETURN
statement.
Let us first consider the simplest compound statement, which in SCHEME we callBLOCK
. Recall that(BLOCK S1 S2)
is defined to be((LAMBDA (DUMMY) S2) S1)
Notice that this not only performsS1
beforeS2
, but also returns the value ofS2
. Furthermore, we defined(BLOCK S1 S2 ... Sn)
so that it returns the value ofSn
. ThusBLOCK
may be used as a compound expression, and models a LISPPROGN
, which is aPROG
with noGO TO
statements and an implicitRETURN
of the last "statement" (really an expression).
Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978.(BLOCK x1 x2 ... xn)
(BLOCK x) → x
(BLOCK x . r) → ((LAMBDA (A B) (B)) x (LAMBDA () (BLOCK . r)))BLOCK
sequentially evaluates the subformsxi
from left to right. For example:(BLOCK (ASET' X 43) (PRINT X) (+ X 1))
returns44
after settingx
to43
and then printing it {NoteBLOCK
Exploits Applicative Order}.(LET ((v1 x1) (v2 x2) ... (vn xn)) . body)
→ ((LAMBDA (v1 v2 ... vn) (BLOCK . body)) x1 x2 ... xn)LET
provides a convenient syntax for binding several variables to corresponding quantities. It allows the forms for the quantities to appear textually adjacent to their corresponding variables. Notice that the variables are all bound simultaneously, not sequentially, and that the initialization formxi
may be evaluated in any order. For convenience,LET
also supplies aBLOCK
around the forms constituting its body. - ^ Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
DO
- This is like the MacLISP "new-style"DO
; old-styleDO
is not supported. - ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978 [2021-11-07]. (原始內容存檔於2022-04-06).
Another difference from LISP is that SCHEME is implemented in such a way that tail-recursions execute without net growth of the interpreter stack. The effect of this is that a procedure call behaves like a
GOTO
and thus procedure calls can be used to implement iterations, as in PLASMA. - ^
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
CATCH
- This is the "escape operator" which gives the user a handle on the control structure of the interpreter. The expression:
(CATCH <identifier> <expression>)
evaluates<expression>
in an environment where<identifier>
is bound to a continuation which is "just about to return from theCATCH
"; that is, if the continuation is called as a function of one argument, then control proceeds as if theCATCH
expression had returned with the supplied (evaluated) argument as its value. ……
As another example, we can define aTHROW
function, which may then be used withCATCH
much as they are in LISP:
(DEFINE THROW (LAMBDA (TAG RESULT) (TAG RESULT)))
- ^ David Madore, "call/cc mind-boggler" Portuguese Web Archive的存檔,存檔日期2011-01-22
- ^ Yin Wang, "Understanding the Yin-Yang Puzzle"
- ^ Richard P. Gabriel; Kent M. Pitman. Technical Issues of Separation in Function Cells and Value Cells. Lisp and Symbolic Computation. June 1988, 1 (1): 81–101 [2021-11-08]. S2CID 26716515. doi:10.1007/bf01806178. (原始內容存檔於2006-11-13).
- ^ Taylor Campbell. SRFI 62: S-expression comments. The SRFI Editors, schemers.org. 2005-07-21 [2012-08-09]. (原始內容存檔於2022-04-11).
- ^ Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978 [2021-11-07]. (原始內容存檔於2022-04-06).
Syntactic Extensions - SCHEME has a syntactic extension mechanism which provides a way to define an identifier to be a magic word, and to associate a function with that word. The function accepts the magic form as an argument, and produces a new form; this new form is then evaluated in place of the original (magic) form. This is precisely the same as the MacLISP macro facility.
- ^ Kohlbecker, E.; Friedman, D. P.; Felleisen, M.; Duba, B. Hygienic Macro Expansion (PDF). ACM conference on LISP and functional programming. 1986 [2021-11-10]. (原始內容 (PDF)存檔於2017-08-29).
In most current Lisp systems the expander's task is confined to the process of finding syntactic extensions and replacing them by their expansions. This implies, in particular, the each macro function is responsible for the integrity of the program. For Lisp systems (and other languages with similar macro facilities) this means specifically that variable binding must not be corrupted. This, however, is not as simple a task as it sounds. ……
The real danger of these erroneous macros is that they are treacherous. They work in all cases but one: when the user - or some other macro writer - inadvertently picks the wrong identifier name.
Various techniques have been proposed to circumvent this capturing problem, but they rely on the individual macro writer. if even one of many macro writers is negligent, the macro system becomes unsafe. We claim that the task of safely renaming macro-generated identifier is mechanical. It is essentially an α-conversion, which is knowledgeable about the origin of the identifiers. For these reasons we propose a change to the navïe macro expansion algorithm which automatically maintains hygienic conditions during expansion time.
Kohlbecker, E; Wand, M. Macro-by-example: Deriving syntactic transformations from their specifications (PDF). Symposium on Principles of Programming Languages. 1987 [2021-11-10]. (原始內容 (PDF)存檔於2022-04-12). - ^ William Clinger and Jonathan Rees, editors. Revised4 Report on the Algorithmic Language Scheme. ACM Lisp Pointers. 1991, 4 (3): 1–55 [2012-08-09]. (原始內容存檔於2022-01-22).
- ^ Flatt, Matthew. Binding as sets of scopes. Proceedings of the 43rd Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages. 2016: 705–717. ISBN 978-1-4503-3549-2. S2CID 15401805. doi:10.1145/2837614.2837620.
- ^ 83.0 83.1 Philip L. Bewig. SRFI 41: Streams. The SRFI Editors, schemers.org. 2008-01-24 [2012-08-09]. (原始內容存檔於2021-03-07).
- ^ Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文).
EVALUATE
- This is similar to the LISP functionEVAL
. It evaluates its argument, and then evaluates the resulting s-expression as SCHEME code. - ^ 85.0 85.1 The Scheme of Things: The June 1992 Meeting - Evaluating computed expressions. ACM SIGPLAN, Lisp Pointers, Volume V, Issue 4. 1992 [2021-11-12]. (原始內容存檔於2022-04-04).
- ^
Gerald J. Sussman, Guy L. Steele Jr. The Revised Report on SCHEME: A Dialect of LISP. 1978 [2021-11-07]. (原始內容存檔於2022-04-06).
(ENCLOSE fnrep envrep)
ENCLOSE
takes two s-expressions, one representing the code for a procedure, and the other representing the (lexical) environment in which is to run.ENCLOSE
returns a (closed) procedure which can be invoked.
The representation of the code is the standard s-expression description (a lambda-expression). the representation of the environment is an association list (a-list) of the traditional kind:
((var1 . value1) (var2 . value2) ...)
NIL
represents the global lexical environment. ……
TheEVALUATE
primitive described in [SCHEME] has been removed from the language. We discovered (the hard way) that the straightforward implementation ofEVALUATE
(evaluate the given expression in the current environment) destroys referential transparency. We then altered it to evaluate the expression in the top-level environment, but were still disturbed by the extent to which one is tied to a particular representation of a procedure to be executed. - ^ William D Clinger. SRFI 6: Basic String Ports. The SRFI Editors, schemers.org. 1999-07-01 [2012-08-09]. (原始內容存檔於2021-10-21).
- ^ Scott G. Miller. SRFI 28: Basic Format Strings. The SRFI Editors, schemers.org. 2002-06-25 [2012-08-09]. (原始內容存檔於2022-05-08).
- ^ Scheme Systems Supporting SRFIs. The SRFI Editors, schemers.org. 2009-08-30 [2012-08-09]. (原始內容存檔於2021-06-20).
- ^ Scheme Benchmarks. [2022-11-14]. (原始內容存檔於2022-11-04).
- ^ Brian Harvey, Matthew Wright. Simply Scheme: Introducing Computer Science. 1999 [2021-11-07]. (原始內容存檔於2022-05-04).
- ^ Eric Grimson. 6.001 Structure and Interpretation of Computer Programs. MIT Open Courseware. Spring 2005 [2009-10-20]. (原始內容存檔於2009-10-01).
- ^ Alex Vandiver, Nelson Elhage; et al. 6.184 - Zombies drink caffeinated 6.001. MIT CSAIL. January 2009 [2009-10-20]. (原始內容存檔於2009-08-28).
- ^ Brian Harvey. CS 61A Structure and Interpretation of Computer Programs. UC Berkeley Webcasts. Spring 2011 [2011-12-18]. (原始內容存檔於2016-03-16).
- ^ John DeNero. CS 61A Structure and Interpretation of Computer Programs. UC Berkeley. Fall 2011 [2011-12-18]. (原始內容存檔於2011-12-29).
- ^ Dov Grobgeld. The GIMP Basic Scheme Tutorial. The GIMP Team. 2002 [2009-10-20]. (原始內容存檔於2010-01-24).
- ^ guile-gnome. Free Software Foundation. [2009-10-20]. (原始內容存檔於2016-03-04).
延伸閱讀
外部連結
- 維基教科書中有關Scheme Programming的文字
- 維基教科書中有關Write Yourself a Scheme in 48 Hours的文字
- 維基共享資源上的相關多媒體資源:Scheme