The Racket Guide

英文文档: https://docs.racket-lang.org/guide/index.html

中文翻译: Racket指南

本指南适用于新的Racket程序员或部分新的Racket程序员。本指南假定你是有编程经验的。如果您是新学习编程,那么请阅读《如何设计程序》(How to Design Programs)这部分。如果你想特别快地了解Racket语言,从这里开始:《快速:用图片介绍Racket》《 Quick: An Introduction to Racket with Pictures》这部分。

第2章简要介绍Racket语言。从第3章开始,本指南深入讨论了大部分的Racket语言工具箱,但把更清晰的细节内容留给Racket语言参考手册和其他参考手册介绍。

目录

2 Racket语言概要(OK)
  2.1 简单值(OK)
  2.2 简单的定义与表达式(OK)
    2.2.1 定义(OK)
    2.2.2 代码缩进(OK)
    2.2.3 标识符(OK)
    2.2.4 函数调用(应用程序)(OK)
    2.2.5 条件表达式if、and、or和cond(OK)
    2.2.6 函数重复调用(OK)
    2.2.7 匿名函数与lambda(OK)
    2.2.8 用define、let和let*实现局部绑定(OK)
  2.3 列表、迭代和递归(OK)
    2.3.1 预定义列表循环(OK)
    2.3.2 从头开始列表迭代(OK)
    2.3.3 尾递归(OK)
    2.3.4 递归和迭代(OK)
  2.4 配对(pair)、列表(list)和Racket的语法(OK)
    2.4.1 用quote引用pair和symbol(OK)
    2.4.2 使用’缩写quote(OK)
    2.4.3 列表和Racket的语法(OK)

3 内置的数据类型(OK)
  3.1 布尔值(Boolean)(OK)
  3.2 数值(Number)(OK)
  3.3 字符(Character)(OK)
  3.4 字符串(Unicode Strings)(OK)
  3.5 字节(Byte)和字节字符串(Byte String)(OK)
  3.6 符号(Symbol)(OK)
  3.7 关键字(Keyword)(OK)
  3.8 配对(Pair)和列表(List)(OK)
  3.9 向量(Vector)(OK)
  3.10 哈希表(Hash Table)(OK)
  3.11 格子(Box)(OK)
  3.12 空值(Void)和未定义值(Undefined)(OK)

