« 采访 Lua 发明人的一篇文章 | 返回首页 | 把 vfs 实现好了 »

有关 Forth

今天晚上继续读 《Masterminds of Programming》,忍不住又翻译了半章关于 Forth 之父的访谈。我以前读过几篇更早时期关于他的访谈,部分了解他的观点。小时候还特别迷 Forth 。这位神叨叨的老头很有意思。

没看过原来的译本,只是自己按自己的理解翻了第 4 章 Forth 的前一半。我也算对 Forth 很有爱的人吧,也还了解 Forth 里诸如 ITC (Indirected-threaded code) 这种术语到底指的什么,不过还是觉得翻译有点吃力。

对 Forth 同样有爱的同学们姑且看之吧。


Forth 语言及语言的设计

你怎样定义 Forth

Chunk: Forth 是一门计算机编程语言,其语法规模简到极致。把参数栈专门独立出来是语言的一大特色,这使得子程序调用非常高效。因此,语言采用后缀表达式(先列参数,再跟操作符),并鼓励把程序高度细分成众多短小的子程序的风格。这些子程序共享栈上参数。

我曾经读过关于 Forth 这个名字的介绍,据说是象征着第四代软件开发。你可以给我们多介绍一些吗?

Chunk: Forth 源于 "Fourth (第四)" 这个词。暗指 “第四代计算机语言”。据我回忆,我跳过了一代。FORTRAN/COBOL 是第一代;Algol/Lisp 是第二代。这些语言都强调语法。语法越详尽,越能检查出错误。但大部分错误是语法错误。我决定将语法元素减到最小,而强调其语义。被加载的 Forth 词就真正的表达其含义。

你把 Forth 当成一个语言工具集。我这样理解这个视角:给出相对其它语言更简单的语法,提供比其它语言所用的更短的词构成一个词汇表的能力。我还漏掉了什么吗?

Chunk Moore: 就是这样。其中关键一点是,我们尽其可能的分解。一段 Forth 程序有大量的小词构成。相对来看,同样的 C 程序用到的词则少的多,而且每个词都要大的多。

我所谓短小的词,是指可以用一行源代码定义出来的词。你可以用刚定义出来的词来定义下一个新词,如此层叠,最终得到数千个词的定义,最终形成了语言。这里面临的挑战是:1) 判断哪些词是有用的。2) 记住所有定义出来的词。我现在正在编写的应用程序有上千个词。所以我写了个工具来搜索这些词,不过你必须先记得有这个词,大概怎么拼才找得出来。

好了,现在有了种完全不同的编程风格,程序员需要一点时间才能适应。我已经看过太多 Forth 程序就像是从 C 程序直译过来的。这样做可不对。正确的做法是从零开始。关于这个工具集还有一个有趣的地方,只要你好好做,定义出来的新词和内核中预定义的词并无不同。它们一样高效,对外的意义也相同。定义你自己词是没有额外开销的。

外部结构看起来是由许多小词组成,这一点是源于 Forth 的实现吗?

Chunk: 这是我们非常高效的子程序调用序列的结果。这里面没有参数传递,因为语言是基于栈的。所有的一切仅仅只是调用和返回。而栈暴露在外。让机器识别的语言编译在一起,进入和退出字程序只需要逐字翻译成一条 call 指令和一条 return 指令。你总可以进一步的达到汇编语言层面,得到等价的指令。你可以定义一个词刚好执行一条真正的机器指令,而不是做一次子程序调用,这样,它可以匹敌任何其它语言的效率,甚至比一些语言更高效。

这样就没有 C 调用的消耗

Chunk: 没错。这赋予了程序员巨大的灵活性。如果你能聪明的分解问题,你不仅仅只是高效的完整任务,还使得完成的过程变得格外易读。

另一方面,如果你做的很糟糕,你最终的代码世界上只有你一个人能解读——连你那全知全晓的经理都看不懂你写的代码。你可以写出天书。所以,这是把双刃剑。你能做的完美无暇,也能弄得污七八糟。

你能说点什么(或展示点代码)让用别的编程语言的开发人员对 Forth 一见钟情吗?

Chunk: 让有经验的程序员对 Forth 感兴趣挺难的。这是因为他已经为他正在使用的语言/操作系统相关的工具学习做了大量的投资。为他的应用程序积累了许多。即使是告诉他们,Forth 会更小,更块,更简单,也及不上把所有东西都重写的代价。一个新手程序员,或是一个没这么干扰的需要写点代码的工程师可能更容易接受一点。嗯,或是有某个有经验的程序员要开个新项目,而这个项目有些新的约束条件,比如我现在在做的多核芯片上做开发。

你提到你见过的大量 Forth 程序看起来像 C 程序。那么你自己怎么设计一个更好的 Forth 程序的?

Chunk: 由底至上。

首先,你估计会有一些 I/O 信号要去产生,好吧,就来产生它们。这样你就写一些代码控制这些信号的产生过程。然后你一点点来,知道最终构建出高层的词。假定你把它起了个名字叫 go ,接下来你敲一下 go ,一切如期发生。

我不信任由顶向下的系统分析方法。他们判断问题是什么,然后再分解,分解出来却很难实现。

领域驱动设计建议以客户的词汇来描述商务逻辑。在创建词库和使用你问题域的术语之间有什么联系?

Chunk: 最好是程序员在开始写代码前了解那个领域。我会和客户沟通。我会倾听他用的词汇,并试着用这些词,这样他也能明白程序在做什么。Forth 因其采用后缀记号法使代码费用易读。

如果我写一个经济有关的应用程序,我可能会用一个叫作 "percent" 词。你可以在代码中写 "2.03 percent" 。这里 percent 的参数就是 2.03 ,一切都和看起来那样自然。

一个从穿孔卡片计算机时代开始的项目居然到了互联网时代的现代计算机上还这么有用。Forth 于 1968 年在 IBM 1130 上设计并运用。它到了 2007 年继续为并行处理所用,这真奇妙。

