Takafumi Shido 著html
出處,http://deathking.github.io/yast-cn/linux
------------------git
簡介程序員
這是一本面向初學者的溫和且按部就班的Scheme教程。目標讀者是僅有些許編程經驗的PC用戶。github
若是你不滿意於其它的教程,那麼請嘗試本書。咱們有不少方法去解釋像Scheme程序設計語言這樣的抽象主題,這之中最好的方法取決於讀者的能力以及素養。(沒有對任何人來講都絕對完美的方法。)這也正是儘管已經有不少Scheme語言的教程,我還另寫一本的緣由所在。編程
本教程的目的在於給讀者在Scheme程序設計上提供足夠的知識和能力以便可以閱讀最好的計算機科學教科書之一的——《計算機程序的構造和解釋》(Structure and Interpreter of Computer Program,SICP)。SICP使用Scheme做爲授課語言。數組
中文版序數據結構
Scheme恰似中國傳統棋盤遊戲——圍棋。app
這是由於它們均可以根據至關簡單的規則產生美妙的代碼或者棋局,這些規則在它們的領域中都是最簡單的。簡單的規則,無限的美妙變幻,這些都無比吸引那些聰明的傢伙。但同時,大天然卻讓它們難以掌握。
我編寫這份教程以打開掌握Scheme之門。我相信中文譯版會幫助更多的程序員掌握Scheme程序設計語言。
紫藤貴文
---------------------
解釋器返回3做爲答案。請注意如下三點:
對參數的求值順序是沒有被規範的,也就是說,參數並非老是會從左到右求值。
函數exact->inexact
用於把分數轉換爲浮點數。
quotient
用於求商數(quotient)。remainder
和modulo
用於求餘數(remainder)。sqrt
用於求參數的平方根(square root)。數學上的三角函數,諸如sin
,cos
,tan
,asin
,acos
和atan
均可以在Scheme中使用。atan
接受1個或2個參數。若是atan
的參數爲1/2 π
,那麼就要使用兩個參數來計算。
指數經過exp
函數運算,對數經過log
函數運算。a
的b
次冪能夠經過(expt a b)
來計算。
形如這些由括號、標記(token)以及分隔符組成的式子,被稱爲S-表達式。
---
練習 1
使用Scheme解釋器計算下列式子:
練習2
使用Scheme解釋器求解下列式子:
做者給出的答案:
(* 4 (atan 1.0)) ;⇒ 3.141592653589793
-----
運行環境是,Deepin 終端,MIT - Scheme
MIT/GNU Scheme running under GNU/Linux
This is GNU Emacs 24.5.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
of 2017-09-12 on hullmann, modified by Debian
-----
做爲Lisp語言你們族的一員,Scheme一樣擅長於處理表。你應該理解表以及有關表的操做以掌握Scheme。
表在在後面章節中的遞歸函數和高階函數中扮演重要角色。
首先,讓我解釋一下表的元素:Cons單元(Cons cells)。
是一個存放了兩個地址的內存空間。Cons單元可用函數cons
生成。函數cons
給兩個地址分配了內存空間,並把存放指向1
的地址放在一個空間,把存放指向2
的地址放在另外一個空間。
存放指向1
的地址的內存空間被稱做car
部分,對應的,存放指向2
的地址的內存空間被稱做cdr
部分。car
和cdr
分別是寄存器地址部分(Contents of the Address part of the Register)和寄存器減量部分(Contents of the Decrement part of the Register)的簡稱。cons
這個名字是術語構造(construction)的簡稱。
是Cons單元經過用cdr
部分鏈接到下一個Cons
單元的開頭實現的。表中包含的’()
被稱做空表。就算數據僅由一個Cons單元組成,只要它的cdr
單元是’()
,那它就是一個表。
事實上,表能夠像下面這樣遞歸地定義:
‘()
是一個表ls
是一個表且obj
是某種類型的數據,那麼(cons obj ls)
也是一個表 正由於表是一種被遞歸定義的數據結構,將它用在遞歸的函數中顯然是合理的。不使用Cons單元的數據結構稱爲原子(atom)。數字,字符,字符串,向量和空表’()
都是原子。’()
既是原子,又是表。
練習1
使用
cons
來構建在前端表現爲以下形式的數據結構。
("hi" . "everybody") ; (cons "hi" "everybody")
(0) ; `(0)
(1 10 . 100) ; (cons 1 (cons 10 100))
(1 10 100) ; (cons 1 (cons 10 (cons 100 `())))
(#\I "saw" 3 "girls") ; (cons #\I (cons "saw" (cons 3 (cons "girls" `()))))
("Sum of" (1 2 3 4) "is" 10) ; (cons "Sum of" (cons `(1 2 3 4) (cons "is" (cons 10 `()))))
全部的記號都會依據Scheme的求值規則求值:
全部記號都會從最內層的括號依次向外層括號求值,
且最外層括號返回的值將做爲S-表達式的值。
一個被稱爲引用(quote)的形式能夠用來阻止記號被求值。它是用來將符號或者表原封不動地傳遞給程序,而不是求值後變成其它的東西。例如,(+ 2 3)
會被求值爲5
,然而(quote (+ 2 3))
則向程序返回(+ 2 3)
自己。由於quote
的使用頻率很高,他被簡寫爲’
。實際上,’()
是對空表的引用,也就是說,儘管解釋器返回()
表明空表,你也應該用’()
來表示空表。
Scheme有兩種不一樣類型的操做符:
其一是函數。函數會對全部的參數求值並返回值。
另外一種操做符則是特殊形式。特殊形式不會對全部的參數求值。
除了quote
,lambda
,define
,if
,set!
,等都是特殊形式。
返回一個Cons單元的car
部分和cdr
部分的函數分別是car
和cdr
函數。
若是cdr
部分串連着Cons單元,解釋器會打印出整個cdr
部分。
若是Cons單元的cdr
部分不是’()
,那麼其值稍後亦會被展現。
練習2
求值下列S-表達式。
(car '(0)) ; 0
(cdr '(0)) ; ()
(car '((1 2 3) (4 5 6))) ; (1 2 3)
- (cdr `((1 2 3) (4 5 6))) ; ( (4 5 6) ) // 本身添加,做爲對比!!兩個括號
(cdr '(1 2 3 . 4)) ; (2 3 . 4)
(cdr (cons 3 (cons 2 (cons 1 '())))) ; (2 1)
注意:. 的做用 !!
(cdr (cons 1 (cons 2 (cons 3 4)))) ;Value 29: (2 3 . 4) (cons 1 (cons 2 (cons 3 4))) ;Value 30: (1 2 3 . 4)
/* 示例對比 */
(cons 1 (cons 2 3)) ;Value 27: (1 2 . 3) (cdr (cons 1 (cons 2 3))) ;Value 23: (2 . 3) ********** (cons 1 (cons 2 (cons 3 `()))) ;Value 28: (1 2 3) (cdr (cons 1 (cons 2 (cons 3 `())))) ;Value 24: (2 3)
list
函數使得咱們能夠構建包含數個元素的表。函數list
有任意個數的參數,且返回由這些參數構成的表。
----------難點-----------------
因爲Sheme是函數式編程語言,你須要經過編寫小型函數來構造程序。
所以,明白如何構造並組合這些函數對掌握Scheme尤其關鍵。
在前端定義函數很是不便,所以咱們一般須要在文本編輯器中編輯好代碼,並在解釋器中加載它們。
你可使用define
來將一個符號與一個值綁定。
你能夠經過這個操做符定義例如數、字符、表、函數等任何類型的全局參數。
讓咱們使用任意一款編輯器(記事本亦可)來編輯代碼片斷1中展現的代碼,並將它們存儲爲hello.scm
,放置在相似於C:\doc\scheme\
的文件夾下。若是能夠的話,把這些文件放在你在第一章定義的MIT-Scheme默認文件夾下。
; Hello world as a variable (define vhello "Hello world") ;1 ; Hello world as a function (define fhello (lambda () ;2 "Hello Scheme"))
操做符define
用於聲明變量,它接受兩個參數。
define
運算符會使用第一個參數做爲全局參數,並將其與第二個參數綁定起來。
所以,代碼片斷1的第1行中,咱們聲明瞭一個全局參數vhello
,並將其與"Hello,World"
綁定起來。
緊接着,在第2行聲明瞭一個返回「Hello Scheme」
的過程。
1 ]=> (load "hello") ;Loading "hello.scm"... done ;Value: fhello 1 ]=> (cd "./") ;Value 13: #[pathname 13 "/home/yws/Documents/Demo/scm/./"] 1 ]=> vhello ;Value 14: "hello world" 1 ]=> fhello ;Value 15: #[compound-procedure 15 fhello] 1 ]=> (fhello) ;Value 16: "hello scheme"
特殊形式lambda
用於定義過程。
lambda
須要至少一個的參數,第一個參數是由定義的過程所需的參數組成的表。由於本例fhello
沒有參數,因此參數表是空表。
在解釋器中輸入vhello
,解釋器返回「Hello,World」。
若是你在解釋器中輸入fhello
,它也會返回像下面這樣的值:#[compound-procedure 16 fhello]
,
這說明了Scheme解釋器把過程和常規數據類型用一樣的方式對待。
正如咱們在前面章節中講解的那樣,Scheme解釋器經過內存空間中的數據地址操做全部的數據,所以,全部存在於內存空間中的對象都以一樣的方式處理。
若是把fhello
當過程對待,你應該用括號括住這些符號,好比(fhello)
。而後解釋器會按照第二章講述的規則那樣對它求值,並返回「Hello Scheme」。
1 ]=> (define vhello "hello world") ;Value: vhello 1 ]=> vhello ;Value 38: "hello world"
------------------
1 ]=> (define fhello (lambda () "hello Scheme")) ;Value: fhello fhello ;Value 39: #[compound-procedure 39 fhello]
1 ]=> + ;Value 40: #[arity-dispatched-procedure 40]
注意,Value 後面的數值。。竟然,保存着原來的位置,在內存的地址沒變!!
能夠經過在lambda
後放一個參數表來定義有參數的函數。
; hello with name (define hello (lambda (name) (string-append "Hello " name "!"))) ; sum of three numbers (define sum3 (lambda (a b c) (+ a b c)))
保存文件,並在解釋器中載入此文件,而後調用咱們定義的函數。
(load "farg.scm") ;Loading "farg.scm" -- done ;Value: sum3 (hello "Lucy") ;Value 20: "Hello Lucy!" (sum3 10 20 30) ; 重點!!不是lambda (...) ;Value: 60 Hello
函數hello
有一個參數(name)
,並會把「Hello」
、name的值
、和"!"
連結在一塊兒並返回。
預約義函數string-append,
能夠接受任意多個數的參數,並返回將這些參數連結在一塊兒後的字符串。
sum3
:此函數有三個參數並返回這三個參數的和。
用lambda
定義函數是一種規範的方法,但你也可使用相似於代碼片斷3中展現的短形式。
; hello with name (define (hello name) (string-append "Hello " name "!")) ; sum of three numbers (define (sum3 a b c) (+ a b c))
在這種形式中,函數按照它們被調用的形式被定義。代碼片斷2和代碼片斷3都是相同的。有些人不喜歡這種短形式的函數定義,可是我在教程中使用這種形式,由於它可使代碼更短小。
我只能說。。蒙圈了,練習題,看答案都搞不懂。。本身寫的又報錯。再看一遍
----------------------
練習1
按照下面的要求編寫函數。這些都很是簡單但實用。
練習2
讓咱們按照下面的步驟編寫一個用於計算飛行距離的函數。
(define pi (* 4 (atan 1.0)))
。g
爲9.8m/s^2
。提示:設落地時瞬時豎直分速度爲-Vy
,有以下關係。2 * Vy = g * t
此處t
爲落地時的時間。v
和角度theta
擲出的小球的飛行距離。提示:首先,將角度的單位轉換爲弧度(假定轉換後的角度爲theta1
)。初始水平、豎直分速度分別表示爲:v*cos(theta1)
和v*sin(theta1)
。落地時間能夠經過問題3中定義的函數計算。因爲水平分速度不會改變, 所以能夠利用問題2中的函數計算距離。