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中的函数计算距离。