Chunk: 其间 Forth 也进化了。不过 Forth 可能是最为简洁的计算机语言。它没给程序员附加任何约束。他(她)可以瘦的层次化方式定义一些词精确切合问题的原貌。

你在设计程序时,是否把让程序读起来像英文一样做为一个目标?

Chunk: 在非常高的层次上看,是这样。但英文并非功能性描述的好语言。英语不是设计来干这个的,不过英语和 Forth 有个共同的特性,就是你能定义新词。

你用以前定义好的许多词通过解释的方式来定义新的词汇。对于自然语言来说,有可能不严谨。如果你查字典的话,会发现有些是循环定义,你查不到本质内容。

是不是把注意力转移到一堆词上面,比 C 里出现的那些各种括号来说,Forth 因此能写出更好味道的程序?

Chunk: 希望如此。这使得 Forth 程序员去关注事物的外貌,而不仅仅是其功能。如果你能组织起一系列的词,把它们有序的排列起来,会感觉很好。这正是为什么我开发了 colorForth 。我曾为 Forth 里老的语法烦恼。比如,按现在的方法,你要做注释,就必须用一左括号和一右括号来括起来表示。

我看着所有标点符号说,“好吧,可能还有更好的方式。” 这更好的方式最麻烦的一点是,源代码中每一个词都需要附加一个标签。如果我能忍受这点开销,所有的符号都另人舒服的消失了,取而代之的是,每个词都有了颜色。对我来说,这是个优雅的表达其功能的方式。

我遭到了色盲群体的抨击。他们对我试图把他们排出程序员行列的做法义愤填膺。不过某人最后想了个招,用字体的区分代替颜色的区分,这也是个不错的方法。

关键点在于,每个词有个四位的标签,这能区分出 16 种事物。编译器可以立刻感知它要做什么,而不用从上下文去推断。

第二代和第三代语言都皈依了极简主义,举例来说就是实现了 meta-circular bootstrapping (圆环自举,靠自身把自身运作起来)。Forth 是在对语言的概念定义以及硬件需求量方面的极简主义的最好的例子。这是当年的时代特征或是你做出的跨时代的创举吗?

Chunk: 非也。当时再三考虑的设计目标是尽可能的做一个最小的内核。只预定义最为必要的几个词,然后让程序员再去添加他觉得合适的。主要因素是可移植性。在那个时代,大打的小型计算机,接着又是一大坨微型计算机。而我必须把 Forth 弄到如此之多的机器上去。

我想干这点事能尽量轻松点。我要干的就是弄出一个百来个词的内核,以此能够组成一个,我叫作操作系统,但其实不完全是操作系统的东西,这个东西再给出几百个词为人所用。接下来你就能在这上面做开发了。

我来提供做前两个阶段的工作,让程序员去做第三个。我也经常做应用程序开发程序员。定义我知道的词总是很有必要。头一百个词可能用机器语言或汇编语言定义,至少是直接和特定平台打交道。第二和第三百个词可以是高层次的词,在较低层次最小化机器依赖性。接下来,应用程序就能最大限度的做到机器无关了,这样很容易把程序从一台机器移植到另一台上。

当初你能在第二阶段之上方便的移植吗?

Chunk: 绝对如此。比如我有个文本编辑器,用来编辑源代码的。它总是在各种机器上不需要修改任何地方都能用。

坊间流传着一个传说,每次你看到一台新机器,你就立刻动手把 Forth 移植到上面。是说的这个吗?

Chunk: 没错。实际上对于理解一台机器如何工作;了解那些可以用来更容易实现 Forth 标准包里词的诡异机器特性;这是条最简单的途径。

你是怎样发明 indirect-threaded code 的?

Chunk: 代码是一个很微妙的概念。每个 Forth 词在字典里有一个入口。对于基于直接线索的编码(direct-threaded code ,有译为直接串线编码),遇到引用每个词的位置直接指向要执行的代码。而基于间接线索的编码(indirect-threaded code 有译为间接串线编码) 则指明一个包含了代码所在地址的位置。这使得地址之外的信息能被访问到——比如,一个变量的值。

这可能是最为紧凑的词组织方法了。它可以等价于基于直接线索的编码和基于子程序的编码(subroutine-threaded code ,即类似 C 语言编译成的那种直接调用子程序方式)。当然这个概念和术语在 1970 年时还没有。但对我来说,这是实现各种各样词的最自然方式。

Forth 会如何影响计算机系统的未来走向吗?

Chunk: 这已经在发生了。我在微处理器优化方面干了 25 年,最新近的一个多核芯片的核心是 Forth 计算机。

Forth 提供了些啥?作为一个简单的语言,它使得这样一台简单的计算机:有 256 个字的本地内存;两个下压栈;32 条指令;异步操作;易于和相邻机器通讯;可以很小,且功耗极低。

Forth 鼓励被高度分解的程序。这非常适合在多核芯片上做并行处理。大量小程序鼓励你每一个都深思熟虑的设计。这样最终你可能只需要写 1% 的代码就够了。

只要我听到有人吹嘘代码达到了上百万行,我就知道他们肯定前面理解错问题了。当代没啥问题需要写几百万行代码。要么是程序员太粗心、要么项目经理太混蛋、要么就是为兼容一些不存在的需求。

使用 Forth 对需要小计算机编程是个巨牛叉的策略。别的语言都提供不了相当的模块化能力和扩展性。尤其计算机越来越小,它们之间必须做网络化协作(智能微尘?),这就是未来的环境。

这听起来像 Unix 的重要原则之一:以许多程序,每个只做一件事,相互作用。这依旧是当今最好的设计吗?在一台机器上跑多个程序会被通过网络运行的多个程序取代吗?

Chunk: 让代码跑在多个线程上的这个概念,被 Unix 和别的操作系统所实现。这是并行化处理的先驱。但这里有些重要的差别。

