我是靠谱客的博主 怕黑香烟,最近开发中收集的这篇文章主要介绍Scheme 编程语言(1)介绍,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Scheme 编程语言(1)

Translator: Once Day date:2022年10月22日

漫漫长路有人对你微笑过吗…

仅供学习交流之用,请尊重原书版权

原书:《The Scheme Programming Language, Fourth Edition》R. Kent Dybvig.

原书章节: The Scheme Programming Language : Chapter 1.Introduction.

这是一个学习的过程,不断修改和完善,供自己日后复习,水平有限,自娱自乐。

机翻文档,兼带修改。私人笔记,切莫多言

1.介绍

Scheme是一种通用的计算机编程语言。它是一种高级语言,支持操作结构化数据,如字符串、列表和向量,以及传统的数据类型(数字和字符)。Scheme通常被认为专于符号处理程序,但它同样具有丰富的数据类型和灵活的控制结构,因此也可作为通用的语言。可以使用Scheme来编写文本编辑器、优化的编译器、操作系统、图形软件包、专家系统、数值应用程序、财务分析软件包、虚拟现实系统,以及几乎所有其他类型的应用程序。Scheme是一种相当简单的语言,因为它基于少量的语法形式和语义概念。大多数的Scheme实现具有交互式的shell,因此可以一边交互一边学习。然而,Scheme是一种很难完全理解的语言,想要完全学会这门语言,需要细心且认真的学习和实践

对于相同的实现,Scheme程序在不同机器上的各版本之间是高度可移植的,因为对程序员来说,机器依赖关系几乎是完全隐藏的。经过很多Scheme语言设计者的努力,它们还可以在不同的实现之间移植。这些Scheme语言设计者发布了一系列关于Scheme的报告,即“修订版报告”。最近的“修订(6)报告”强调通过一组标准库和定义新的可移植库及顶级程序的标准机制来实现可移植性。

一些早期的Scheme系统效率低下且速度缓慢,不过许多基于编译器的新Scheme实现速度很快,其程序运行速度与用低级语言编写的等效程序一样快。有时会保留一些相对低效的功能,如支持泛型算法的实时检查(run-time checks),以及帮助程序员检测和纠正各种常见的编程错误。这些检查功能可能在许多实现中被禁用。

Scheme支持多种类型的数据值或对象,包括字符、字符串、符号、对象的列表或向量,以及一整套数值数据类型,包括复数、实数和任意精度有理数。

保存对象内容所需的存储空间根据情况动态分配,并保留存储空间直到不再需要,然后自动释放,这通常由垃圾收集器来定期释放不可访问对象所使用的存储空间。简单的原子值,例如小整数、字符、布尔值和空列表,通常表示为直接值(不需要通过指针来间接访问存储空间),因此不会产生分配或回收开销。

无论如何表示,所有对象都是一级数据(first-class data)值。因为它们是无限保留的,所以它们可以作为过程(函数)的参数自由传递,作为过程(函数)的值返回,并组合成新的对象。这与许多其他的编程语言不同,在这些语言中,数组等复合数据有以下情况:

  • 要么是静态分配且永不释放
  • 要么是在进入代码块时分配,在代码块退出时无条件释放
  • 要么是由程序员显式分配并释放

Scheme语言的核心是一个语法形式的小核心,所有其他形式都是从它构建的。这些核心形式,以及从它们派生出的一组扩展语法形式和一组基本过程组成了完整的Scheme语言。Scheme的解释器或编译器可以非常小,可能非常快,而且非常可靠。扩展的语法形式和许多基本过程可以用Scheme自定义,从而简化了实现并提高了可靠性。

Scheme程序与Scheme数据结构共享一个公共的打印表示。因此,任何Scheme程序都具有作为Scheme对象的原生内部表示。例如,变量和语法关键字对应符号,而结构化语法形式对应列表(lists)。这种表示是Scheme提供的语法扩展工具的基础,该工具用于根据现有的语法形式和过程定义新的语法形式。它还促进了在Scheme中直接实现解释器、编译器和其他针对Scheme的程序转换工具,以及在Scheme中实现针对其他编程语言的程序转换工具。

