| 论坛注册| 加入收藏 | 设为首页| RSS
Google
您当前的位置:首页 > Linux频道 > Linux开发区 > 软件开发

Scheme 语言介绍

时间:2005-11-30 18:49:33  来源:  作者:
        (member 'vampire '(troll banshee vampire))
        ;(vampire)
        (member 'banshee '(troll banshee vampire))
        ;(banshee vampire)
        (assoc  'LittleBear '((HappyMole vampire)
                              (LittleBear banshee)
                              (LittleTiger troll)))
        ;(LittleBear banshee)

Scheme 还提供一些函数来在表和字符串之间做转换。但是,它们只有有限的用处,因为 list->string 只接受字符的表,而 string->list 对一个字符串的成员生成它们的 ascii 码等价的一个列表。

        (list->string '(#b #e #a #r))
        ; "bear"
        (string->list "bear")
        ; (98 101 97 114)

向量是异类的结构,它的分量可以被改变并使用开始于 0 的整数来索引。它们的长度是它们可以包含的元素的数目,并在建立向量的时候固定了。有效的索引值位于 0 到(length - 1)范围内。使用 # 符号指示向量。

        (vector 7 "is one of" '(7 13 42) )
        ;#(7 "is one of" (7 13 42) )

是有三个分量的 3 向量: 一个数值,一个字符串,和一个表。要生成这样的向量。Scheme 提供了创建(make-vector, vector)、谓词(vector?)、选择(vector-length, vector-ref)、更换(vector-set!)和转换(vector->list)过程。

        (define Friends (make-vector 5))
        ;#( () () () () () )
        (vector-set! Friends 0 'bear)
        ;#( bear () () () () )
        (vector-length Friends)
        ;5
        (vector-ref Friends 1)
        ;()
        (vector-ref Friends 0)
        ;bear
        (vector->list Friends)
        ;(bear () () () ())

在到分量的访问必须被计算出来的应用中、向量是有用的数据结构;比如,在棋盘中移动是可以运算出来的。例如,一个象棋盘可以存储在分量是八个向量的一个向量,其中每个向量的八个分量都可以持有“棋子”或表示空方格。

导引控制流

任何编程语言都需要提供某种设施来导引程序执行的流程。这种控制结构传统上迎合三个种类:顺序、选择和重复。Scheme 表达式通常顺序的求值;最内层的表达式先于在程序外层的表达式。cond、if 和 case 将在多个可供选择的执行路径中选择某一个,还可以使用 begin 把表达式组合到一个复合结构中。

        ; 一次"化装"舞会
        (define party '((HappyMole vampire)
                        (LittleBear banshee)
                        (LittleTiger troll)))
        ;party
        ;和一个"猜谜游戏"
        (cond
         ((equal? (assoc 'HappyMole party)
                  '(HappyMole troll))
          "moles are trolls")
         ((equal? (assoc 'LittleBear party)
                  '(LittleBear banshee))
          "bears are banshees")
         ((equal? (assoc 'LittleTiger party)
                  '(LittleTiger troll))
          "tigers are trolls")
         (else "bad guess ?"))
 
        ;bears are banshees

cond 过程将接收任何数目的条件-动作对,这里的条件将按顺序求值,如果非假,则接着导致与这个条件相关联的所有表达式的执行。总是如此,最后一个表达式生成的值将是 cond 返回的值。与 Pascal 等传统语言不同,条件的求值是“懒惰的”。这意味着只在前面的条件被证实为假的时候才进行当前的尝试。一旦找到真条件并且它的相关表达式已经被求值了,cond 立即退出。在上面的例子中对 LittleTiger 的角色的正确猜测将永远找不到。这里有可选的 else 子句在所有条件都是假的时候调用。cond 是足够强力来描述任何可能的选择。但是,出于程序员的方便,还是提供了一些额外的选择函数,因为 cond 对于很多人好象是太复杂了。

        ;查找作为‘舞会’中第一对舞伴的键的 tiger
        (if (equal? (caar party) 'LittleTiger)
            (display "tiger is first")
            (display "keep looking"))
        ;keep looking #t

if 期望三个表达式: 一个条件,一个 then 分支和一个 else 分支。如果条件产生假,则条件产生假,则执行第三个表达式(代表 else 分支),否则执行第二个表达式(then 分支)。可以使用 begin 来定义符合表达式,因为它对于在一个分支上关联多于一个表达式经常是必须的。

        ;查找作为‘舞会’中第一对舞伴的键的 mole
        (if (equal? (caar party) 'HappyMole)
            (begin (DisplayLine "mole is first")
                   (display "who's next ?"))
            (display "keep looking"))
        ;mole is first
        ;who's next ? #t

case 表达式类似于对应的 Pascal 控制结构。求值一个表达式并使用结果的值作为索引,用来从一组侯选者中做出选择。每个侯选者都被描述为选择子和相关的一些表达式的一个表,选择子(selector)必须是常量。

        (set! age 42)
        (case (+ age 1)
              ((6    ) 'excited)
              ((21   ) 'rejoicing)
              ((30 40) 'depressed)
              (else 'indifferent))
        ;indifferent
        ;Owl 的生日聚会
        (set! luckyFellow 'owl)
        (DisplayList "an ideal pet for" luckyFellow " would be a ")
        (display (case luckyFellow
                       ((tiger bear mole) "dog")
                       ((owl)             "tortoise")
                       ((fox)             "snake")
                       ((duck elephant)   "goldfish")
                       (else "???")) )
        ;an ideal pet for owl would be a tortoise

递归是在 Scheme 中指定重复的最优雅和有效的方式。但是,我们将推迟查看这个概念直到讨论完过程之后。尽管能用迭代表示的任何东西都可以捕获到一个等价的递归公式中,也不管 Scheme 优化它的执行以至于在效率上没有相关的损失的事实,我可以使用 do 来描述迭代执行。在第一眼上,do 好象是某种复杂的构造。它接受一些索引变量,它们开始于某个初始值,并以指定的方式增加它们。测试终止条件,如果它返回假则接着求值它的循环体,如果循环终止,则求值一个序列的表达式并返回最后一个的值。do 的结构被示例如下。

       (do ((  ) ...)
           ; 声明变量
           (  ... )
           ;-> 跳出
           ... )
           ; 执行循环体
        (let ((friends '(mole bear tiger)))
          (do ((next friends (cdr next)))
              ((null? next) "all clean now !")
              (DisplayLine "wash " (car next))))
        ;wash mole
        ;wash bear
        ;wash tiger
        ;"all clean now !"

let 为循环的执行定义一个适当的局部上下文。注意对 friends 的绑定只在 let 的作用域内是可获得的。很多 Scheme 方言(比如 MacScheme)也为无限迭代提供了 while 表达式。因为这不是一个标准特征,我们将限定只在它显著的增加代码的清晰性的情况下才使用它。它一般很容易被递归或 do 所取代。除了跨越表达式的迭代之外,Scheme 还提供了跨越表的所有元素的迭代。

        (for-each display party)
        ;(HappyMole vampire) (LittleBear banshee)
        (LittleTiger troll) ()
        (map abs '(1 -2 -3))
        ;(1 2 3)

for-each 对一个表的所有元素应用表达式。典型的用于发挥这个过程产生的副作用的利益。for-each 返回的值是未指定的,在另一方面,把每个应用的结果写到一个表中,接着返回它。在两种情况中的过程都不需要是预先定义的过程。程序员可以提供 lambda 表达式或他自己命名的过程。


Lambda 表达式和环境

Scheme 的执行环境被表示为所谓的“lambda 表达式”。这个术语起源于 Church 的 lambda 演算(calculus),它充当 MacCarthy 在符号计算上的某些想法的模型。Lambda 表达式开始于一个关键字“lambda”,随后式一个(可能为空)参数的列表和一个表达式序列。当 lambda 表达式应用在正确的上下文中的时候,这些表达式按顺序执行并返回最后的值。

        (lambda () "hi there")
        ;#
        ( (lambda () "hi there") )
        ;"hi there"

在第一个例子中的 lambda 表达式定义过程文字。在第二个 lambda 表达式周围包装了额外的一对括号强制它执行。Lambda 表达式可以接受固定或可变数目的参数。使用不同的语法惯例来指出需要那种参数传递机制。

        ((lambda (aFriend) ; 函数有一个形式参数: aFriend
            (DisplayLine "hi there " aFriend))
         'HappyMole) ; 调用时带有一个实际参数: HappyMole
        ;hi there HappyMole #t

这里把由 lambda 表达式表示的过程应用到作为它的参数的 "HappyMole"上。这生成一个计算,在其中 DisplayLine 把要求的字符串放置到屏幕上。#t 是最后的显示返回的值,因此它还是整个 lambda 表达式的返回值。如果你在你自己的平台上写过了这个例子,你最有可能忘记引用 HappyMole,导致 Scheme 抱怨另一个 "unbound variable"。得到正确的引用可能需要很多实践,但是不久之后它就会成为“第二天性”。

Scheme 提供两种可供选择的方式来要求可变数目的实际参数。

        ; lambda 表达式,调用时带有 3 个实际参数
        ((lambda someFriends  ; 参数周围没有括号 !
            (DisplayLine "hi there " someFriends))
         'mole 'bear 'tiger)
        ;hi there (mole bear tiger) #t
        ; 调用时带有 0 个实际参数
        ((lambda someFriends
            (DisplayLine "hi there" someFriends)) )
        ;hi there () #t  

下面的所有参数都绑定到一个表(它可以为空)中并传递给 lambda 表达式。请注意我们必须提供一个不带任何括号的单一的参数来导致这种行为。如果我们想要让我们所有的参数都是可选择的,可以使用

        ; 一个强制的和一些可选的参数
        ((lambda (aFriend . someMore)
            (DisplayLine "hi there " someMore))
         'mole 'bear 'tiger) ; 三个参数
        ;hi there (bear tiger) #t  ; mole 现在被绑定到 "aFriend"
        ((lambda (aFriend . someMore)
            (DisplayLine "hi there" someMore)))
            ; 没有参数
        ;ERROR: Too few arguments to procedure
        ;0 arguments supplied - 1 argument expected

这里的 aFriend 将被绑定为 mole,而所有余下的实际参数将再次被组合到一个表中并绑定到 someMore。 当然,我们现在必须提供最少一个实际参数。

Scheme 提供一个谓词(procedure?)来测试一个对象实际上是否为过程。apply 在某些情况下也是有用的,它强制一个过程在当前上下文中的应用。注意 apply 总是期望一个表作为它的单一的参数。

        (procedure? (lambda () (display "hello")))
        ;#t
        (apply (lambda (aFriend)
                 (DisplayLine "hello" aFriend))
               '(mole) )
        ;hello mole #t

Lambda 表达式可以包含它们自己的对数据和过程的“局部”定义。因此它们是主要的的环境建造块, Scheme 依据“词法作用域”的概念而有层次的安排它们。使用这种环境来定义在一个计算期间的所有点上(“作用域内”)都是当前可见的对象,早期的 Lisp 系统只支持“动态作用域”,这是导致无数非常难于跟踪的程序错误的一个“特征”。

执行的嵌套上下文

 4/7   |‹ ‹‹ 2 3 4 5 6 7 ›› ›|

来顶一下
近回首页
返回首页
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
相关文章
    无相关信息
栏目更新
栏目热门