4 表达式和定义(OK)
  4.1 标记法(OK)
  4.2 标识符和绑定(OK)
  4.3 函数调用(过程程序)(OK)
    4.3.1 求值顺序和元数(OK)
    4.3.2 关键字参数(OK)
    4.3.3 apply函数(OK)
  4.4 lambda函数(程序)(OK)
    4.4.1 申明剩余(rest)参数(OK)
    4.4.2 声明可选(optional)参数(OK)
    4.4.3 声明关键字(keyword)参数(OK)
    4.4.4 多解函数:case-lambda(OK)
  4.5 定义:define(OK)
    4.5.1 函数简写(OK)
    4.5.2 咖喱函数简写(OK)
    4.5.3 多值和define-values(OK)
    4.5.4 内部定义(OK)
  4.6 局部绑定(OK)
    4.6.1 平行绑定:let(OK)
    4.6.2 顺序绑定:let*(OK)
    4.6.3 递归绑定:letrec(OK)
    4.6.4 命名let(OK)
    4.6.5 多值绑定:let-values,let*-values,letrec-values(OK)
  4.7 条件分支(OK)
    4.7.1 简单分支:if(OK)
    4.7.2 组合测试:and和or(OK)
    4.7.3 约束测试:cond(OK)
  4.8 排序(OK)
    4.8.1 前置影响:begin(OK)
    4.8.2 后置影响:begin0(OK)
    4.8.3 if影响:when和unless(OK)
  4.9 赋值:set!(OK)
    4.9.1 使用赋值的指导原则(OK)
    4.9.2 多值赋值:set!-values(OK)
  4.10 引用:quote和'(OK)
  4.11 准引用:quasiquote和`(OK)
  4.12 简单分派:case(OK)
  4.13 动态绑定:parameterize(OK)

5 自定义的数据类型(OK)
  5.1 简单的结构类型:struct(OK)
  5.2 复制和更新(OK)
  5.3 结构子类(OK)
  5.4 不透明结构与透明结构的对比(OK)
  5.5 结构的比较(OK)
  5.6 结构类型的生成性(OK)
  5.7 预制结构(OK)
  5.8 更多的结构选项(OK)

6 模块(OK)
  6.1 模块基础知识(OK)
    6.1.1 组织模块(OK)
    6.1.2 库集合(OK)
    6.1.3 包和集合(OK)
    6.1.4 添加集合(OK)
  6.2 模块的语法(OK)
    6.2.1 module表(OK)
    6.2.2 #lang简写(OK)
    6.2.3 子模块(OK)
    6.2.4 main和test子模块(OK)
  6.3 模块的路径(OK)
  6.4 导入:require(OK)
  6.5 输出:provide(OK)
  6.6 赋值和重定义(OK)

7 合约(OK)
  7.1 合约和边界(OK)
    7.1.1合约的违反(OK)
    7.1.2 合约与模块的测试(OK)
    7.1.3 嵌套合约的测试(OK)
  7.2 函数的简单合约(OK)
    7.2.1 ->类型(OK)
    7.2.2 define/contract和->的使用(OK)
    7.2.3 and和any/c(OK)
    7.2.4 扩展自己的合约(OK)
    7.2.5 合约的高阶函数(OK)
    7.2.6 带”???“的合约信息(OK)
    7.2.7 解析合约的错误消息(OK)
  7.3 通常的函数合约(OK)
    7.3.1 可选参数(OK)
    7.3.2 剩余参数(OK)
    7.3.3 关键字参数(OK)
    7.3.4 可选关键字参数(OK)
    7.3.5 case-lambda合约(OK)
    7.3.6 参数和结果的依赖(OK)
    7.3.7 检查状态的变化(OK)
    7.3.8 多个结果值(OK)
    7.3.9 固定但静态未知数量的参数(OK)
  7.4合约:一个全面的例子(OK)
  7.5 结构的合约(OK)
    7.5.1 对特定值的确保(OK)
    7.5.2 对所有值的确保(OK)
    7.5.3 检查数据结构的特性(OK)
  7.6 用#:exists和#:∃抽象合约(OK)
  7.7 额外的例子(OK)
    7.7.1 客户管理器的组成(OK)
    7.7.2 参数(简单)栈(OK)
    7.7.3 字典(OK)
    7.7.4 队列(OK)
  7.8 建立新合约(OK)
    7.8.1 合约结构属性(OK)
    7.8.2 所有的警告和报警(OK)
  7.9 陷阱(OK)
    7.9.1 合约和eq?(OK)
    7.9.2 合约的范围和define/contract(OK)
    7.9.3 合约的生存期和判定(OK)
    7.9.4 定义递归合约(OK)
    7.9.5 混合set!和合约(OK)

8 输入和输出(OK)
  8.1 端口的种类(OK)
  8.2 默认端口(OK)
  8.3 读和写Racket数据(OK)
  8.4 数据类型和序列化(OK)
  8.5 字节、字符和编码(OK)
  8.6 输入/输出模式(OK)

9 正则表达式(OK)
  9.1 写regexp模式(OK)
  9.2 匹配正则表达式模式(OK)
  9.3 基本申明(OK)
  9.4 字符和字符类(OK)
    9.4.1 常用的字符类(OK)
    9.4.2 POSIX字符类(OK)
  9.5 量词(OK)
  9.6 簇(OK)
    9.6.1 后向引用
    9.6.2 非捕捉簇(OK)
    9.6.3 回廊(OK)
  9.7 替代(OK)
  9.8 回溯(OK)
  9.9 前后查找(OK)
    9.9.1 向前查找(OK)
    9.9.2 向后查找(OK)
  9.10 一个扩展示例(OK)

10 异常与控制(OK)
  10.1 异常(OK)
  10.2 提示和中止(OK)
  10.3 延续(OK)

11 迭代和推导(OK)
  11.1 序列构造(OK)
  11.2 for和for*(OK)
  11.3 for/list和for*/list(OK)
  11.4 for/vector和for*/vector(OK)
  11.5 for/and和for/or(OK)
  11.6 for/first和for/last(OK)
  11.7 for/fold和for*fold(OK)
  11.8 多值序列(OK)
  11.9 打断迭代(OK)
  11.10 迭代性能(OK)

12 模式匹配(OK)

13 类和对象(OK)
  13.1 方法(OK)
  13.2 初始化参数(OK)
  13.3 内部和外部名称(OK)
  13.4 接口(Interface)(OK)
  13.5 Final、Augment和Inner(OK)
  13.6 控制外部名称的范围(OK)
  13.7混合(mixin)(OK)
    13.7.1 混合和接口(OK)
    13.7.2 mixin表(OK)
    13.7.3 参数化的混合(OK)
  13.8 特征(trait)(OK)
    13.8.1 混合集的特征(OK)
    13.8.2 特征的继承与超越(OK)
    13.8.3 trait表
  13.9类合约(OK)
    13.9.1 外部类合约(OK)
    13.9.2 内部类合约(OK)

14 单元(部件)(OK)
  14.1 签名和单元(OK)
  14.2 调用单元(OK)
  14.3 连接单元(OK)
  14.4 一级单元(OK)
  14.5 完整的-module签名和单元(OK)
  14.6 单元合约(OK)
    14.6.1 给签名添加合约(OK)
    14.6.2 给单元添加合约(OK)
14.7 unit(单元)与module(模块)的比较(OK)

15 反射和动态求值(OK)
  15.1 eval(OK)
    15.1.1 本地域(OK)
    15.1.2 命名空间(OK)
    15.1.3 命名空间和模块(OK)
  15.2 操纵的命名空间(OK)
    15.2.1 创建和安装命名空间(OK)
    15.2.2 共享数据和代码的命名空间(OK)
  15.3 脚本求值和使用load(OK)

16 宏(Macro)(OK)
  16.1 基于模式的宏(OK)
    16.1.1 define-syntax-rule(OK)
    16.1.2 词法范围(OK)
    16.1.3 define-syntax和syntax-rules(OK)
    16.1.4 匹配序列(OK)
    16.1.5 标识符宏(OK)
    16.1.6 set!转化器(OK)
    16.1.7 宏的宏生成(OK)
    16.1.8 扩展的例子:函数的参考调用(OK)
  16.2 通用宏转化器(OK)
    16.2.1 语法对象(OK)
    16.2.2 宏转化器程序(OK)
    16.2.3 混合模式和表达式:syntax-case(OK)
    16.2.4 with-syntax和generate-temporaries(OK)
    16.2.5 编译和运行时阶段(OK)
    16.2.6 通用阶段等级(OK)
      16.2.6.1 阶段绑定(OK)
      16.2.6.2 阶段和模块(OK)
    16.2.7 语法污染(OK)
      16.2.7.1 破坏模式(OK)
      16.2.7.2 污染和代码检查(OK)
      16.2.7.3 保护的导出(……)

17 创建语言
  17.1 模块语言
    17.1.1 隐式绑定
    17.1.2 使用#lang s-exp
  17.2 读者的扩展
    17.2.1 源位置
    17.2.2 可读性
  17.3 定义新的#lang语言
    17.3.1 指定#lang语言
    17.3.2 使用#lang reader
    17.3.3 使用#lang s-exp syntax/module-reader
    17.3.4 安装语言
    17.3.5 源操纵配置
    17.3.6 模块操纵配置

18 并发和同步
  18.1 线程
  18.2 线程的信箱
  18.3 信号量
  18.4 通道
  18.5 带缓冲的异步通道
  18.6 同步事件和sync

19 性能
  19.1 DrRacket中的性能
  19.2 字节码和即时(JIT)编译器
  19.3 模块与性能
  19.4 功能调用优化
  19.5 突变与性能
  19.6 letrec性能
  19.7 短数(fixnum)和大数(flonum)的最优化
  19.8 非检查的、非安全的操作
  19.9 外部指针
  19.10 正则表达式性能
  19.11 内存管理
  19.12 可达性和垃圾收集
  19.13 弱盒测试
  19.14 减少垃圾收集暂停

20 并行
  20.1 与未来的平行性
  20.2 点平行
  20.3 分布式的地方

21 运行和创建可执行文件
  21.1运行racket和gracket
    21.1.1 互动模式
    21.1.2 模块模式
    21.1.3 装载模式
  21.2 脚本
    21.2.1 UNIX脚本
    21.2.2 Windows批处理文件
  21.3 创建独立的可执行文件

22 更多的库
  22.1 图形和图形用户界面
  22.2 Web服务器
  22.3 使用外部库
  22.4 更多的库

23 Racket和Scheme的方言
  23.1 更多的Racket
  23.2 标准
    23.2.1 R5RS
    23.2.2 R6RS
  23.3 教学

24 命令行工具和编辑器选择
  24.1命令行工具
    24.1.1编译和配置:raco
    24.1.2 互动求值
    24.1.3 完整的Shell
  24.2 Emacs
    24.2.1 专家模式
    24.2.2 较小模式
    24.2.3 包specific到有害模式
  24.3 vim
  24.4 Sublime Text

参考文献

索引

1 Racket语言欢迎你

依赖于你如何看待它,Racket语言会是:

  • 1、一种编程语言——一种Lisp语言的方言,继承于Scheme;
  • 2、一系列编程语言——如Racket或者其它等等;
  • 3、一系列工具集——用于一系列编程语言的。

当不会出现混乱的地方,我们就用简单的Racket。

Racket的主要工具是包括:

  • 1、racket——核心编译器、解释器和运行时系统;
  • 2、DrRacket——编程环境;
  • 3、raco——一个用于执行Racket命令以安装软件包、建立库等等的命令行工具。

最有可能的是,你想使用DrRacket探索Racket语言,尤其是在开始阶段。如果您愿意,您还可以使用命令行racket解释器和您喜欢的文本编辑器,也可以参见《命令行工具和选择编辑器》(Command-Line Tools and Your Editor of Choice)部分内容。本指南的其余部分介绍了与语言无关的编辑器的选择。

如果你使用DrRacket,就需要选择适当的语言,因为DrRacket可以容纳许多不同的变种如Racket,以及其他语言。如果你以前从未使用DrRacket,启动它,在DrRacket顶上的文本区域输入这一行:

#lang racket

然后单击“运行”(run)按钮的上方的文本区。DrRacket就明白你的意思在Racket执行正常变体的工作(相对于较小的racket/base或许多其他的可能性)。

如果你使用DrRacket之前已经使用了以#lang开始的其它语言,那么DrRacket会记得你上次使用的语言,而不是从#lang推断的语言。在这种情况下,使用"语言|选择语言……"(Language|Choose Language…)菜单项去改变。在出现的对话框中,选择第一项,它告诉DrRacket使用通过#lang申明在源程序中的语言。仍然要把把#lang放在文本区域的顶部的。

1.1 与Racket语言交互

DrRacket底部的文本区和racket的命令行程序(启动时没有选择)作为一种计算器。你打出一个racket的表达式,按下回车键,答案就打印出来了。在Racket的术语里,这种计算器叫做“读取求值打印”(read-eval-print)循环或REPL。

一个数字本身就是一个表达式,而答案就是数字:

> 5
5

REPL:read eval print loop

字符串也是一个求值的表达式。字符串在字符串的开始和结尾使用双引号:

> "Hello, world!"
"Hello, world!"

Racket 中使用双引号定义字符串

Racket使用圆括号包装较大的表达式——几乎任何一种表达式,而不是简单的常数。例如,函数调用被写入:大括号,函数名,参数表达式,闭括号。下面的表达式用参数调用"the boy out of the country",4,and7调用内置函数substring:

> (substring "the boy out of the country" 4 7)
"boy"

1.2 定义和交互

你可以通过使用define表像substring那样定义自己的函数,像这样:

> (define (extract str)
  (substring str 4 7))

> (extract "the boy out of the country")
"boy"
> (extract "the country out of the boy")
"cou"

虽然你可以在REPL求值这个define表,但定义通常是你要保持并今后使用一个程序的一部分。所以,在DrRacket中,你通常会把定义放在顶部的文本区——被称作定义区——随着#lang前缀一起:

#lang racket

(define (extract str)
  (substring str 4 7))

如果调用(extract "the boy")是程序的主要行为的一部分,那么它也将进入定义区域。但如果这只是一个例子,你用来测试extract,那么你会更容易如上面那样离开定义区域,点击“运行”(Run),然后将在REPL中求值(extract "the boy")。

当使用命令行的racket代替DrRacket,你会在一个文件中用你喜欢的编辑器保存上面的文本。如果你将它保存为“extract.rkt”,然后在同一目录开始racket,你会对以下序列求值:

(enter! "extract.rkt")
> (extract "the gal out of the city")
"gal"

enter!表加载代码和开关的求值语境到模块里面,就像DrRacket的运行按钮一样。

1.3 创建可执行文件

如果你的文件(或在DrRacket的定义区域)包含:

#lang racket

(define (extract str)
  (substring str 4 7))

(extract "the cat out of the bag")

那么它是一个在运行时打印“cat” 的完整程序。你可以在DrRacket中运行程序或在racket中使用enter!,但如果程序被保存在‹src-filename›中,你也可以从命令行运行

racket ‹src-filename›

将程序打包为可执行文件,您有几个选项:

  • *在DrRacket,你可以选择Racket|Create Executable...菜单项。
  • *从命令提示符,运行raco exe ‹src-filename›,这里‹src-filename›包含程序。(参见《raco exe: Creating Stand-Alone Executables 》部分获取更多信息。)
  • *在UNIX或Mac OS中,可以通过在文件的开头插入以下行将程序文件转换为可执行脚本:
 #! /usr/bin/env racket

同时,在命令行中用chmod +x ‹filename› 改变文件权限去执行。

只要racket在用户的可执行搜索路径中脚本就会工作。另外,在#!后使用完整路径提交给racket(在#!和路径之间有空格),在这种情况下用户的可执行搜索路径无关紧要。

1.4 给有LISP/Scheme经验的读者的一个说明

如果你已经知道一些关于Scheme或Lisp的东西,你可能会试图这样将

(define (extract str)
  (substring str 4 7))

放入"extract.rktl"并且如下运行racket

> (load "extract.rktl")
> (extract "the dog out")
"dog"

这将起作用,因为racket会模仿传统的Lisp环境,但我们强烈建议不要在模块之外使用load或编写程序。

在模块之外编写定义会导致糟糕的错误消息、差的性能和笨拙的脚本来组合和运行程序。这些问题并不是特别针对racket,它们是传统顶层环境的根本限制,Scheme和Lisp实现在历史上与临时命令行标志、编译器指令和构建工具进行了斗争。模块系统的设计是为了避免这些问题,所以以#lang开始,你会在长期工作中与Racket更愉快。

02 Racket概要

本章提供了一个快速入门的以Racket语言骨架作为背景的指南。有Racket经验的读者可以直接跳到内置数据类型部分。

2.1 简单值

Racket值包括数字、布尔值、字符串和字节字符串。DrRacket和文档示例中(当你在着色状态下阅读文档时),值表达式显示为绿色。

一、数值(Numbers)

数值书写为惯常的方式,包括分数和虚数:

1       3.14
1/2     6.02e+23
1+2i    9999999999999999999999

二、布尔值(Booleans)

布尔值用#t表示真,#f表示假。但是,在条件表达式里,所有的非#f值都被当做真。

在条件表达式里,所有的非#f值都被当做真。---- 所有的非#f值可以看作输入,转换为#t 作为结果。

三、字符串(Strings)

字符串写在双引号("")之间。在一个字符串中,反斜杠(\)是一个转义字符;例如,一个反斜杠之后的双引号为包括文字双引号的字符串。除了一个保留的双引号或反斜杠,任何Unicode字符都可以在字符串常量中出现。

"Hello, world!"
"Benjamin \"Bugsy\" Siegel"
"λx:(μα.α→α).xx"

当一个常量在REPL中被评估,通常它的打印结果与输入的语法相同。在某些情况下,打印格式是输入语法的标准化版本。在文档和DrRacket REPL中,结果打印为蓝色而不是绿色以强调打印结果与输入表达式之间的区别。 比如:

> 1.0000
1.0

> "Bugs \u0022Figaro\u0022 Bunny"
"Bugs \"Figaro\" Bunny"

Racket 用双引号表示字符串,Python中提供了单引号,双引号,三个三引号,三个双引号表示法,提供了更多的灵活性,但可以考虑尽量用双引号。

2.2 简单的定义与表达式

一个程序模块一般被写作:

#lang ‹langname› ‹topform›*

<topform>既是一个<definition>也是一个<expr>。REPL也评估<topform>

在语法规范里,文本使用灰色背景,比如#lang,代表文本。文本与非结束符(像<ID>)之间必须有空格,除了(、)及[、]之前或之后不需要空格。注释以;开始,直至这一行结束,空白也做相同处理。

Racket参考中提供了更多不同的注释形式。

后边遵从如下惯例:*在程序中表示零个或多个前面元素的重复,+表示前一个或多个前面元素的重复,{}组合一个序列作为一个元素的重复。

2.2.1 定义

表的定义:

( define ‹id› ‹expr› )

绑定‹id›‹expr›的结果,而

( define ( ‹id› ‹id›* ) ‹expr›+ )

绑定第一个‹ID›到一个函数(也叫程序),以参数作为命名‹ID›,对函数的实例,该‹expr›是函数的函数体。当函数被调用时,它返回最后一个‹expr›的结果。

例子:

(define pie 3)             ; 定义 pie 为 3

(define (piece str)        ; 定义 piece 为一个有一个参数的函数
  (substring str 0 pie))

> pie
3
> (piece "key lime")
"key"

在封装下,函数定义实际上与非函数定义相同,函数名不需要在函数调用中使用。函数只是另一种类型的值,尽管打印形式必须比数字或字符串的打印形式更不完整。

例子:

> piece
#<procedure:piece>
> substring
#<procedure:substring>

函数定义可以包含函数体的多个表达式。在这种情况下,在调用函数时只返回最后一个表达式的值。其他表达式只对一些边角功能进行求值,比如进行打印。

例子:

(define (bake flavor)
  (printf "pre-heating oven...\n")
  (string-append flavor " pie"))

> (bake "apple")
pre-heating oven...
"apple pie"

对于函数调用, 函数体会依次执行各个表达式,然后返回最后一个表达式的值

Racket程序员更喜欢避免副作用,所以一个定义通常只有一个表达式。这是重要的,但是,了解多个表达式在定义体内是被允许的,因为它解释了为什么以下nobake函数未在其结果中包含它的参数:

(define (nobake flavor)
  string-append flavor "jello")

> (nobake "green")
"jello"

在nobake,没有括号包括string-append给"jello",那么他们是三个单独的表达而不是函数调用表达式。string-append表达式和flavor被求值,但结果没有被使用。相反,该函数的结果是最终的表达式"jello"。

2.2.2 代码缩进

换行和缩进对于解析Racket程序来说并不重要,但大多数Racket程序员使用一套标准的约定来使代码更易读。例如,定义的主体通常在定义的第一行下缩进。标识符是在一个没有额外空格的括号内立即写出来的,而闭括号则从不自己独立一行。

DrRacket会根据标准风格自动缩进,当你输入一个程序或REPL表达式。例如,如果你点击进入后输入(define (greet name),那么DrRacket自动为下一行插入两个空格。如果你改变了代码区域,你可以在DrRacket打Tab选择它,并且DrRacket将重新缩进代码(没有插入任何换行)。象Emacs这样的编辑器提供Racket或Scheme类似的缩进模式。

重新缩进不仅使代码更易于阅读,它还会以你希望的方式给你更多的反馈,象你的括号是否匹配等等。例如,如果在函数的最后一个参数之后省略一个结束括号,则自动缩进在第一个参数下开始下一行,而不是在define关键字下:

(define (halfbake flavor
                  (string-append flavor " creme brulee")))

在这种情况下,缩进有助于突出错误。在其他情况下,在缩进可能是正常的,一个开括号没有匹配的闭括号,racket和DrRacket都在源程序的缩进中提示括号丢失。

Python 的缩进语法无疑是成功的。

2.2.3 标识符

Racket的标识符语法特别自由。不含特殊字符。

 ( ) [ ] { } " , ' ` ; # | \

