Teach Yourself Scheme in Fixnum Days 笔记

发布于 2022-09-07 16:36:25 字数 248 浏览 20 评论 9

本帖最后由 Lispor 于 2010-11-29 17:29 编辑

正在学习 scheme, 用的是 guile.

准备每学完一章, 贴一篇笔记, 欢迎大家批评

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

忆离笙 2022-09-12 04:14:12

不错不错,希望能继续更新,呵呵

治碍 2022-09-12 04:11:05

第五贴:词法变量

请见我的博客:http://lispor.is-programmer.com/

太多了,以后就不再贴到这了

や莫失莫忘 2022-09-12 04:03:49

本帖最后由 Lispor 于 2010-12-09 00:00 编辑

第四贴:条件语句

像其他语言一样, scheme 也提供了条件语句. 其中, 最基本的语句为 if:

  1. (if test-expression
  2.     then-branch
  3.     else-branch)

复制代码如果 test-expression 计算结果为真<除 #f 值外>, 执行 then-branch, 否则执行 else-branch .

  1. guile> (define p 80)
  2. guile> (if (> p 70)
  3. ...        'safe
  4. ...        'unsafe)
  5. safe
  6. guile> (if (< p 90)
  7. ...        'low-pressure)  ; no else-branch
  8. low-pressure

复制代码scheme 提供的其他条件语句都可以用 if 语句写出的宏来定义出来.

1. cond
cond 语句为嵌套的 if 语句提供了一种更方便的形式:

  1. (if (char<? c #c)
  2.     1
  3.     (if (char=? c #c)
  4.         0
  5.         1))

复制代码可用 cond 语句更简单表示为:

  1. (cond ((char<? c #c) -1)
  2.       ((char=? c #c) 0)
  3.       (else 1))

复制代码
2. case

case 又是 cond 的一种特殊情况的简写语句:

  1. guile> (case c
  2. ...       ((#a) 1)
  3. ...       ((#b) 2)
  4. ...       ((#c) 3)
  5. ...       (else 4))
  6. 3

复制代码3. and 和 or
scheme 也提供了 and 和 or 语句, 同大多数语言一样, 它们都具有短路特性:

  1. guile> (and 1 2)
  2. 2
  3. guile> (and #f 1)
  4. #f
  5. guile> (or 1 2)
  6. 1
  7. guile> (or #f 1)
  8. 1

复制代码

非要怀念 2022-09-12 03:55:23

本帖最后由 Lispor 于 2010-12-05 18:50 编辑

第三贴: Forms(语句块)

读者也许已经注意到: 前面的 scheme 例子中列举了许多符号表达式, 其实在 scheme 中, 程序就是数据. 因此, #c 就是一个程序,或者语句块.

scheme 对语句块 #c 求值为 #c, 因为 #c 是自求值的. 并不是所有符号表达式都是自求值的. 比如, 表达式 xyz 求值结果为 xyz 变量所绑定的值. 而列表符号表达式 (string->number "16") 求值结果是数字 16.

并不是所有的符号表达是都是正确程序. 如果你写如下的代码 (1 . 2), scheme 就会报错.

scheme 对一个列表符号表达式求值, 首先求值语句块的第一个元素, 如果其为一个过程, 那么语句块剩下的元素会被求值以作为这个过程的参数; 如果第一个元素是一种特殊语句, 那么就会以一种特殊的方式来对该语句进行求值, 我们前面碰到的这种特殊语句有 begin, define 和 set!.  begin 语句对其后的语句进行顺序求值, 并返回最后一个语句的求值结果. define 语句定义变量并将一个值绑定到该变量, set! 改变变量绑定的值.

一、Procedures(过程)

我们已经看过许多 scheme 内置过程, 例如: cons, string->list 等等.
我们也可以用 lambda 语句来自定义过程. 比如, 下面定义了一个程序, 对其参数加 2:

  1. guile> (lambda (x) (+ x 2))

复制代码其中, 第一个子语句 (x) 是参数类表, 剩下的子语句是过程体. 这个函数可以向内置过程那样对一个参数进行调用:

  1. guile> ((lambda (x) (+ x 2)) 5)
  2. 7

复制代码如果以后会调用这个过程很多次, 我们可以把这个过程绑定到一个变量, 以后直接引用变量就可以了:

  1. guile> (define add2
  2. ...        (lambda (x) (+ x 2)))
  3. guile> (add2 5)
  4. 7

复制代码1. 参数列表
一个 lambda-过程的参数列表就是紧跟着 lambda 符号的第一个子语句. add2 是一元参数过程, 它的参数列表为 (x), 在过程体内, 符号 x 就是被调用过程的参数所绑定到的变量, 变量 x 是过程体中的局部变量.

我们可以定义二元或多元参数过程.
下面定义了一个二元参数过程来计算矩形的面积, 此函数的两个参数分别是矩形的长和宽:

  1. guile> (define area
  2. ...        (lambda (length breadth)
  3. ...            (* length breadth)))
  4. guile> (area 3 2)
  5. 6

复制代码注意: area 过程对其两参数进行相乘, 同内置函数 * 一样. 因此, 我们可以更简单定义 area 过程:

  1. guile> (define area *)
  2. guile> (area 4 5)
  3. 20

复制代码2. 变参
某些过程支持可变数量的参数, 这个只需把 lambda 参数列表换作一个符号<symbol>, 这个符号作为该过程的一个变量被绑定到所调用过程的参数组成的列表.

通常, lambda 参数列表可以为一个形如 (x ...) 的列表, 或者是一个形如 (x ... . z) 的点对. 当是点对的时候, 在点(.)之前的所有变量被绑定到被调过程相应的参数, 点(.)之后的那个变量被绑定到被调过程剩下的所有参数所组成的列表.

二、Apply(应用)

scheme 中的 apply 过程可以把一个过程作用到一个参数列表:

  1. guile> (define x '(1 2 3))
  2. guile> (apply + x)
  3. 6

复制代码通常, apply 过程第一个参数为一个过程, 其后参数的数量是可变的, 但是, 最后一个参数必须为一个列表:

  1. guile> (apply + 1 2 3 x)
  2. 12

复制代码三、Sequencing(序列)

我们可以用 begin 特殊语句把那些需要顺序执行的子语句组合起来. 许多 scheme 语句含有隐式的 begin 语句. 比如, 我们可以定义一个过程用来显示它的三个参数, 其间用空格分离, 下面就是一种定义方法:

  1. (define display3
  2.    (lambda (arg1 arg2 arg3)
  3.       (begin
  4.          (display arg1)
  5.          (display " ")
  6.          (display arg2)
  7.          (display " ")
  8.          (display arg3)
  9.          (newline))))

复制代码在 scheme 中, lambda 语句体就是一个隐式的 begin 语句, 因此, 在 display3 的定义中, begin 是可以省略的, 更简单的一种方法是下面的定义:

  1. (define display3
  2.    (lambda (arg1 arg2 arg3)
  3.        (display arg1)
  4.        (display " ")
  5.        (display arg2)
  6.        (display " ")
  7.        (display arg3)
  8.        (newline)))

复制代码

流云如水 2022-09-12 03:52:28

三、其他数据类型

scheme 还包含了一些其它数据类型. 过程(procedure)就是其中的一个. 我们已经见过了许多过程了, 例如, display, +, cons 等等. 实际上,它们只是一些变量, 而这些变量被绑定到相应的过程, 这些过程并不像数值和字符具有那样可显性:

  1. guile> cons
  2. #<primitive-procedure cons>

复制代码迄今为止, 我们所见到的过程都是原始过程(系统过程), 由一些全局变量来引用他们. 用户还可以自定义过程.

另外一种数据类型是端口(port). 端口为输入输出提供执行通道. 端口通常会和文件和控制台相关联.

在我们的 "hello world!" 程序中,我们使用 display 函数向控制台输出了一个字符串.
display 可有两个参数, 第一个参数为要输出的值, 另一个参数就是第一个参数所要输出的端口.
在我们的程序中, display 未提供第二个参数, 此时, display 会采用默认的标准输出端口作为输出端口.
我们可以通过 current-output-port 函数来获取当前标准输出端口. 我们可以显式的使用 display 函数:
(display "hello, world!" (current-output-port))

四、S-expressions(符号表达式)

s-expressions 是 symbol-expressions 的简写, 我们前面学习过的数据类型可以被通称为 s-expressions, 因此, 42, #c, (1 . 2), #(a b c), "hello", (quote xyz), (string->number "16") 和 (begin (display "hello, world!") (newline)) 都是 s-expressions.

〆凄凉。 2022-09-12 03:00:56

本帖最后由 Lispor 于 2010-11-29 12:53 编辑

二、复合类型

复合数据类型由其他数据相结合构造而成.

1. strings(字符串)
字符串是字符的序列, 可以用双引号括着字符序列构造字符串, 字符串是自求值的:

  1. guile> "Hello, World!"
  2. "Hello, World!"

复制代码string 函数返回其参数(字符)构造的字符串:

  1. guile> (string #h #e #l #l #o)
  2. "hello"

复制代码现在让我们定义一个全局变量 greeting:

  1. guile> (define greeting "Hello; Hello!")

复制代码在此注意:引号中的分号(;)并不是注释.

在一给定字符串中, 可以访问和修改任何一个字符.
string-ref 和 string-set! 函数:

  1. guile> (string-ref greeting 0)
  2. #H
  3. guile> (string-set! greeting 5 #,)
  4. guile> greeting
  5. "Hello, Hello!"

复制代码string-append 函数可以把数个字符串组合成一个新的字符串:

  1. guile> (string-append "E"
  2. ...                   "Pluribus"
  3. ...                   "Unum")
  4. "EPluribusUnum"

复制代码你也可以生成一个指定长度的字符串.

  1. guile> (define a-3-char-long-string (make-string 3))

复制代码string? 函数判断一个值是否为字符串.

  1. guile> (string? "Hello, World")
  2. #t
  3. guile> (string? 123)
  4. #f

复制代码有 string, make-string, string-append 生成的字符串是可以用 string-set! 函数改变的.

2. vectors(向量)
向量与字符串相似, 不过其元素可以为任意类型, 不仅仅是字符. 不仅如此, 其元素也可以为向量自己, 这样就可以构造出多维向量.

下面是一种构造 5 个整数元素的向量的方法:

  1. guile> (vector 0 1 2 3 4)
  2. #(0 1 2 3 4)

复制代码注意:在 scheme 中, 向量的表达形式为 #(v1 v2 v3 ...)

与 make-string 相似, make-vector 函数生成一个指定长度的向量:

  1. guile> (define v (make-vector 5))

复制代码vector-ref 和 vector-set! 函数可以访问和改变一个向量中的元素.
vector? 判断一个值是否为向量.

3. dotted pairs and lists(点对和列表)

一个点对由两个任意类型值顺序组合而成, 其中第一个元素被称为 car, 第二个元素被称作 cdr, 点对的构造函数为 cons.

  1. guile> (cons 1 #t)
  2. (1 . #t)

复制代码点对不是自求值的, 因此直接构造该数据(不通过 cons 函数产生)需要引用它们:

  1. guile> (quote (1 . #t))
  2. (1 . #t)
  3. guile> '(1 . #t)
  4. (1 . #t)
  5. guile> (1 . #t)
  6. Backtrace:
  7. In current input:
  8.    7: 0* [1 ...
  9. <unnamed port>:7:1: In expression (1 . #t):
  10. <unnamed port>:7:1: Wrong number of arguments to 1
  11. ABORT: (wrong-number-of-args)

复制代码car 和 cdr 函数分别获取点对的第一个和第二个元素:

  1. guile> (define x (cons 1 #t))
  2. guile> (car x)
  3. 1
  4. guile> (cdr x)
  5. #t

复制代码set-car! 和 set-cdr! 函数分别改变点对的第一个和第二个元素:

  1. guile> (set-car! x 2)
  2. guile> (set-cdr! x #f)
  3. guile> x
  4. (2 . #f)

复制代码点对可以包含其他点对:

  1. guile> (define y (cons (cons 1 2)
  2. ...                    3))
  3. guile> y
  4. ((1 . 2) . 3)

复制代码为了分别获取值 1 和 2, 我们应该先获取该点对的第一个元素 (1 . 2), 然后在获取该元素的第一个和第二个元素:

  1. guile>(car (car y))
  2. 1
  3. guile> (cdr (car y))
  4. 2

复制代码为此, scheme 提供了一个简写形式, caar 和 cdar:

  1. guile> (caar y)
  2. 1
  3. guile> (cdar y)
  4. 2

复制代码当点对多层嵌套时, scheme 有一种简写形式:

  1. guile> (cons 1 (cons 2 (cons 3 (cons 4 5))))
  2. (1 2 3 4 . 5)

复制代码(1 2 3 4 . 5) 是 (1 . (2 . (3 . (4 . 5)))) 的简写形式, 此式最后一个 cdr 为 5.

当嵌套点对最后一个 cdr 为空表<被简写为 () >时, scheme 提供了一个更简写的形式:

  1. guile> '(1 . (2 . (3 . (4 . ()))))
  2. (1 2 3 4)

复制代码这种特殊类型的点对被称作为表.
scheme 中可以用 list 函数来产生一个表:

  1. guile> (list 1 2 3 4)
  2. (1 2 3 4)

复制代码list 中元素可以用 list-ref 函数来索引:

  1. guile> (define y (list 1 2 3 4))
  2. guile> (list-ref y 0)
  3. 1

复制代码list-tail 返回 list 中索引及以后的元素:

  1. guile> (list-tail y 1)
  2. (2 3 4)

复制代码函数 pair?, list? 和 null? 用来判断一个值是否为点对, 列表或者是空表:

  1. guile> (pair? '(1 . 2))
  2. #t
  3. guile> (pair? '(1 2))
  4. #t
  5. guile> (pair? '())
  6. #f
  7. guile> (list? '())
  8. #t
  9. guile> (null? '())
  10. #t
  11. guile> (list? '(1 2))
  12. #t
  13. guile> (list? '(1 . 2))
  14. #f
  15. guile> (null? '(1 2))
  16. #f
  17. guile> (null? '(1 . 2))
  18. #f

复制代码4. conversions between data types(各数据类型间的变换)
scheme 提供了许多可以进行数据类型转换的函数. 前面我们学习了通过 char-upcase 和 char-downcase 函数来进行字符的大小写转换. 我们可以用 char->integer 函数来把字符类型变换为整数类型, 同样也可以用 integer->char 函数把整数类型变换为字符类型:

  1. guile> (char->integer #d)
  2. 100
  3. guile> (integer->char 100)
  4. #d

复制代码字符串可以通过 string->list 转换为由各个字符组成的列表:

  1. guile> (string->list "hello")
  2. (#h #e #l #l #o)

复制代码还有其他的一些类似的类型转换函数: list->string, vector->list 和 list->vector.

数值可被转换为字符串, 同样, 字符串也可被转换为数值:

  1. guile> (number->string 16)
  2. "16"
  3. guile> (string->number "16" 8)
  4. 14

复制代码其中, string->number 函数第二个参数是可选的, 是用来指定被转换的基数.

符号与字符串间也可以进行类型转换

  1. guile> (symbol->string 'symbol)
  2. "symbol"
  3. guile> (string->symbol "string")
  4. string

复制代码

老街孤人 2022-09-11 11:26:59

本帖最后由 Lispor 于 2010-11-29 12:52 编辑

第二贴:数据类型

scheme 有丰富的数据类型:一些属于简单类型, 一些属于复合类型

一、简单类型

1.Booleans(布尔型/是非型)
scheme 中的布尔型有 #t(真/是) 和 #f(假/非)
scheme 有一个判断一个值是否为布尔类型的函数 boolean?

  1. guile> (boolean? #t)
  2. #t
  3. guile> (boolean? "Hello, World!")
  4. #f

复制代码not 函数对其参数(被对待为布尔类型)取否:

  1. guile> (not #f)
  2. #t
  3. guile> (not #t)
  4. #f
  5. guile> (not "Hello, World!")
  6. #f

复制代码scheme 规定任何非 #f 值为真.

2.numbers(数值)
scheme 数值类型有整数, 有理数, 实数和复数. 一个整数为有理数, 一个有理数为实数, 一个实数为复数, 总之, 这几个类型全部归为数值类型.

  1. guile> (number? 42)
  2. #t
  3. guile> (number? #t)
  4. #f
  5. guile> (complex? 2+3i)
  6. #t
  7. guile> (real? 2+3i)
  8. #f
  9. guile> (real? 3.1416)
  10. #t
  11. guile> (real? 22/7)
  12. #t
  13. guile> (rational? 2+3i)
  14. #f
  15. guile> (rational? 22/7)
  16. #t
  17. guile> (rational? 3.1416)
  18. #t
  19. guile> (integer? 22/7)
  20. #f
  21. guile> (integer? 42)
  22. #t

复制代码scheme 数值默认书写形式为 10 进制形式(#d可不写), 但也可写为二进制(开头为#b)、八进制(开头为#o)和十六进制(开头为#x)形式.

eqv? 可以用来判断两个数值是否相等:

  1. guile> (eqv? 42 42)
  2. #t
  3. guile> (eqv? 42 #f)
  4. #f
  5. guile> (eqv? 42 42.0)
  6. #f

复制代码但是我们通常用 = 函数来判断两个已知类型为数值的值是否相等:

  1. guile> (= 42 42)
  2. #t
  3. guile> (= 42 #f)
  4. Backtrace:
  5. In current input:
  6.   22: 0* [= 42 {#f}]
  7. <unnamed port>:22:1: In procedure = in expression (= 42 #f):
  8. <unnamed port>:22:1: Wrong type: #f
  9. ABORT: (wrong-type-arg)
  10. guile> (= 42 42.0)
  11. #t

复制代码其他的比较函数有 <, <=, >, >=:

  1. guile> (< 3 2)
  2. #f
  3. guile> (>= 4.5 3)
  4. #t

复制代码也有一些数值计算函数:+, -, *, /, expt:

  1. guile> (+ 1 2 3)
  2. 6
  3. guile> (- 5.3 2)
  4. 3.3
  5. guile> (- 5 2 1)
  6. 2
  7. guile> (* 1 2 3)
  8. 6
  9. guile> (/ 6 3)
  10. 2
  11. guile> (/ 22 7)
  12. 22/7
  13. guile> (expt 2 3)
  14. 8
  15. guile> (expt 4 1/2)
  16. 2.0

复制代码其中, 对于只有一个参数的情况, - 函数返回参数的相反数, / 函数返回参数的倒数:

  1. guile> (- 4)
  2. -4
  3. guile> (/ 4)
  4. 1/4

复制代码函数 max 和 min 分别返回其参数中的最大值和最小值:

  1. guile> (max 1 3 4 2 3)
  2. 4
  3. guile> (min 1 3 4 2 3)
  4. 1

复制代码abs 函数返回其参数的绝对值:

  1. guile> (abs 3)
  2. 3
  3. guile> (abs -4)
  4. 4

复制代码这些只是冰山一角, scheme 提供了许多和非常全面的数值计算和三角函数. 比如, atan, exp 和 sqrt 函数等等.

3. characters(字符类型)

scheme 字符类型均是以 # 开头的. 比如, #c 代表 c 字符. 一些 non-graphic 字符具有描述性的名字,比如, #newline、#tab, 空格符可以写为 #  , 但 #space 更易懂.
char? 函数判断一个值是否属于字符类型:

  1. guile> (char? #c)
  2. #t
  3. guile> (char? 1)
  4. #f
  5. guile> (char? #;)
  6. #t

复制代码注意, 这里的分号及其后不会被注释.

字符类型有一些比较函数: char=?, char<?, char<=?, char>?, char>=?:

  1. guile> (char=? #a #a)
  2. #t
  3. guile> (char<? #a #b)
  4. #t
  5. guile> (char>=? #a #b)
  6. #f

复制代码可以使用 char-ci 来代替 char, 但这些函数比较字符时忽略字符大小写:

  1. guile> (char-ci=? #a #A)
  2. #t
  3. guile> (char-ci<? #a #B)
  4. #t

复制代码可以用 char-downcase 和 char-upcase 进行字符大小写转换:

  1. guile> (char-downcase #A)
  2. #a
  3. guile> (char-upcase #a)
  4. #A

复制代码4. symbol(符号)
上面我们介绍的简单类型数据都是自求值的(self-evaluating). 比如, 如果我们在 repl 中键入这些类型的数据, 所输出的结果就是这个数据的自身:

  1. guile> #t
  2. #t
  3. guile> 42
  4. 42
  5. guile> #c
  6. #c

复制代码而符号类型不是这样, 这是因为符号在 scheme 中被用作变量的标识符, 因此符号将被计算为变量的值. 尽管如此, 符号类型仍然属于简单数据类型, 在 scheme 中符号与字符、数值等等都属于合法的值.

为了使一个符号不被 scheme 认为是一个变量, 我们应该像下面用 quote 语句来返回这个符号:

  1. guile> (quote xyz)
  2. xyz

复制代码因为在 scheme 中引用(quote)一个符号特别常见, 对此有一个简写形式:

  1. guile> 'E               ;'E 在 scheme 中与 (quote E) 等价
  2. E
  3. guile> (quote E)
  4. E

复制代码在 guile 中, 符号的大小写是不同的. 因此 Calorie 和 calorie 是不同的符号<但是有一些其它的 scheme 实现大小写是相同的>:

  1. guile> (eqv? 'Calorie 'calorie)
  2. #f

复制代码我们可以利用 define 语句定义一个符号 xyz 作为一个全局变量:

  1. guile> (define xyz 9)
  2. guile> xyz
  3. 9

复制代码我们可以使用 set! 语句来改变一个变量的值:

  1. guile> (set! xyz #c)
  2. guile> xyz
  3. #c

复制代码

素食主义者 2022-09-08 23:42:45

本帖最后由 Lispor 于 2010-11-21 22:39 编辑

第一贴: Hello World

进入 scheme 之前, 先写一个经典的 "Hello World" 程序:

  1. ;The first program "hello.scm"
  2. (begin
  3.   (display "Hello, World!")
  4.   (newline))

复制代码其中:

  • 第一行为注释, 当 scheme 遇到分号(;)时, 就会把分号到行尾的内容当作注释而不去处理.
  • begin-form 在 scheme 中会对其后的子表达式顺序求值, 并返回最后一个子表达式的结果. 在本例中, begin-form 有两个子表达式, 第一个是 display 函数, 该函数打印出其参数<string>; 第二个是 newline 函数,该函数打印一个换行符<carriage return>.

为了运行这个程序, 首先要运行你的 scheme.
在此, 我用的是 guile.<当然你也可以用 scheme 其他的实现>
在终端键入 "guile", 随后出现一个提示符 "guile>"

  1. $ guile
  2. guile>

复制代码此时, 你进入了 REPL<read-eval-pring-loop>, 顾名思义, read your input, evaluate it, print the result(if any).

键入:

  1. guile> (load "hello.scm")
  2. Hello, World!

复制代码退出你的 scheme, 键入:

  1. guile> (exit)
  2. $

复制代码

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文