大的计算机承担得起多线程通常会要付出的一些代价。最终弄出个庞大的操作系统。对于并行化处理来说,永远都是计算机越多越好。

在资源一定的情况下,更多的计算机意味着更小的计算机。但很小的计算机是承担不起在大计算机上承担的代价的。

小的计算机的网络化会发生在芯片之上,通过 RF 连接的芯片之间。小的计算机内存也小。操作系统无处容身。计算机必须自治,自己要有能力保持通讯。因此通讯环节必须简单——没有那些煞费苦心的协议。软件也必须紧凑高效。最为理想的应用程序就是 Forth 了。

那些需要数百万行代码筑就的系统将会淡出历史舞台,是它们造就了巨大的中央计算机。分布式技术需要条不同的思路。

一门语言若是设计成支持繁杂的,拘泥于语法条条框框的代码,就会鼓励程序员写出巨大的程序。并以此自得,洋洋得意。没什么压力去寻找紧凑的方案。

以繁杂句法定义出来的语言生成的代码也可以很小,但通常做不到。以语法默示的实现流程导致了笨拙而不那么高效的目标代码。这对于小的计算机来说并不合适。一门良好设计的语言在源码和目标码之间存在一对一的联系。这向程序员昭显了源码如何生成为最终代码。注重性能、减少对文档的需求,使得程序员感到满足。

Forth 设计成对于源代码和二进制输入的目标代码皆很紧凑,这也是嵌入式开发中广泛使用的原因。不过在许多其它领域程序员总有别的理由去用别的语言。是不是说这些语言的设计的某些方面只是增加了原代码或是目标码的开支吗?

Chunk: Forth 的确很紧凑。一个因素在于他的语法量很小。

其它语言看起来是故意的增加一些语法,弄出点冗余,可以帮助语法检查以及错误检测。

Forth 没提供啥机会用来做错误检查,因为它没有冗余信息。这也使得源代码非常紧凑。

我感觉其它语言几乎所有的错误都出在语法上。设计者看起来为程序员犯下编译器就都能找出来的错误创造了条件。这没啥经济价值。这不是为写出正确的代码自找麻烦吗。

比如说类型检查吧。不同的数字类型之间的赋值错误会被侦测到。无意中带来的后果是程序员必须自己来转换类型,有时就是想回避类型检查而得到他们真正想干的事情。

语法导致的另一结果是它必须适应所有的应用程序的意图。这就会越来越复杂。Forth 是一个可扩展的语言。程序员可以创建一些别的语言只能通过编译器的改进才能获得同样性能的结构。而所有的能力不需要一开始就想好提供出来。

Forth 的特征之一是使用后缀操作符。这能简化编译器,从源代码到目标代码给出一对一的关系。程序员对他写的代码的充分理解能增加代码编译后的紧凑程度。

**许多最近的计算机语言(尤其是 Python 和 Ruby)都把可读性引为其关键好处。Forth 在这方面与之相较可以从中学到并保持些什么?Forth 能在可读性方面的定义方面传授给其它语言些什么东西?

Chunk: 计算机语言都宣称要可读。但他们并不可读。或许对懂这门语言的人来说是可读的。但初学者还是稀里糊涂的。

问题就在于晦涩、武断、隐秘的语法中。那些小括号啦,& 符啦,等等。你试着学习它们为啥出现在那里,最终推断,其实没什么好理由。但是你还是要按规则办事。

你无法说这门语言。你必须像 Victor Borgia 那样叽里呱啦的把符号都念出来。

Forth 通过最小化语法来减轻这个问题。它用的哪些个神秘符号,@ 和 ! 可以读成“取”和“存”。这些个使用符号是因为出现的太频繁了。

程序员被鼓励使用自然语言中的词汇。它们不通过标点的间隔组织在一起。若是你选好了词,你就能构造出有意义的句子。实际上有人用 Forth 来写诗。

另一个优势是后缀表示。一个像 “6 英寸”这样的短语能把操作符“英寸”作用于参数 6,这是非常自然的表达方法。非常的可读。

另一方面,程序员的任务就是开发吃一组词来描述问题。这个词典会变得非常大。读者需要了解整个词典使得程序可读。程序员就必须好好的定义词。

总而言之,用任何语言,这些都会影响程序的阅读。

你如何定义你的工作中如何定义成功?

Chunk: 一个优雅的解决方案。

人们并没有用 Forth 编程。Forth 就是一个程序。他们添加新的词创建一个词典来定义问题。当正确的词被定义出来,一切都浑然天成。接下来你就能你可以互动地解决所有相关问题的任何方面。

举个例子:我可以定义一些词来描述一个电路。我以后想把这个电路加到一块芯片里去,显示电路的布局,校验设置规则,模拟跑一下。用来干这些事情的词决定了应用程序的形态。如果精心选择这些词,提供一个紧凑而有效的工具集,然后我就搞定了。

你在哪学会写编译器的?在那个年代每个人都必须去写编译器吗?

Chunk: 哦,我在六十年代去了趟 Stanford ,那里有组研究生正在写一个 ALGOL 编译器——Burroughs 5500 用的版本。当时我想他们不过才三四个人,就三四个人坐在那写一个编译器,我那个内牛满面啊。

我想,“靠,他们要能做,我也能做。”然后我就干了。其实一点也不难。当年写编译器还是有点神秘西西的。

现在其实也还是神秘西西的

Chunk: 嗯,不过已经没那么神秘了。你看现在新语言一个个的冒出来,我不知道算解释型的还是编译型的,不管怎样,有黑客风范的人都想做一个。

操作系统是另一个稀奇古怪的东西。操作系统吓人的复杂,而且完全没用。它是 Bill Gates 成功向世界推销出去的一个光彩夺目的概念。这可能是世界上出现的最大的骗局。