除了文字,使常数数字序列,几乎任何非空白字符序列形成一个‹ID›。例如,substring是一个标识符。另外,string-append和a+b是标识符,而不是算术表达式。这里还有几个例子:

+
Hfuhruhurr
integer?
pass/fail
john-jacob-jingleheimer-schmidt
a-b-c+1-2-3

2.2.4 函数调用(应用程序)

我们已经看到过许多函数调用,更传统的术语称之为过程应用程序。函数调用的语法是:

( ‹id› ‹expr›* )

‹expr›决定了‹ID›命名函数提供的参数个数。

racket语言预定义了许多函数标识符,比如substring和string-append。下面有更多的例子。

Racket代码例子贯穿整个文档,预定义的名称的使用链接到参考手册。因此,你可以单击标识符来获得关于其使用的详细信息。

> (string-append "rope" "twine" "yarn")  ; append strings
"ropetwineyarn"
> (substring "corduroys" 0 4)            ; extract a substring
"cord"
> (string-length "shoelace")             ; get a string's length
8
> (string? "Ceci n'est pas une string.") ; recognize strings
#t
> (string? 1)
#f
> (sqrt 16)                              ; find a square root
4
> (sqrt -16)
0+4i
> (+ 1 2)                                ; add numbers
3
> (- 2 1)                                ; subtract numbers
1
> (< 2 1)                                ; compare numbers
#f
> (>= 2 1)
#t
> (number? "c'est une number")           ; recognize numbers
#f
> (number? 1)
#t
> (equal? 6 "half dozen")                ; compare anything
#f
> (equal? 6 6)
#t
> (equal? "half dozen" "half dozen")
#t

