使用 Modula-3 构建分布式电子表格
早在 Borland 推出 Turbo Pascal 1.0 时,Philip Khan 做了一件精明的事情:他包含了简单电子表格的源代码,这就是许多程序员购买该产品的原因。在 Lotus 1-2-3 是当时的杀手级应用时,没有什么比瞥见其关键数据结构——稀疏矩阵更具吸引力的了。
当然,电子表格已不再是前沿技术。那么,其更新版本会是什么呢?从最近的市场宣传来看,我认为是分布式、跨平台且支持网络的电子表格。您将如何构建一个这样的电子表格呢?
Delphi,Pascal 最新的化身,如果可以只在 Windows 中使用,那倒是不错的选择。然而,对我们来说,Linux 兼容性是必须的。您可以尝试掌握 CORBA 的复杂性,但该标准现在正与微软的 DCOM 进行地盘争夺战,后者是一种行为更加复杂的生物。然而,Linux 程序员还有另一种选择。
Modula-3 语言及其周边系统为编写分布式应用程序提供了一个简单、清晰、成熟且健壮的工具。(请参阅侧边栏“简要传记”。)在本文中,我将重点介绍构建分布式电子表格所需的步骤。我的目标不是提供一个成熟的产品,而是一个代码框架,它说明了所有关键组件。
一块软件在三种意义上可以被认为是“分布式”的。
数据和计算可以划分为单独的进程。特别是,数据可以从多个客户端(GUI 查看器)查看,即使它存储在其他地方。
可执行文件可以驻留在不同的机器上——例如,一对 Linux 服务器支持 Windows 和 Linux 客户端的混合。
工作可以在人与人之间分配。您和我可以远程协作处理同一个电子表格,并采取预防措施以确保我不会错误地覆盖您的条目。
与传统应用程序相比,分布式软件更难设计和正确实现。尽管如此,它允许增长和灵活的组织。
我们的任务需要三种基本成分
电子表格对象:最初,使用二维数组就足够了。一旦我们的应用程序启动并运行,经验将有助于改进对象的接口。稍后,固定的数组可以替换为稀疏矩阵。
显示小部件:将用户界面与数据分离可以简化修改并简化跨平台部署的任务。
连接胶水:电子表格对象和显示小部件需要能够相互通信。
在 Modula-3 中,网络对象提供连接胶水。妙处在于,就您的代码而言,调用网络上某处的对象几乎与调用您自己程序内部的对象一样容易。大部分繁重的工作都为您完成了。
作为一种现代的、通用的系统编程语言,Modula-3 在设计上很精简,但实用且功能强大。应用范围从有趣的东西(多人游戏)到严肃的东西(操作系统)再到极其严肃的东西(911 呼叫中心)。十年的使用使参考编译器变得稳固可靠。
当前的实现存在于 Win32 和流行的 Unix 版本中。特别是 Linux 端口,受到了持续的关注。有几个版本可供下载,包括完整的源代码树。(有关指针,请参阅侧边栏“Modula-3 资源”)。
除了开放性之外,该语言还有许多值得推荐的功能,包括
简洁的、源自 Algol 的语法
对模块和接口的显式支持
用于调用外部 C 代码和库的机制
传统类型和对象类型(带有单继承)
用于多线程编程的内置线程和互斥锁
用于支持错误处理的断言和异常
用于简化内存使用的增量垃圾回收器
如果这让您想起 Java,那并非偶然。虽然 Java 的语法源自 C++,但许多关键改进直接源自 Modula-3。Modula-3 的一个实现甚至允许与 Java 进行混合搭配集成。
位于“第一圈外”的功能,虽然未在语言本身中定义,但包括
Quake,一种简化的构建语言,取代了 make
算法和容器对象的标准库
轻量级数据库组件
带有用户界面工具包的跨平台窗口系统
网络对象
网络对象允许我们分阶段进行。首先,可以将电子表格构建为单个可执行文件。其次,作为在同一台机器上运行的多个进程。最后,作为在多台机器上运行的多个进程。阶段之间的跳跃很小。
我们需要一些用于电子表格的底层数据结构,所以让我们从简单地输入开始
TYPE Grid: REF ARRAY OF ARRAY OF INTEGER;
或
TYPE Grid: REF ARRAY OF ARRAY OF Money.T;这定义了一个二维整数网格(在第一行),或者,作为第二种选择,定义了一个 Money.T 类型的网格。整数是内置类型。Money.T 是程序员定义的类型;“.T”后缀是 Modula-3 约定。(在真正的电子表格中,每列都将具有不同的用户定义类型。现在先忽略这个细节。)
如果您愿意,可以在变量声明期间或程序执行期间在堆上分配新网格。
VAR myGrid : Grid := NEW (Grid, rows, cols); BEGIN myGrid := NEW (Grid, 100, 20); END.
myGrid 的第二次赋值将擦除第一次赋值,但不要惊慌——我们没有内存泄漏。Modula-3 垃圾回收器负责回收丢失的内存。对象变量(无需析构函数)也是如此,包括在远程机器上分配内存的对象。
为了充实我们的电子表格对象,我们接下来将一些运算符方法附加到网格。一个好的位置是在单独的“接口”文件中。列表 1 包含 spreadsheet.i3 的初始版本。我们的对象现在被声明为 Spreadsheet.T 类型。
接口的重要属性是它不包含任何可执行代码。这保留给“.m3”或模块文件。接口不说明如何计算某些东西,而仅说明它做什么。这类似于 C 中的 .h 文件,但更严格。只有在接口中显式公开的操作——或用术语“导出”来说——才可供外部使用。
(敏锐的读者可能已经注意到 Grid 的表示形式在 spreadsheet.i3 中公开了——这是一件坏事。Modula-3 确实允许您在实现文件中隐藏表示形式的细节。但这将使我们进入不透明类型,这是一个更高级的主题。)
Modula-3 配备了一个名为 Trestle 的跨平台窗口系统。在 Trestle 之上是一个名为 VBTkit 的用户界面工具包,以及一个 UI 构建器 FormsVBT。如果您愿意,您可以直接调用 X(或者,Win32 GDI),但这样做会失去可移植性。
程序用户界面的描述称为“Trestle 表单”。表单是使用嵌套括号组织的名称和值的文本描述。表单元素包括窗口、框架、按钮等,以及颜色等属性。列表 2 是弹出式计算器的示例表单,如图 1 所示。
重要的一点是,表单是在其自己的文件中定义的,而不是在任何 Modula-3 代码之外。当用户界面设计师与主要程序员是不同的人时,这种关注点分离被证明是有价值的。表单不描述如何构建界面,而仅描述界面的外观。FormsVBT 库在运行时构建它并将其挂钩到您的代码中。