操作系统对你来说做着绝对的无用功。其实你有这样一些东西就够了:一个叫作磁盘驱动程序的子程序,一个叫什么什么通讯支持的子程序,而在现代社会,操作系统啥也没做。实际上,Windows 花了大量时间在包装层上,或是诸如磁盘管理器这样不相干的东西上。你有了上 G 的磁盘,有了上 M 的内存。世界格局发生了变化,使得操作系统不那么有用了。

那设备支持怎么办?

Chunk: 你对每个设备有一个子程序。那是一个库,而不是操作系统。你需要那个就装载哪个。

在工作间隙后,你怎么继续编程?

Chunk: 在被困扰的时候,我不会中断我的编码。我会充满热情的思考问题,做梦都会想着它们。我想这是一个 Forth 的特质:在小段时间(以天计)内全神贯注的解决一个问题。这帮助 Forth 应用程序自然的被分解为一个个子项目。几乎所有的 Forth 代码都简单易读。当我真的要做一些偏激的事情,我会做好注释。好的注释能帮我以后回到问题中,不过重新阅读和理解代码总还是有必要的。

你在设计或编程中犯过最大的错误是什么?你从中学到点什么?

Chunk: 20 多年前,我想为设计 VLSI 芯片开发个工具。我的新电脑上没有 Forth ,因此我就想用另种方案,写机器语言。不是汇编,就是用 16 进制码敲机器指令。

我像我写 Forth 程序那样编写代码,分层次的定义出需要简单的相互有关的词。最终搞定了。我用了这个东西 10 年。但是无法维护,也没有文档。最终,我用 Forth 重写了一遍,这个玩意变得更为小巧而且简单多了。

我的结论是,Forth 比机器语言更高效。一部分源于其交互性,另一部分是因为它的语法。Forth 的一个很漂亮的方面是,数字可以用计算它们的表达式文档化的表达。

Comments

2010年毕业后,我就接触了forth语语言,2012年后开始用C。真心感觉forth就是鸦片,一旦使用一生相伴。forth真心是个牛逼的胶水语言,牛逼到可以把不同计算机胶水起来。不过forth标准在多任务上有待说明

最近看到一个8th语言,挺不错
(8th-dev.com),它是基于forth的。

本人早在80年代就使用forth,当时是fig-83标准。
文章无论翻译的如何好,均不如自己亲自用forth写个小程序的感受更确切。forth的特点不太容易用语言表达,可谓“妙不可言”。
现在在windows上比较好用的是Win32Forth。支持多线程和对象编程,非常好用。
有一句老话或许能在某种程度上可以描述一下Forth:如果C语言让你感觉它是最好的程序设计语言,那么forth会让你感觉你自己是最好的程序设计师。
至于forth程序的运行速度是否比C更快,取决于程序设计师自己。因为设计师可以直接使用汇编提高自己的关键程序段。

云风没翻译的后半部分(硬件)

Visitor:人们是怎么看待他们进行开发工作的硬件平台的,是一种资源还是一种限制?如果你认为它是一种资源,你是否会想要优化这些代码并压榨每个硬件的极限?如果你认为它是一种限制,你是否会在写代码的时候怀着一个想法,你的代码将来会运行在一个更强大的新硬件上?这不是一个假设,因为现在的硬件更新换代非常快。

Chuck:一个非常敏锐的观察,软件必然与它的硬件互相匹配。PC上的软件都在期盼更快更强大的电脑,但是对于嵌入式系统来说,软件更期望硬件系统能在项目周期里保持稳定。并且不是所有的软件都需要从一个项目转移到另一个项目。所以这里的硬件是一种强制性的约束,但并不意味着是一种限制。但是,对于PC来说,硬件是一种将会增长的资源。而并行处理的出现改变了这一想法。应用程序们无法像对付单个电脑那样压榨多个电脑的资源极限,这使得它们无法跑得更快。重写历史遗留下来的软件来适应并行处理的办法显然是不现实的,期待某天出现更聪明的编译器能拯救这一切,反倒是更有可能。

Visitor:现存问题的根源是什么?

Chuck:是速度!在一个应用程序中,电脑必须做很多事,这些事可以被单个处理器以多任务的方式完成,也可以同时被多个处理器完成。后者更快,而且同时期的软件需要那种速度。

Visitor:解决方案是在硬件上,还是在软件上,又或是两者兼有?

Chuck:把多个处理器粘合到一起没什么难度的,所以硬件上的问题已经解决了。如果软件能够用硬件这种方式来编程,那么问题就解决了。但是话又说回来了,如果软件能以这样的方式重写一遍,那软件就足够高效,也就不需要使用多处理器了。现在的问题是,要用多处理器来运行未经修改的历史遗留软件。这需要一种仅在传说中出现过的非常聪明的编译器。

让我更吃惊的是,70年代写的软件现在无法被重写。一个可能的原因是,在那些日子里,软件是令人激动的,所有的编程工作在第一时间就被完成了,程序员们怀着极大的乐趣每天工作18个小时。但是现在,编程只是一个朝九晚五的工作,就像日程表上的团队任务的一部分,没什么乐趣可言。所以他们加了另一个软件层来避免重写旧的软件。这至少比重写一个愚蠢的文字处理器更有乐趣。

Visitor:在普通的计算机上,我们已经拥有了强大的计算能力,但是在这些系统上,到底做过多少有实际价值的计算?它们又做过多少移动、格式化数据的事?

Chuck:说的没错。大多数计算机只是在移动数据,并不是在计算。不仅仅是移动数据,还有压缩,xx,加密(encrypting, scrambling.)。At high data rates, this must be
done with circuitry so one wonders why a computer is needed at all.

Visitor:我们能从中学到什么?我们应该用另一种不同的方法制造硬件?
Don Knuth曾经发出过一个质疑:在一秒钟的时间里,计算机内部到底发生过什么?他说,我们所发现的,能改变很多事。

Chuck:我的计算机芯片认识到了这一点,在它拥有一个简单但很慢的乘法运算的时候,它并不常用。在内核之间传递数据和存取内存才是最主要的操作。