+,- 等操作符也可以看作函数,函数名在前,函数参数在后,共同组成表达式

2.2.5 条件表达式if、and、or和cond

以下是一个最简单的表达式是if条件:

( if ‹expr› ‹expr› ‹expr› )

第一‹expr›总是被求值。如果它产生一个非#f值,那么第二个‹expr›被求值并作为整个if表达式的结果,否则第三‹expr›被求值并作为结果。

例如:

    > (if (> 2 3)
          "bigger"
          "smaller")

    "smaller"

    (define (reply s)
      (if (equal? "hello" (substring s 0 5))
          "hi!"
          "huh?"))

    > (reply "hello racket")
    "hi!"
    > (reply "λx:(μα.α→α).xx")
    "huh?"

( if ‹expr› ‹expr› ‹expr› ) 实际上就是条件表达式。

复杂的条件可以由嵌套的if表达式构成。例如,当给定非字符串时,可以使reply函数工作:

(define (reply s)
  (if (string? s)
      (if (equal? "hello" (substring s 0 5))
          "hi!"
          "huh?")
      "huh?"))

而不是复制 "huh?"事例,这个函数这样写会更好:

(define (reply s)
  (if (if (string? s)
          (equal? "hello" (substring s 0 5))
          #f)
      "hi!"
      "huh?"))

但是这种嵌套的if很难读。Racket通过and和or表提供了更多的更易读的快捷表示,它可以和任意数量的表达式搭配:

( and ‹expr›* )
( or ‹expr›* )

and表中断情况:当表达式返回#f,它停止并返回#f,否则它会运行。or表遇到一个真的结果时,同样的产生中断情况。

例子:

(define (reply s)
  (if (and (string? s)
           (>= (string-length s) 5)
           (equal? "hello" (substring s 0 5)))
      "hi!"
      "huh?"))

> (reply "hello racket")
"hi!"
> (reply 17)
"huh?"

嵌套if的另一种常见模式是一个序列测试,每个测试都有自己的结果:

(define (reply-more s) (if (equal? "hello" (substring s 0 5)) "hi!" (if (equal? "goodbye" (substring s 0 7)) "bye!" (if (equal? "?" (substring s (- (string-length s) 1))) "I don't know" "huh?"))))

对一个序列的测试的快捷形式是cond表:

( cond {[ ‹expr› ‹expr› ]} )

一个cond表包含了括号之间的一个序列的分句表。在每一个分句表,第一‹expr›是测试表达式。如果它产生真,那么剩下的‹expr›分句表被求值,并在这些分句表的最后一个提供整个cond表达结果,同时其余的分句表被忽略。如果测试‹expr›产生#f,那么分句表剩余的‹expr›被忽视,并继续下一个分句表求值。最后一项分句表可以使用else作为一个#t测试表达式的相同意义。

使用cond,reply-more函数可以更清楚地写成如下形式:

(define (reply-more s) (cond [(equal? "hello" (substring s 0 5)) "hi!"] [(equal? "goodbye" (substring s 0 7)) "bye!"] [(equal? "?" (substring s (- (string-length s) 1))) "I don't know"] [else "huh?"]))

(reply-more "hello racket") "hi!" (reply-more "goodbye cruel world") "bye!" (reply-more "what is your favorite color?") "I don't know" (reply-more "mine is lime green") "huh?"

对于cond从句表的方括号的使用是一种惯例。在Racket中,圆括号和方括号实际上是可互换的,只要(匹配)或[匹配]即可。在一些关键的地方使用方括号可以使Racket代码更易读。

2.2 简单的定义与表达式 2.2.1 定义 2.2.2 代码缩进 2.2.3 标识符 2.2.4 函数调用(应用程序) 2.2.5 条件表达式if、and、or和cond 2.2.6 函数反复调用 2.2.7 匿名函数与lambda 2.2.8 用define、let和let*实现局部绑定 2.3 列表、迭代和递归 2.3.1 预定义列表循环 2.3.2 从头开始列表迭代 2.3.3 尾递归 2.3.4 递归和迭代 2.4 pair、list和Racket的语法 2.4.1 用quote引用pair和symbol 2.4.2 使用引用的缩写(') 2.4.3 列表和Racket的语法

© Licensed under CC BY-NC-SA 4.0

价值投资不能保证我们盈利, 但价值投资给我们提供了通向成功的唯一机会。——巴菲特

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!