使用 Modula-3 构建分布式电子表格

作者:John Kominek

早在 Borland 推出 Turbo Pascal 1.0 时,Philip Khan 做了一件精明的事情:他包含了简单电子表格的源代码,这就是许多程序员购买该产品的原因。在 Lotus 1-2-3 是当时的杀手级应用时,没有什么比瞥见其关键数据结构——稀疏矩阵更具吸引力的了。

当然,电子表格已不再是前沿技术。那么,其更新版本会是什么呢?从最近的市场宣传来看,我认为是分布式、跨平台且支持网络的电子表格。您将如何构建一个这样的电子表格呢?

Delphi,Pascal 最新的化身,如果可以只在 Windows 中使用,那倒是不错的选择。然而,对我们来说,Linux 兼容性是必须的。您可以尝试掌握 CORBA 的复杂性,但该标准现在正与微软的 DCOM 进行地盘争夺战,后者是一种行为更加复杂的生物。然而,Linux 程序员还有另一种选择。

Modula-3 语言及其周边系统为编写分布式应用程序提供了一个简单、清晰、成熟且健壮的工具。(请参阅侧边栏“简要传记”。)在本文中,我将重点介绍构建分布式电子表格所需的步骤。我的目标不是提供一个成熟的产品,而是一个代码框架,它说明了所有关键组件。

分布式应用程序框架

一块软件在三种意义上可以被认为是“分布式”的。

  1. 数据和计算可以划分为单独的进程。特别是,数据可以从多个客户端(GUI 查看器)查看,即使它存储在其他地方。

  2. 可执行文件可以驻留在不同的机器上——例如,一对 Linux 服务器支持 Windows 和 Linux 客户端的混合。

  3. 工作可以在人与人之间分配。您和我可以远程协作处理同一个电子表格,并采取预防措施以确保我不会错误地覆盖您的条目。

与传统应用程序相比,分布式软件更难设计和正确实现。尽管如此,它允许增长和灵活的组织。

软件成分

我们的任务需要三种基本成分

  1. 电子表格对象:最初,使用二维数组就足够了。一旦我们的应用程序启动并运行,经验将有助于改进对象的接口。稍后,固定的数组可以替换为稀疏矩阵。

  2. 显示小部件:将用户界面与数据分离可以简化修改并简化跨平台部署的任务。

  3. 连接胶水:电子表格对象和显示小部件需要能够相互通信。

在 Modula-3 中,网络对象提供连接胶水。妙处在于,就您的代码而言,调用网络上某处的对象几乎与调用您自己程序内部的对象一样容易。大部分繁重的工作都为您完成了。

关于 Modula-3

作为一种现代的、通用的系统编程语言,Modula-3 在设计上很精简,但实用且功能强大。应用范围从有趣的东西(多人游戏)到严肃的东西(操作系统)再到极其严肃的东西(911 呼叫中心)。十年的使用使参考编译器变得稳固可靠。

当前的实现存在于 Win32 和流行的 Unix 版本中。特别是 Linux 端口,受到了持续的关注。有几个版本可供下载,包括完整的源代码树。(有关指针,请参阅侧边栏“Modula-3 资源”)。

除了开放性之外,该语言还有许多值得推荐的功能,包括

  • 简洁的、源自 Algol 的语法

  • 对模块和接口的显式支持

  • 用于调用外部 C 代码和库的机制

  • 传统类型和对象类型(带有单继承)

  • 用于多线程编程的内置线程和互斥锁

  • 用于支持错误处理的断言和异常

  • 用于简化内存使用的增量垃圾回收器

如果这让您想起 Java,那并非偶然。虽然 Java 的语法源自 C++,但许多关键改进直接源自 Modula-3。Modula-3 的一个实现甚至允许与 Java 进行混合搭配集成。

位于“第一圈外”的功能,虽然未在语言本身中定义,但包括

  • Quake,一种简化的构建语言,取代了 make

  • 算法和容器对象的标准库

  • 轻量级数据库组件

  • 带有用户界面工具包的跨平台窗口系统

  • 网络对象

网络对象允许我们分阶段进行。首先,可以将电子表格构建为单个可执行文件。其次,作为在同一台机器上运行的多个进程。最后,作为在多台机器上运行的多个进程。阶段之间的跳跃很小。

步骤 1:基本构建

我们需要一些用于电子表格的底层数据结构,所以让我们从简单地输入开始

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 确实允许您在实现文件中隐藏表示形式的细节。但这将使我们进入不透明类型,这是一个更高级的主题。)

步骤 2:用户界面设计

Modula-3 配备了一个名为 Trestle 的跨平台窗口系统。在 Trestle 之上是一个名为 VBTkit 的用户界面工具包,以及一个 UI 构建器 FormsVBT。如果您愿意,您可以直接调用 X(或者,Win32 GDI),但这样做会失去可移植性。

程序用户界面的描述称为“Trestle 表单”。表单是使用嵌套括号组织的名称和值的文本描述。表单元素包括窗口、框架、按钮等,以及颜色等属性。列表 2 是弹出式计算器的示例表单,如图 1 所示。

