在本系列文章中,Opensource.com 通讯员 和其他人一直在用各种编程语言编写相同的“猜数字”游戏。这个练习展示了在大多数编程语言中都能找到的基本概念——变量、表达式和语句——如何应用于学习新的语言。
大多数语言都有其设计支持的“做事方式”,这些方式可能彼此大相径庭。这些方式包括模块化(将相关功能组合在一起)、声明式与命令式、面向对象、低级与高级语法特性等等。
在“猜数字”程序中,计算机会选择一个介于 1 到 100 之间的数字,并要求你猜这个数字。程序循环直到你猜对答案。
在本文中,我将向你展示如何用 Algol 68 编写这个应用程序。我还将尝试练习你在任何编程语言中都能找到的以下概念
- 变量
- 输入
- 输出
- 条件求值
- 循环
用 Algol 68 猜数字
此示例使用了 Algol 68 Genie 编译器,该编译器在许多 Linux 发行版中都可用,由 Marcel Van Der Veer 创建。
好的,我能听到你们中的一些人在抱怨,“哦不,为什么要用 Algol 68?这种语言太老了,而且自从有实现以来就一直无关紧要。” 我尊重你们的意见,真的。真的。但我相信,今天的编程语言中存在许多很酷的东西,这些东西都来自于 Algol 68 设计者们的辛勤思考,这证明了学习一点关于这种语言的知识是值得的。
Algol 68 是静态类型的,而且不是很冗长。Algol 68 Genie 可以一次编译和执行,因此 Algol 68 感觉有点像脚本语言。Genie 实现提供了许多有用的挂钩到我们最喜欢的操作系统中,这使其不会感觉过时,并且意味着可以用它完成相当多的有用的工作。
关于开始时值得提到的一些要点
- Algol 68 的设计原则之一是让任何可以合理地被认为是表达式(一种传递值的结构)的东西都成为合法的代码。
- Algol 68 的“保留字”通常在程序源代码中表示为粗体符号,这对于文本文件来说有点难做到,因此大多数编译器都有一种指示标记是保留字的方法。在 Algol 68 Genie 中,默认是使用所有大写字母;例如,
BEGIN
、IF
、THEN
等。 - 说到
BEGIN … END
、IF … THEN … ELSE
之类的东西,Algol 68 有一个“封闭语法”。一个BEGIN
必须有一个对应的END
,类似于 C 或 Java 中的{ }
;一个IF
必须有一个FI
,一个DO
必须有一个OD
。 - Algol 68 通常将空格视为不相关的——即使在数字或变量名中也是如此。因此,
myvariablename
和my variable name
(甚至myvar iablename
)都指向同一个变量。 - Algol 68 需要在序列中要逐语句求值的语句之间(但不跟随语句)使用“继续符号”——分号。
- Algol 68 包含“字符串”类型,但不提供丰富的字符串处理原语集,这可能有点令人沮丧。
- Algol 68 是命令式的而不是声明式的,也不是面向对象的。
有了这个前言,这是我的“猜数字”实现(带有行号,以便更容易回顾一些具体功能)
1 on logical file end (stand in,
2 (REF FILE f) BOOL: (print(("Goodbye!",new line));stop));
3 first random(42);
4 INT random number = ENTIER (next random * 100.0) + 1;
5 print(("the secret number is",random number,new line));
6 print("guess a number: ");
7 WHILE
8 INT guess = read int;
9 IF guess < random number THEN
10 print("too low, try again: ");
11 TRUE
12 ELIF guess > random number THEN
13 print("too high, try again: ");
14 TRUE
15 ELSE
16 print(("that's right",new line));
17 FALSE
18 FI
19 DO SKIP OD
分解
直接切入主题:第 1 行和第 2 行定义了从控制台输入的输入流中检测到文件结尾时会发生什么。
这种情况由调用过程 on logical file end
并传递两个参数来管理:一个要监视的文件和一个在检测到文件结尾时要调用的过程。你要监视的文件是标准输入,stand in
。第 2 行是过程的定义;这是一个产生过程的表达式。它有一个参数,它是一个指向名为“f”的文件的指针,写为 REF FILE f
。它返回一个布尔值,由 BOOL
指示。
:
之后的文本是过程体,它
- 以一个简短的 begin 符号开始,
(
- 后面跟着调用
print
过程,其中包含两个参数的列表,字符串"Goodbye!"
和new line
过程,该过程将在输出流中发出一个换行符 - 后面跟着一个“继续符号”——分号
- 后面跟着调用过程
stop
- 后面跟着简短的 end 符号,
)
- 后面跟着括号,关闭对“on logical file end”调用的参数列表
- 后面跟着“继续符号”,
;
更详细地说,我可以将此过程定义写成
(REF FILE f) BOOL: BEGIN
print(("Goodbye!",new line));
stop
END
可能值得一提的是,Algol 68 在值和对值的引用之间做了非常严格的区分。Algol 68 值在概念上类似于当今许多流行的编程语言中看到的常量或不可变或最终值。一个值不能被改变。另一方面,对值的引用本质上定义了一个可以存储值的位置,并且该位置的内容可以被更改。这对应于变量,或可变值,或非最终值。
通过具体的例子来说明
INT forty two = 42
定义了一个名为 forty two
的“整数值”,其求值为数字 42
。
以及
INT fink := 42
定义了一个整数变量 fink
,并使用 :=
将值 42
赋值给它。这个表达式实际上是以下内容的简写
REF INT fink = LOC INT;
fink := 42
这使得值 (INT forty two
) 和变量 (REF INT fink
) 之间的对应关系更清晰,但以一定的冗长为代价。LOC INT
东西是一个“局部生成器”——整数值的空间在堆栈上分配。还有一个“堆生成器”,可用于构建跨过程调用持久存在的结构。
呼!回到代码。
第 3 行和第 4 行通过首先使用整数“种子”参数(这有点必须是 42,对吧?)调用“setup”过程 first random()
来初始化系统随机数生成器,然后调用过程 next random
——它不带参数,因此不需要在两者之间没有任何东西的情况下使用括号——并将结果乘以 100 以得到一个介于 0.0 和 99.9999… 之间的结果,使用一元运算符 ENTIER 截断创建的结果以得到一个介于 0 和 99 之间的结果,最后加 1 以得到一个介于 1 和 100 之间的结果。
此时值得一提的是,Algol 68 似乎是第一个支持定义一元和二元运算符的语言,这些运算符与过程不同。你必须将表达式放在括号中
next random * 100.0
因为否则 ENTIER
会绑定到 next random,给出数字 0,而不是整个表达式。
第 5 行是对 print
过程的调用,这里用作调试工具。请注意嵌套的括号;print
接受一个参数,该参数可以是可打印的值或表达式,也可以是可打印的值或表达式的列表。当它是一个列表时,你使用一个“表示法”,它以括号开始和结束。
第 6 行再次使用 print
来提供单个字符串 guess a number:
。
第 7 行到第 20 行是一个 WHILE … DO … OD
循环。这里有一些有趣的事情:首先,所有工作都是作为 WHILE
求值的逻辑表达式的一部分完成的,因此循环体只包含保留字 SKIP
,这意味着“什么都不做”。
第 8 行到第 17 行是两个语句的序列:整数值 guess
的定义,它是通过调用过程 read int
从输入中获取整数获得的,然后是 IF … THEN … ELIF … ELSE … FI
语句。请注意,THEN
、ELIF
和 ELSE
部分都以布尔值 TRUE
或 FALSE
结尾。这导致整个 IF… FI
语句返回 TRUE
或 FALSE
,它作为语句序列中的最后一个语句,“传递”给 WHILE
的值,以确定是否再次循环。
更典型的语言可能具有类似于以下的结构
boolean doAgain = true;
while (doAgain) {
if less then
doAgain = true
else if more then
doAgain = true
else
doAgain = false
}
因为 Algol 68 是面向表达式的,所以你不需要声明变量 doAgain
;你只需将要生成的值合并到 WHILE
部分求值的表达式中即可。
这样做很酷的地方在于,你可以像 C 中的三元运算符一样做事——除了更广泛、更好——使用标准的 IF...FI
do again := IF guess < random number THEN print("something"); TRUE ELIF guess > random number THEN print("something else"); TRUE ELSE print("another thing"); FALSE FI
请注意,我还注意在不必要时不要将任何内容声明为可变值。由于值 guess
仅具有 WHILE
循环的范围,因此它每次都只定义一个新值。
我没有处理的一个恼人的小问题源于 read int
的使用;如果沮丧的用户输入一个无法转换为整数的值,程序将停止并出现错误情况。你可以通过调用过程 on value error
来管理这个问题,这类似于 on logical file end
过程。我把它留给你自己去解决。你不会认为你可以不练习就摆脱困境吧,是吗?
我们学到了什么
在引言中,我列出了本次练习应该探索的编程概念。我做得怎么样?
- 变量: 这表明 Algol 68 将变量视为命名位置,并支持命名(不可变)值。
- 输入: 它使用
stand in
作为预定义的控制台输入,并处理文件结束条件。 - 输出: 它使用
print
在控制台上打印消息。 - 条件求值: 它使用 Algol 68 的
if-then-else-fi
和if
语句作为表达式。 - 循环: 它使用 Algol 68 的
while
循环,包括使用语句序列来计算要测试的值。
它还使用了一些 Algol 68 标准库(Algol 68 称之为“标准前奏”)功能,包括随机数生成器和 I/O 异常测试。
运行程序
$ a68g guess.a68
the secret number is +26
guess a number: 50
too high, try again: 25
too low, try again: 37
too high, try again: 31
too high, try again: 28
too high, try again: 26
that's right
$
我没有涵盖的一件事是注释。在 Algol 68 Genie 中,注释可以以符号 COMMENT
、CO
或 #
开始和结束,例如
# this is a comment #
如果你有兴趣探索 Algol 68,请查看 Marcel 的网站 或 Rosetta Code 上 Algol 68 的许多贡献解决方案。
最后,回到“死语言”这个问题。是的,这有点深奥。但是学习晦涩难懂的语言是欣赏我们已经走了多远(或者,在某些情况下,没有走多远)以及对我们习以为常的语言特性给出更全面的视角的好方法。
评论已关闭。