Visitor:一方面,你有一种能使人们开发他们自己的词汇表,并且不需考虑硬件问题的语言;另一方面,你有一个非常小的、能紧密连系硬件的内核。一个很有意思的问题是,forth是怎么连接这两者之间的断层的?在某些机器上,在你和你的forth内核之间真的没有用操作系统连系起来?

Chuck:没有,forth是真正独立的,这个内核已经包含了所有必需的东西。

Visitor:但是它为使用forth编程的人抽象了硬件。

Chuck:是的。

Visitor:LISP机也做过类似的事,但是并没有变得流行起来。forth倒是不声不响的完成了这个工作。

Chuck:不过,LISP并没有包含I/O。事实上,C也没有包含I/O。正因为它没有包含I/O,所以它才需要一个操作系统。而forth在一开始的时候就包含了I/O。我不相信那些大多数普遍的情况,当你面对一个新型的机器时,你需要这个新机器的唯一原因就是,你需要它的特别之处。而你要做的,就是利用它的这些特性,你需要工作在输入输出这个底层层面上,而且你也能够做到这点。

Visitor:Kernighan和Ritchie曾经为C而争论过是否需要一个最小的公因子使porting变得更容易,而你已经找到更容易的Port方式了。

Chuck:我有几种标准的方式去做那些事。我有一个词——我先假设它已经存在了——它能从一个Port上取得8个bit。它在不同的计算机上会用不同的方法被定义,但是它在堆栈上应该拥有同样的功能。

Visitor:也就是说,forth相当于C加上标准I/O库?

Chuck:嗯,但是在早期我是用标准FORTRAN库工作的,它糟糕得可怕。它只有错误的词,而且极其昂贵和笨拙。它很容易就能定义出一大堆执行I/O操作的指令,而且还是你不需要的那种排在前头的预定义协议(protocol)。

Visitor:你觉得你自己在这种情况下浪费了很多不必要的时间?

Chuck:在FORTRAN里,是这样。当你在处理Windows的时候,这里没什么你能做的,它们不会让你控制I/O的,所以我远离了Windows。即便如此,即便没有Windows,对forth来说,奔腾处理器仍然是最复杂难用的机器。它有过多的指令,还有过多的硬件特性,例如the lookaside buffers and the different kinds of caching这些你完全无法忽视的东西。你只能用自己的方式走自己的路,而且为了使forth在它上面跑起来,必要的初始化代码也是最困难最笨拙的。即使它只需要被执行一次,我仍然要花费很多的时间试着弄明白怎样才能使它正确的运作。我们曾经在奔腾的芯片上独立运行过forth,所以我们明白这有多么麻烦。这个过程延续了差不多10年,也在某种程度上去适应Intel在硬件上作出的变更。

Visitor:你提到forth能支持异步操作,你能解释一下什么是异步操作吗?

Chuck:好吧,这里有几种解释。forth总是有着多重编程,多重读取的叫做协作的能力。我们有一个词叫pause,如果你有一个任务,并且它运行到了一个无事可做的阶段,它会说pause,然后调度程序会分配资源给循环中的下一个任务。如果你不说pause,你会独占整个计算机,但事实上绝不会出现那种情况,因为这是一个专一(dedicated )的计算机。它在运行单个应用程序的时候,其他的任务也是友好的。我猜在那些古老的日子里,所有的任务都应该是友好的。这就是一种异步,这些任务都可以运行,做它们自己的事,而不需要同步。

forth的其中一个特性是,那个名叫pause的词能被隐藏在底层词中。每一次你试着读写磁盘时,这个pause将会为你执行,因为磁盘组知道它不得不等待这个操作的结束。

在新的芯片里,我开发的多核芯片有着同样的哲学。每个计算机都是独立运行的,当你的计算机上有一个任务,而旁边的计算机有另一个任务时,他们在同一时刻运行,但却能彼此交流。这就相当于一个多线程计算机上的任务所做的事。

forth只是非常好的因子化这些独立的任务。事实上,在多核计算机里,我使用并不是完全一样的程序,但是我能用同样的方式因子化这些程序,使它们能并行跑起来。

Visitor:这些联合起来的多线程中,是否每一个线程都有它自己的堆栈?是你给它们分配的吗?

Chuck:当你在做一个任务转换时,有时候你所需要做的全部事情,只是把词保存在栈顶,然后修改堆栈指针。有时候你不得不把堆栈转移出去,并加载一个新堆栈,但是如果出现那种情况,我会给它一个深度非常浅的堆栈

Visitor:你故意限定了堆栈的深度?

Chuck:是的。最初,堆栈的深度相当随意。我设计的第一个芯片有一个256个深度的堆栈,因为当时我认为这个堆栈已经很小了。我设计的其中一个堆栈只有4个深度。现在我选定8或10作为一个好的堆栈深度。so my minimalism has gotten stricter over time.

Visitor:我将期待它走向另一条路。

Chuck:不过,在我的VLSI设计应用中,我专为递归设计了一种芯片,我不得不设置了大约4000的堆栈深度。为了做到这一点,需要一种完全不同的堆栈,一种用软件实现的堆栈。但是,在奔腾芯片上,它能成为一个硬件堆栈。


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

应用程序设计

Visitor:你带来了一个观念,forth是针对例如微型计算机网络协作(智能微尘)的理想语言。你认为哪种应用程序适合那些微型计算机?

Chuck:当然是需要互相交流的程序。但是我才刚刚开始学习怎么让独立的计算机群能联合完成一个更大的任务。我们设计的多核电脑实在是很小,它们只有64个词长的内存。好吧,为了显得它们更不一般,它们拥有128个词长的内存:64 RAM,64 ROM。每个词都能支持4个指令,在一个给定的计算机里只有512个指令,所以任务变得相当的简单。现在你拿来一个任务,类似于TCP/IP,堆栈并因子化这个任务,把它分给若干个计算机,用这种方式,你能用不超过512个指令的计算机执行这些操作。这是一个美妙的设计问题,也是我目前正在使用的工作方式。