Scheme变量和关键字具有词法范围(lexically scoped),Scheme程序具有块结构。标识符可以导入到程序或库中,也可以在给定的代码块(如库、程序或过程体)中本地绑定。局部绑定仅在其词法范围内可见,即在组成特定代码块的程序文本内。如果在此块之外出现同名标识符,则指向不同的绑定,如果块外部没有对标识符的绑定,则引用无效。块可以嵌套,一个块中的绑定可以遮蔽周围块中同名标识符的绑定。绑定的范围是绑定标识符可见的块减去标识符被遮蔽的块剩下的其他部分。块结构和词汇作用域有助于创建模块化、易于阅读、易于维护和可靠的程序。高效的词法作用域代码是可能的,因为编译器可以在程序求值之前确定所有(标识符)绑定的作用域以及每个标识符引用解析对应的绑定。当然,这并不意味着编译器可以确定所有变量的值,因为在大多数情况下,直到程序执行时才计算出实际的值。

在大多数语言中,过程(函数)定义只是名称与代码块的关联。代码块的某些局部变量是过程(函数)的参数。在某些语言中,过程定义可以出现在另一个代码块或过程中,但该过程只能在定义的代码块中被调用。在其他情况下,过程只能在顶层区域中定义。在Scheme中,过程定义可能出现在另一个块或过程中,并且该过程可能在此后的任何时候被调用,即使所包含的块已经完成了它的执行。为了支持词法作用域,过程在其代码中附带词法上下文(环境)。

此外,Scheme过程并不总是被命名。相反,过程是第一类数据对象,就像字符串或数字,并且变量绑定到过程与绑定到其他对象的方式相同。

与大多数其他编程语言中的过程一样,Scheme过程可以是递归的。也就是说,任何过程都可以直接或间接地调用自己。许多算法都是通过使用递归形式来最优雅或最有效地表达出来。递归的一种特殊情况称为尾部递归,用于表示迭代或循环。尾部调用发生在一个过程直接返回另一个过程调用的结果时。当过程直接或间接递归地尾部调用自身时,就会发生尾部递归。Scheme实例需要将尾部调用实现为跳转(goto),因此可以避免与递归相关的存储开销。所以,Scheme程序员只需要掌握简单的过程调用和递归,而不需要关心常见循环构造的分类。

Scheme支持定义带有continuations(延续流)的任意控制结构。continuation是一种过程,它将程序的剩余部分体现在程序的给定点上。continuation可以在程序执行期间的任何时候获得。与其他过程一样,continuation是一级对象,可以在创建后的任何时间调用。无论何时调用它,程序都立即从获得continuation的(代码)点开始继续。延续允许实现复杂的控制机制,包括显式回溯、多线程和协程。

Scheme还允许程序员通过编写转换过程( transformation procedures)来定义新的语法形式(syntactic forms)或语法扩展(syntactic extensions),转换过程确定每个新的语法形式如何映射到现有的语法形式。在便利的高级模板语言的帮助下,这些转换过程本身在Scheme中表示,并且该高级模板语言自动化完成语法检查、输入解构和输出重构。默认情况下,在转换过程中维护词法作用域,但是程序员可以控制转换输出中出现的所有标识符的作用域。语法扩展用处很大,如定义新的语言结构、模拟在其他语言中发现的语言结构、实现内联代码扩展的效果,以及甚至在Scheme中模拟整个语言。大多数大型Scheme程序都是由混合的语法扩展和过程定义构建的。

Scheme从Lisp语言演变而来,被认为是Lisp的一种方言。Scheme从Lisp继承了很多特性,包括以下部分:

  • 将值作为第一级对象的处理
  • 一些重要的数据类型(包括符号和列表)
  • 将程序表示为对象

词法作用域和块结构是来自Algol 60的特性。Scheme是第一个采用词法作用域和块结构、一级过程、将尾部调用作为跳转、延续和词法作用域语法扩展等特性的Lisp方言。

Common Lisp和Scheme都是当代的Lisp语言,它们各自的发展都受到了对方的影响。与Scheme类似,但与早期的Lisp语言不同的是,Common Lisp采用了词汇作用域和一级过程,尽管Common Lisp的语法扩展功能不考虑词汇作用域。但是,Common Lisp的过程求值规则不同于其他对象的求值规则,它为过程变量维护了单独的名称空间,因此无法将过程用作第一级对象。此外,Common Lisp不支持延续,也不要求对尾部调用进行适当的处理,但它支持Scheme中未包含的几个不太通用的控制结构。虽然这两种语言是相似的,但是Common Lisp包含了更多专门的构造,而Scheme则包含了更多通用的构建块,可以在此基础上构建这些Common Lisp独有的构造(和其他构造)。

本章的其余部分将描述Scheme的语法和命名约定以及本书中使用的排版约定。

1.1 Scheme 语法

Scheme程序由关键字、变量、结构化形式、常量数据(数字、字符、字符串、引用向量、引用列表、引用符号等)、空格和注释组成。