图 1. Calculator.fv 的外观
假设我们的电子表格已实现,并带有一套测试函数。要构建程序,我们必须告知编译器哪些源文件组成了我们的可执行文件。这在 Modula-3 make 文件或 m3makefile 中完成。示例在列表 3 中显示。
要构建您的程序,请在命令行提示符下键入
m3build
编译器将为您确定依赖关系,仅重新编译必要的内容。
将常规对象(限制为单个地址空间)转换为网络对象(在网络上可见)并不像您想象的那么困难。您必须注意四个细节。
首先,需要链接网络对象库。这在 m3makefile(列表 3)中执行。
其次,对电子表格接口进行以下两项更改
IMPORT Money; IMPORT NetObj; (* new statement *) TYPE T = NetObj.T OBJECT (* modified line *) grid: Grid; name: TEXT; METHODS ...
第三,这仅在执行时才重要,网络对象守护程序需要在后台运行。该程序作为 Modula-3 的一部分提供。通过键入以下命令启动守护程序
netobjd &在客户端-服务器架构中,电子表格对象驻留在服务器上,但客户端发出方法调用(例如,更新单元格)。客户端需要相互了解。这是第四个细节。
netobjd 守护程序充当公告板。首先,服务器发布一条消息,说“我有一个电子表格对象出售。”然后客户端过来并说:“我买那个。”服务器导出;客户端导入;守护程序进行调解。在 CORBA 的术语中,守护程序是对象请求代理。一旦交易完成,客户端和服务器直接相互通信。代码细节在列表 4 中找到。
当服务器和客户端位于同一台机器上时,列表 4 将起作用。假设服务器在某个 Linux 机器——eggnog.cmu.edu——上运行,而客户端在其他地方。确保 netobjd 在 eggnog 上运行,并更改客户端程序中的一行。
address := NetObj.Locate( "eggnog.cmu.edu" );
这样,我们的程序现在就可以通过网络进行通信了。
由于 Modula-3 预先配备了线程支持,因此它还提供了互斥锁(互斥信号量),以便对同一数据的并行操作进行序列化。在我们目前的讨论中,Money.T 类型尚未指定。它实际上可能类似于这样
INTERFACE Money; TYPE T = MUTEX OBJECT cents: INTEGER; END; END Money.
互斥锁保护数据,以便客户端 B 不会在客户端 A 完成之前修改值。当然,单独保护每个单元格是过度的。更优雅的方法是保护单元格范围,锁定由用户操作启动。
图 2 显示了用户 A(Alice)视角的电子表格。她正在处理红色阴影的单元格范围。用户 B(Bob)无法修改这些单元格。他正在处理蓝色单元格,向 Alice 指示这些单元格对她来说是只读的。

图 2. 简单的多人电子表格
要将我们的用户界面程序从 Linux 移植到 Windows NT,请执行以下操作
使用 tar 命令存档客户端源代码。
将 tar 文件复制到您的 Windows 机器。
使用 tar 解压缩文件。转换行尾标记。
在命令行中,键入 m3build。
假设没有低级编程的技巧,本示例中的所有 Modula-3 代码——包括 GUI——都是透明可移植的。例如,不同的路径名约定隐藏在独立于操作系统的接口之后。没有看到 #ifdef。
在本文中,我重点介绍了使用 Modula-3 创建跨平台分布式电子表格的过程。关键步骤是将电子表格包装到网络对象中。通过这种方式,可以以与本地对象完全相同的语法调用远程对象。大部分繁重的工作都为您完成了。
Modula-3 不是创建分布式应用程序的唯一手段,但在我看来,它在简单性和功能之间取得了最佳平衡。从其意图来看,它是一种用于构建大型、稳固的系统以使您完成工作的语言。
显然,我的讨论省略了很多细节。为了帮助填补这个空白,Web 上提供了一个配套教程(请参阅侧边栏“入门指南”。)完整的源代码可用于实验和发明。
John Kominek 拥有滑铁卢大学计算机科学硕士学位,目前是 CMU 的研究生。当被追问时,他承认 Linux 的发音与 Linus 押韵。可以通过电子邮件 jkominek@cs.cmu.edu 与他联系。