我认为这就是几乎所有应用程序的真实写照。当一个应用程序被分解成独立的片段时,它就变得相当容易编写,即使是运行在单个的连续执行的处理器上。就像视频画面通常做的那种分解成帧的方式一样。同样,我也认为这是压缩和解压缩图像的真实写照。但是我才刚刚开始学习怎样去做这些。

Visitor:你是否试过其他一些并不适合它的领域?

Chuck:当然是历史遗留软件。我被那些个遗留软件纠结过,但是我认为还不如重新规划需求,这种方式更加自然,更接近我们大脑思考的方式。我其中一个动机就是想有一个小内核。我的想法是,意识产生于它们的交流,而不是它们中任何一个的操作。

遗留软件是一个不受重视但很严重的问题,它只会变得越来越糟,不仅仅在银行,还在航空和其他技术界。这个问题就是几百万行的代码,而这些代码能被forth用几千行代码重写。不要考虑用聪明的机器去翻译这些代码,那只会制造出更庞大的代码。而且没有办法能使那些代码有效的运作,这些代价和冒险是相当可怕的。遗留代码有可能成为我们文明衰落的原因。

Visitor:这听起来像是你在打赌未来的10-20年里,我们将看到越来越多的软件产生于代码之间的散漫衔接。

Chuck:嗯,是的。我很确定会出现这种情况。RF交流相当不错,他们谈论你身体里的微小因子,那些对事物的确定、对事物的感知,并且这些因子能通过RF(或者声音)进行交流。它们做不了太多事,它们只是一些分子,所以这能显示这个世界是怎么运行的。它也是我们人类社会组织起来的方式。我们有60多亿独立的因子在这里协作。

Visitor:选择贫乏的词汇将导致糟糕的设计和缺乏可维护性的程序。用数百个词编写一个大型应用程序会不会导致出现某些不被外人理解的隐语?你是怎么避免这个问题的?

Chuck:我是记不住那些词的,如果你想去记住它们,你会把自己的脑子记到打结。在一个程序里,我有这个词——现在我已经忘了它叫什么了——但是我曾经定义并且修改过它,而它最终的意思是“相反的”。就像你有一个叫right的词,但是它却向左走,那将是极其可怕的混乱。我做了一番心里斗争,最后还是重命名了这个词,否则它会成为程序中干扰你认知的不可理解的部分。

我喜欢使用完整的英文单词,而不是缩写。我喜欢把它们拼出来。另一方面,我也希望它们足够短小。不久你就能用完短小又含义丰富的英文单词,然后你就该去做点别的事了。我讨厌用前缀的方式命名——这是一种无脑的方式,并且它试图创造出让你可以不断重复使用完全相同的老名字的名字空间。在我看来,这是一种逃避。这是一种辨别词名的简单方法,但你能够有更加聪明的办法。非常多的forth程序都有清晰的词汇表,你能很容易的重用这些词。在这个语境里,这个词做了这个;在那个语境里,它又做了别的。但在我的VLSI设计里,以上这些全都不适用了。我需要至少上千个词,并且它们还不是英文单词,它们都是信号名字或别的什么。我不得不完全改变我以往的做法。现在它变得完全不可读了。但是在另一方面,它里面充满了类似nand、nor、xor之类的逻辑门。尽管如此,我还是在使用这些词。

现在,我看到其他人在使用forth,我也不想自命是唯一的forth程序员。
他们其中一些人能很好的给事物命名,但另一些人却做的很糟。
一些人能写出很易读的句子,而另一些人认为这样做完全没必要。
一些人能定义出很短的词,而另一些人却能定义出一个占据了满满一页的词。
forth没有规则的限制,但却有风格的选择。

forth与 C 、Prolog、ALGOL、FORTRAN的不同点在于,这些常规语言总是期望着要把所有可能的结构和语法在一开始就构造出来。这就产生了一些非常笨拙的语言。我认为C就是一个包含了括号、braces、冒号、分号以及所有一切的笨拙语言。而forth排除了这些不必要的东西。

我没有解决这个一般性的问题,我只是提供了一个工具,其他人可以用它解决他们遇到的任何问题。它拥有完成任何一件事的能力,但没有完成所有事的能力

Visitor:微处理器应该包含源代码吗,那样一来它们甚至能在十年之后保持稳定?

Chuck:没错,在微电脑里包含源代码的这种做法能使它们很好的文档化。forth是紧凑的,所以才能使这种做法得以实现。但下一步是包含编译器和编辑器,那样一来微电脑的代码在不需要另一台电脑或操作系统的情况下,就能被检查和修改,否则时间长了就有丢失源代码的可能。colorForth是我这种做法的一个尝试。全部所需的只是以K为单位的源代码,这样就能很容易的储存在闪存上,它将会在久远的未来里发挥它的作用。

Visitor:语言的设计和用这种语言编写的软件在设计上有什么连系吗?

Chuck:一种语言就决定了它的使用方式。人类的语言真实的证明了这一点。看看罗曼语(法语,意大利语)、西方语言(英语,德语,俄语)和东方语言(阿拉伯语,汉语)的区别,这些语言影响了他们的文化和世界观。语言的种类影响了说什么和怎么说。其中,英语显得尤其简洁并且变得流行。

人机语言也是如此,第一批语言(COBOL, FORTRAN) 太verbose,之后的语言(Algol, C)有着非常严格的语法。这些语言必然会导致庞大、笨拙的描述法则。它们能表达任何事物,但是做的很糟糕。

forth解决了这些问题,它有着自由的语法,它鼓励紧凑、高效的描述,最小化了对注释的需求,而注释会导致不正确的描述,并分散对代码本身的注意力。