重要的一点是,表单是在其自己的文件中定义的,而不是在任何 Modula-3 代码之外。当用户界面设计师与主要程序员是不同的人时,这种关注点分离被证明是有价值的。表单不描述如何构建界面,而仅描述界面的外观。FormsVBT 库在运行时构建它并将其挂钩到您的代码中。

Building a Distributed Spreadsheet in Modula-3

图 1. Calculator.fv 的外观

步骤 3:构建程序

假设我们的电子表格已实现,并带有一套测试函数。要构建程序,我们必须告知编译器哪些源文件组成了我们的可执行文件。这在 Modula-3 make 文件或 m3makefile 中完成。示例在列表 3 中显示。

要构建您的程序,请在命令行提示符下键入

m3build

编译器将为您确定依赖关系,仅重新编译必要的内容。

步骤 4:对象到网络对象

将常规对象(限制为单个地址空间)转换为网络对象(在网络上可见)并不像您想象的那么困难。您必须注意四个细节。

首先,需要链接网络对象库。这在 m3makefile(列表 3)中执行。

其次,对电子表格接口进行以下两项更改

IMPORT Money;
IMPORT NetObj;  (* new statement *)
  TYPE
    T = NetObj.T OBJECT  (* modified line *)
      grid: Grid;
      name: TEXT;
    METHODS
      ...

第三,这仅在执行时才重要,网络对象守护程序需要在后台运行。该程序作为 Modula-3 的一部分提供。通过键入以下命令启动守护程序

netobjd &
在客户端-服务器架构中,电子表格对象驻留在服务器上,但客户端发出方法调用(例如,更新单元格)。客户端需要相互了解。这是第四个细节。
步骤 5:分布式部署

netobjd 守护程序充当公告板。首先,服务器发布一条消息,说“我有一个电子表格对象出售。”然后客户端过来并说:“我买那个。”服务器导出;客户端导入;守护程序进行调解。在 CORBA 的术语中,守护程序是对象请求代理。一旦交易完成,客户端和服务器直接相互通信。代码细节在列表 4 中找到。

当服务器和客户端位于同一台机器上时,列表 4 将起作用。假设服务器在某个 Linux 机器——eggnog.cmu.edu——上运行,而客户端在其他地方。确保 netobjd 在 eggnog 上运行,并更改客户端程序中的一行。

address := NetObj.Locate( "eggnog.cmu.edu" );

这样,我们的程序现在就可以通过网络进行通信了。

步骤 6:单元格范围锁定

由于 Modula-3 预先配备了线程支持,因此它还提供了互斥锁(互斥信号量),以便对同一数据的并行操作进行序列化。在我们目前的讨论中,Money.T 类型尚未指定。它实际上可能类似于这样

INTERFACE Money;
TYPE
  T = MUTEX OBJECT
    cents: INTEGER;
  END;
END Money.

互斥锁保护数据,以便客户端 B 不会在客户端 A 完成之前修改值。当然,单独保护每个单元格是过度的。更优雅的方法是保护单元格范围,锁定由用户操作启动。

图 2 显示了用户 A(Alice)视角的电子表格。她正在处理红色阴影的单元格范围。用户 B(Bob)无法修改这些单元格。他正在处理蓝色单元格,向 Alice 指示这些单元格对她来说是只读的。

Building a Distributed Spreadsheet in Modula-3

图 2. 简单的多人电子表格

步骤 7:移植过程

要将我们的用户界面程序从 Linux 移植到 Windows NT,请执行以下操作

  1. 使用 tar 命令存档客户端源代码。

  2. 将 tar 文件复制到您的 Windows 机器。

  3. 使用 tar 解压缩文件。转换行尾标记。

  4. 在命令行中,键入 m3build

假设没有低级编程的技巧,本示例中的所有 Modula-3 代码——包括 GUI——都是透明可移植的。例如,不同的路径名约定隐藏在独立于操作系统的接口之后。没有看到 #ifdef

结论

在本文中,我重点介绍了使用 Modula-3 创建跨平台分布式电子表格的过程。关键步骤是将电子表格包装到网络对象中。通过这种方式,可以以与本地对象完全相同的语法调用远程对象。大部分繁重的工作都为您完成了。

Modula-3 不是创建分布式应用程序的唯一手段,但在我看来,它在简单性和功能之间取得了最佳平衡。从其意图来看,它是一种用于构建大型、稳固的系统以使您完成工作的语言。

显然,我的讨论省略了很多细节。为了帮助填补这个空白,Web 上提供了一个配套教程(请参阅侧边栏“入门指南”。)完整的源代码可用于实验和发明。

John Kominek 拥有滑铁卢大学计算机科学硕士学位,目前是 CMU 的研究生。当被追问时,他承认 Linux 的发音与 Linus 押韵。可以通过电子邮件 jkominek@cs.cmu.edu 与他联系。

加载 Disqus 评论