关键字、变量和符号统称为标识符。标识符可以由字母、数字和某些特殊字符组成,特殊字符包括以下部分:

?, !, ., +, -, *, /, <, =, >, :, $, %, ^, &, _, ~, @

除此之外,特殊字符还含有一组额外的Unicode字符。标识符不能以@符号开头,通常也不能以任何可组成有效数字的字符开头,例如,数字、加号(+)、负号(-)或小数点(.),像+, -, .如果用来表示正数,负数以及小数,那么是无效的标识符。

但是单纯表示+-.那么它们就是有效的标识符,如+kl->等都是有效的,只要不组成一个有效数字即可

例如,hi, Hello, n,x, x3, x+2?$&*!!都是标识符。

标识符由空格注释圆括号()方括号[](双)引号(")散列标记(#)分隔。分隔符或任何其他Unicode字符可以作为xsv;形式的转义符,并可位于在标识符名称中的任何位置,其中sv是十六进制表示的字符标量值。

Scheme标识符的长度没有固有的限制,程序员可以根据需要使用尽可能多的字符。但是,长标识符不能代替注释,频繁使用长标识符会使程序难以格式化,从而导致难以阅读。一个好的规则是,当标识符的范围很小时使用短标识符,当标识符的范围较大时使用长标识符

标识符可以用大写字母和小写字母的任意组合书写,并且区分大小写。例如, abcde, Abcde, AbCdE和ABCDE都引用不同的标识符。这与以前版本的修正报告(Revised Report)有所不同。

结构化形式(Structured forms和列表常量被括在圆括号内,例如(a b c)(* (- x 2) y)。空列表被写为()。匹配的方括号([])可以用来代替圆括号,通常用于分隔某些标准语法形式的子表达式,以提高可读性,如本书中的示例所示。向量的书写方式与列表类似,只是它们的前面是#,然后是( ),例如,#(这是一个符号向量)。字节向量被写成由#vu8()括起来的无符号字节值序列(0到255的精确整数),例如,#vu8(3 250 45 73)

字符串用双引号括起来,例如,"I am a string"。字符前面是#,例如#a表示字符a字符和字符串常量区分大小写,就像在标识符中一样。数字可以写成整数,例如-123,也可以写成分数,例如1/2。小数可以用浮点数或科学记数法表示,例如1.31e23。还可以写成复数,使用直角坐标或极坐标来表示,例如1.3 - 2.7i-1.2@73在数字的表示中,可忽略大小写的区别

表示true和false的布尔值写为#t#f。Scheme条件表达式实际上将#f视为false,而将所有其他对象视为true,因此30()“false”nil都计算为true

关于每种常量数据类型的语法的详细信息在第6章的各个章节和从第455页开始的Scheme的正式语法中给出。

  • Operations on Objects (scheme.com)(原文第六章)
  • Formal Syntax (scheme.com)(原文455页)

Scheme表达式可能跨越几行,不需要显式输入终止符。表达式之间的空白字符(空格和换行符)的数量并不重要,所以Scheme程序(代码)应该缩进以使得代码尽可能易读地显示代码的结构。注释可以出现在Scheme程序的任何一行,以分号(;)开始,直到该行的结束(换行符截止)。解释特定Scheme表达式的注释通常以同等缩进级别放置于表达式的上一行。解释一个过程或一组过程的注释通常放在过程之前,没有缩进。通常使用多个注释字符来分隔后一种注释,例如;;; 解释以下过程做了什么....

支持另外两种形式的注释: 块注释和数据注释。块注释由#||#对分隔,并且可以嵌套。数据注释由#;前缀及其后面的数据(printed data value)组成。数据注释通常用于注释掉单个定义或表达式。例如,(three #;(not four) element list)注释掉了(not four)。数据注释也可以嵌套,比如#;#;(a)(b)有注释掉(a)和(b)的不太明显的效果。

一些Scheme值,例如过程(procedures)和端口(ports),没有标准的打印表示,因此永远不能作为常量出现在程序的打印语法中。本书使用了#<description>来显示一个过程的输出,例如#<procedure>#<port>

1.2 Scheme 命名约定

Scheme的命名约定旨在提供高度的规律性。以下是这些命名约定的列表:

  • 谓词名称以问号结尾(?)。谓词是返回true或false答案的过程,如eq?, 0 ?,和字符串= ?。常见的数值比较=< > <=>=是这种命名约定的例外,可以不用加?
  • 类型谓词,例如pair?,从类型的名称(在本例中是pair)和问号创建。
  • 大多数用于字符、字符串和向量的过程其名称都以前缀char-string-vector-开头,例如string-append。(一些列表过程的名称以list-开头,但大多数情况下没有以该前缀开头。)
  • 将一种类型对象转换为另一种类型对象的过程其名称写为type1->type2,例如vector->list
  • 具有副作用的过程和语法形式其名称以感叹号(!)结尾,这包括set!vector-set!。技术上执行输入或输出的过程会产生副作用,但它们是此规则的例外,因此不用加上!

程序员应该尽可能在自己的代码中使用这些相同的约定。

1.3 排版和符号约定

有一类标准过程或语法形式被称为返回未指定值(return unspecified),因为它们唯一目的是产生某种副作用。这意味着Scheme实现可以自由返回任意数量的值,其每个值都可以是任何Scheme对象,以此作为过程或语法形式的值。不要指望这些值在不同Scheme实现,或者在相同Scheme实现的不同版本,亦或是在过程或语法形式的两种使用等之间是相同的。一些Scheme系统通常使用特殊对象来表示未指定的值。交互式Scheme系统通常会禁止此对象的打印,因此对于返回未指定值的表达式,不打印它们的值。

虽然大多数标准过程返回一个值,但该语言支持通过第5.8节描述的机制返回0、1、多个甚至可变数量值的过程。如果一个子表达式求值为多个值,那么某些标准表达式可以求值为多个值,例如,通过调用返回多个值的过程。当出现这种情况时,我们说表达式返回的是“值”,而不是其子表达式的“值”。类似地,如果一个标准过程从其过程实参(procedure argument, 参数是一个过程)调用中返回值,则称为返回其过程实参返回的值。

本书使用“must”和“should”来描述程序需求,例如在调用vector-ref时,需要提供一个小于vector长度的索引。如果使用了“必须”这个词,这意味着需求是由Scheme实现强制执行的,即抛出异常,通常带有条件类型和断言。如果使用了“should”,则可能引发异常,也可能不引发异常,如果没有,则程序的行为未定义。

短语“语法违规(syntax violation)”用于描述程序格式不正确的情况。在程序执行之前可以检测到语法违规。当检测到语法违规时,将引发类型和语法的异常,程序将不执行。

本书中使用的排版惯例很简单。所有Scheme对象都使用打字机字体打印,就像在键盘上键入一样。这包括语法关键字、变量、常量对象、Scheme表达式和示例程序。斜体用于在语法形式的描述中分隔语法变量和在过程的描述中分隔参数。斜体字也用在技术术语第一次出现时将其分隔开来。一般来说,语法形式和过程的名称从来不大写,即使在句子的开头也是如此。对于用斜体字书写的语法变量也是如此(开头不大写)。

在对语法形式或过程的描述中,一个或多个原型模板表明了在使用该过程的应用程序中语法形式或参数的正确数量。关键字或过程名用打字机字体给出,括号也是。语法或参数的其余部分以斜体显示,使用的名称暗示了语法形式或过程所期望的表达式或参数的类型。省略号用于指定子表达式或参数出现0次或多次。例如,(or expr …)描述or语法形式,它有零个或多个子表达式,(member obj list)描述一个成员过程,它需要两个参数,一个对象和一个列表。

如果语法形式的结构与它的原型不匹配,就会发生语法冲突。类似地,如果传递给标准过程的参数数量与指定接收的参数数量不匹配,则会引发带有条件类型和断言的异常。如果标准过程接收到的实参的类型不是其名称所隐含的类型,或者不满足过程描述中给出的其他条件,则会引发带有条件类型和断言的异常。例如,vector-set!原型是

(vector-set! vector n obj)

描述说明n必须是一个精确的非负整数,并严格小于向量的长度。因此,vector-set!必须接收三个参数,第一个参数必须是一个向量,第二个参数必须是一个小于向量长度的精确非负整数,第三个参数可以是任何Scheme值。否则,将引发带有条件类型和断言的异常。

在大多数情况下,所需的实参类型是显而易见的,例如vectorobjbinary-input-port。在其他情况下,主要是在数字例程的描述中使用缩写,例如int表示整数,exint表示精确整数,fx表示固定数值。对于那些受影响的部分,在对应章节的开始部分会解释这些缩写。

(第一章完,后续章节将会慢慢更新…)

最后

以上就是怕黑香烟为你收集整理的Scheme 编程语言(1)介绍的全部内容,希望文章能够帮你解决Scheme 编程语言(1)介绍所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部