forth还有一个简单高效的子程序调用方式。在C语言里子程序调用需要昂贵的建立和恢复,这就阻碍了子程序的广泛使用,并且鼓励了制定详尽的参数来分摊调用的花费,但这会导致庞大复杂的子程序。高效的子程序调用允许forth程序能够被高度因子化分解成许多小的子程序,而forth程序本身也确实是这样做的。我个人的风格是一行长度的定义,数百个简短的子程序。在这种情况下,代码里名字的命名就显得非常重要了,就像 a mnemonic device 和一种达到可读性的方法。可读的代码意味着更少的文档,稀少的语法意味着学习forth的代价很小。这些对于我而言,意味着个人的创造力和令人愉悦的代码。其他人却只看到它的短处,害怕在管理层面上失去控制和标准化的缺乏。我认为管理层面的失败比语言的缺陷更加严重。

Visitor:你说“大多数的错误是出在语法上”,你是怎么避免forth编程中其他类型的错误的?类似逻辑错误、可维护性错误,还有糟糕的类型判断。

Chuck:forth里出现的大多数错误都是与堆栈管理相关的。举个例子,如果你不慎在堆栈上遗留了某个不应该呆在那里的东西,而它马上就能让你摔个狗吃屎。每个词都应该有一个对堆栈内容的描述,这很重要。它会告诉你,它往堆栈上放进了什么,又从堆栈上拿出了什么。但这只是一个描述,你不能完全的信任它。有些人会在使用这些词之前对它们进行测试,并观察堆栈上的情况。其实有个最基本的解决方法,那就是因子化。如果你有一个一行长的词,当你读它的时候,你就能在脑子里演绎出它在堆栈上的表现,并且还是正确的。你可以测试它,并观察它是否如你所想的那样工作,但即便如此,你最好还是提防堆栈上有可能出现的错误。dup和drop这两个词在forth程序里是无处不在的,并且它们必定能被正确使用。在脱离语境测试这些词的时候,你只需要放进它们需要的参数,然后观察他们输出的结果。另外,当你用从底往上的方式开始编程时,所有你定义的词都能正确的工作,因为你已经测试过它们了。还有,forth里只有极少几个条件语句,一个if-else-then结构,一个begin-while结构。我要教给你们的哲学是,精简程序中的条件语句的数量。比起一个需要测定某个数值之后再决定做这个还是做那个的词,还有更好的方法,那就是,你有两个词:一个做这个,一个做那个,然后你选择合适的那一个。但是在C语言不是这样做的,因为调用的代价太昂贵了,所以他们更趋向于根据输入的参数,让同样的程序做不同的事。这就导致了那些历史遗留软件里所有的bug和复杂性。

Visitor:试图解决执行不力的情况?

Chuck:嗯。循环是不可避免的,但是循环可以变得很容易。一个forth循环,准确的说,一个colorforth循环,是非常简单的,只有一个单独的入口和一个单独的出口。

Visitor:你会给初学者什么样的忠告,使他们能更愉快更高效的编程?

Chuck:可以肯定的是,这个忠告不会出乎你的意料之外,我会说,你应该学着去写forth代码,即使你不打算成为专业的forth程序员。因为它会教会你一些东西,并且给你一个更好的视角去观察你现在所使用的语言。比如我现在要写一个C程序,即使我以前从没写过C程序,但我会用forth那种一大堆简短子程序的风格去写它。即使这样做会付出某些代价,但我认为这种代价对于可维护性来说是物有所值的。另一件事就是KISS原则,在设计一个航空器、或是写一个应用程序、甚至是一个文字处理器的时候,有一个无法避免的趋势是添加特性、再次添加特性、再三添加特性……直到变得不可能进行维护为止。我给他们的建议是,针对不同的市场,做出一大堆的文字处理器。用微软的WORD写一个简单的Email显然是很傻的行为。所有可能用到的功能之中,有99%是不必要的。你应该有一个Email编辑器。以前应该曾经有过,但现在的趋势不是这样。我是觉得很奇怪的。

保持简单。如果你碰到一个应用程序,如果你就是这个设计团队中的一员,试着说服其他人保持简单。不要预期,不要设计一个你认为将来某一天可能会出现的情况,你只要处理好你当前的问题,过早的预期是没有必要的。你可能会预期到10件事的发生,但最后其中只有一个发生了,所以你浪费了太多的努力。

Visitor:你是怎样理解简单的?

Chuck:近段时间出现了一个关于复杂性的学科,其中就包含了如何测量复杂性。而我喜欢的那个描述(我不知道这里是否还有其他的另一个)就是最短的那一个。或者你有两个概念,较短的那个描述就是更简单的。如果你能为某个东西想出一个较短的定义,你就想出了一个更简单的定义。

但是任何描述都离不开语境,所以上面所说的在这种很微妙的情况下是失败的。如果你想在C里面写一个非常短的子程序,你也许会说这很容易,但是你要依赖于C编译器、操作系统和准备执行这个程序的电脑的存在。所以,你不是在做一个简单的东西,当你考虑到更广阔的环境时,你是在做一个精致的、复杂的东西。我想它就像“美女”,你没法定义它,但是当你看到它的时候,你就知道是它了,简单就是这样的一个东西。

Visitor:那团队编程怎么进行呢?

Chuck:团队编程?你过虑了。一个团队的第一项工作就是把问题分解成相对独立的部分,然后把每一部分分配给团队成员,而团队领导者的职责就是保证各部分能再次结合到一起。有时候两个人需要协同工作,在互相探讨一个问题时能使得问题更加清晰明确,但是过多的交流反而又转回到问题本身。团队思考并没有解放创造力,当若干个人在一起工作时,必然只有一个人在做这个工作。

Visitor:这是否对每一类项目都有效?如果你不得不写一个含有丰富特性的东西呢,例如OpenOffice.org,这个听起来够复杂了吧?

Chuck:某些类似于OpenOffice.org的东西将被因子化成多个子项目,把每一个分配给一个单位去做,并且他们之间能有足够的交流来保证程序的兼容性。

Visitor:你觉得怎样才是一个优秀的程序员?

Chuck:一个优秀的程序员能迅速写出优秀的代码。优秀的代码就是正确、紧凑和可读。“迅速”的意思是以“小时”或“天”的时间单位来计算。而一个糟糕的程序员只会在嘴上谈论这个问题,把时间浪费在计划上而不是编程上,而且还总想升迁到一个不再需要编程和调试的岗位。

Visitor:你对编译器的看法是什么?你觉得它们掩盖了程序员的真实能力吗?

Chuck:编译器可能是所有写出来的代码当中最糟糕的。可能它们被某个以前从未写过一个编译器的人写出来,并且这个人以后也不会再去维护这个编译器。越是精心制作的语言,就越是复杂,bug丛生、无用,指的就是编译器。但是一个简单语言的简单编译器是一个非常有用的工具——除非你只是想看使用说明书。比编译器更重要的是编辑器,多样的编辑器让每一个程序员都选择了他自己喜欢的,而这极大的破坏了协作的努力。这就促进了从一个到另一个的手工作坊式的转译。

编译器作者的另一个失败之处就是他们的编译器都有着自己独特的关键字符,所以键盘无法变得更小更简单,而源代码则不能通用。但是一个程序员的能力跟这些工具是不相关的,所以他能迅速掌握它们的特点并生产出优秀的代码。

Visitor:软件应该怎样文档化?

Chuck:在我眼里,注释的价值远远没有其他人认为的那样重要。原因如下:
@注释应该是精简的,但它们经常是隐秘的,然后你不得不去猜测它们到底是什么意思。
@注释应该是verbose,但它们却淹没了它们所要解释的代码。这实在是很难把代码和注释联系起来。
@注释经常被写得很糟糕,程序员的文字表达能力是很难判定的,特别是当他们的母语不是英语的时候。术语和语法错误经常使注释不可读。
@最关键的是,注释经常是错误的。注释的修改很可能不会与代码的修改同步。虽然代码会被重点回顾,而注释少有这种待遇。一个错误的注释比没有注释更糟。读代码的人必须先判断,注释和代码到底哪个才是正确的。

注释经常被错误的理解了,它们应该解释代码的目的,而不是代码本身,解释代码本身是无用的。并且如果注释是错误的,那就是彻底的欺骗了。注释应该解释为什么代码是现在这样,它想要做的是什么,以及一些用来实现它的花样技巧。

colorForth把注释因子化到了一块阴影区域,把它们从代码中移了出来,这就使代码更加可读。然后它们立刻可以用来阅读或更新。这种方式也根据代码的长度,限制了注释的长度。

注释不能代替准确的文档,一个文档必须对代码模块的解释写得非常详细,这将远远超出注释所能做到的。A document must be written that explains in prose the code module of interest. It should expand greatly the comments and concentrate on literate and complete explanation. Of course, this is rarely done, is often unaffordable, and is easily lost since it is separate from the code.

Visitor:引用自http://www.colorforth.com/HOPL.html:
“forth专利的问题曾经被详尽讨论过,但是软件专利引起了太多的争议性,而且看起来很需要最高法庭的判决,在此之后,NRAO不再继续做这种低效的事了。现在,这个权力归还给了我,我不认为想法应该被专利化。事后证明,forth的唯一机会是在它曾经繁荣过的公众领域,”

Visitor:软件专利至今还是有争议的,你对专利的想法还是跟当初一样吗?

Chuck:我从来就没有支持过软件专利。这听起来就像是要专利化一个想法,而且专利化一个语言是特别恼人的。一个语言只有在它被使用的时候才有成功的可能。任何阻碍它使用的行为都是愚蠢的。

Visitor:你觉得专利化一项技术是阻止还是限制了它的扩散?

Chuck:推销软件很不容易,因为复制软件很容易。公司们做了很多努力去保护他们的产品,甚至采用了使软件无法使用的手段。我对这个问题的回答是,卖硬件送软件,买一赠一。硬件是很难复制的,再加上配套的软件,会显得更有价值。专利是处理这种问题的一个办法,它证明了新发明带来便利的价值。但这需要一种脆弱的平衡,劝阻那些不必要的专利,并连系到以前较早的艺术/专利。要执行以上所说,就要耗费巨大的津贴。近来一些大公司提议修改专利法来排斥单干的发明家,单干的要悲剧了,

云风大哥。。。膜拜一下~~

不错!支持云风大哥翻译好文章!

人名应该是Chuck Moore吧?

谢谢你的翻译!很精彩。

路过,学习了~

Chunk: 哦,我在六十年代去了趟 Stanford ,那里有组研究生正在写一个 ALGOL 编译器——Burroughs 5500 用的版本。当时我想他们不过才三四个人,就三四个人坐在那写一个编译器,我那个内牛满面啊。

我想,“靠,他们要能做,我也能做。”然后我就干了。其实一点也不难。当年写编译器还是有点神秘西西的。

========

这段翻译得太好了。。。内牛满面。。

.......

Forth 的相对性能取决于机器模型。Forth 通常使用 ITC 或 DTC 的模式并不完全切合 x86 的机器模型。用 SC 模式就应该能达到 C 的速度。若换个机器,可能结果就不一样。

而且在 x86 上,基础库也需要根据新指令集仔细的改进。

不过我个人感觉 Forth 首先追求的是紧凑和可移植性,其次才是运行效率。

forth很好啊

Forth 太有趣了。
可读性太差,没法推广

grad student不是毕业生,是研究生。

看了下performance页面,还是比不上C的运行速度呀 :(

建议在wave去创一个项目。。
参照这个。。
http://mmdekq.appspot.com/media/agZtbWRla3FyDAsSBU1lZGlhGME-DA/NF2C_Wave.html

真的很难得,这样痴迷于编程!

Post a comment

非这个主题相关的留言请到:留言本