SPARK2000中的多态性实现解析
1. 引言
SPARK编程语言是Ada的一个子集,适用于开发高完整性软件。它通过移除一些会给程序正确性证明带来困难的Ada特性(如泛型、异常、访问类型等),并引入用于支持流分析、可见性控制和形式化证明的注解,来实现这一目标。
SPARK2000是SPARK95的面向对象扩展,旨在支持面向对象编程,同时允许进行模块化推理。这得益于行为子类型(behavioural subtypeoo)的概念,该概念保证了每个扩展类型的行为与其祖先类型一致。
然而,最初的SPARK2000提案不允许使用访问类型,这使得通过类范围类型声明的变量无法实现真正的多态性。因为一旦初始化,类范围类型的变量就不能修改其类型,在其生命周期内不能呈现多种形式,所以不能被视为多态的。这种对类范围类型的限制过于严格,阻碍了SPARK2000提供的面向对象范式的优势。
为了解决这个问题,需要在不影响原有方法的前提下,允许在SPARK2000中使用访问类型。这需要解决两个主要问题:禁止对象的别名/共享,以避免副作用;确保任何SPARK2000程序的内存需求是可预测的,因为如果执行期间内存不足,可能会发生错误。由于第二个问题,不允许定义递归类型。在没有经过验证的垃圾回收系统的情况下,必须提供显式的内存管理机制。
2. SPARK2000简介
SPARK2000是SPARK95的面向对象扩展,其引入面向对象编程得益于支持模块化推理的行为子类型概念。行为子类型关系保证了每个扩展类型的行为与其祖先类型一致,并且扩展类型可以有与祖先不同的行为,只要这种行为不能被祖先类型的用户观察到。因此,如果满足一组明确定义的规则,标记类型可以在派生时进行扩展,而无需重新验证先前正确的程序/证明。
由于Ada95的一些限制,SPARK2000使用了更强的行为子类型概念。扩展类型不能修改继承组件的类型(或名称),并且对继承的任何原语操作的签名的修改也受到限制,因为所有控制参数必须具有相同的类型。因此,扩展类型不能为继承的组件选择合适的名称,也不能修改其类型以实现更高效的抽象实现。此外,参数/结果的协变/逆变灵活性也受到限制,以确保所有继承的子程序都是原语操作。在SPARK2000中,不允许重载和定义非原语操作的子程序。
在SPARK2000中,类引入的任何方法的第一个参数具有特殊含义,它代表接收消息的对象。SPARK2000引入了六个新注解:
-
类不变式(class invariant)
:只能用于标记记录,指定对象的组件在每个原语操作执行前后必须维护的不变式。该不变式可视为标记类型所有原语操作的前置和后置条件的一部分,但不包括带有“initialstate”注解的子程序的前置条件。
-
初始状态(initialstate)
:只能在过程中引入,表明该过程是初始状态操作,对于任何对象(形式参数除外),它必须是第一个执行的子程序。如果有多个初始状态过程,使用哪一个先执行并不重要。
-
类型转换(type conversion)
:在SPARK2000中,层次结构中对象的类型转换不能随意使用,只有带有此注解的子程序才允许有类型转换和记录聚合的语句。
-
公共(public)
:必须在每个原语操作中引入,表明子程序是公共的。
-
私有(private)
:必须在每个原语操作中引入,表明子程序是私有的,即只能由其类型的其他子程序使用。
-
包装(wraps)
:用于向SPARK2000的检查器/证明工具表明,原语操作实际上是一个包装子程序,其唯一目的是在将参数应用于被包装的操作之前,将其转换为正确的类型和视图。此注解对于将MooZ规范细化为SPARK2000代码很重要。
SPARK2000中的行为子类型关系由语法和语义规则组成。语法规则用于保证签名兼容性,并确保程序员只能定义原语操作。除原语操作的第一个参数外,所有其他类型为标记类型的形式参数必须允许通过类范围类型实现多态性。语义规则用于保证扩展类型的行为与其所有祖先一致,但扩展类型中的原语操作可以在其祖先子程序的适用域之外操作,并返回祖先对象不可接受的结果。这是因为在SPARK2000中不允许别名,这种行为不能被任何祖先类型的用户观察到。
SPARK2000是对SPARK95的保守扩展,所有为SPARK2000定义的规则仅适用于引入标记类型的包。
3. SPARK2000中的多态性
为了在SPARK2000中支持真正的多态性,需要引入访问类型。以下是相关规则和机制的介绍:
3.1 类型定义和变量声明规则
为了支持所有类型定义和变量声明的一致命名,提出了以下约定规则:
1.
标记类型命名
:在定义标记类型时,其名称应以“class”结尾。
2.
访问类型命名
:对于每个标记类型,有一个全局声明的类范围类型的访问类型。该访问类型的名称是将标记类型名称中的“class”替换为“object”。
3.
原语操作参数类型
:原语操作的第一个形式参数必须是访问参数,其类型如第一条规则所定义。
4.
其他参数类型
:除第一个参数外,如果其他形式参数的类型由标记类型定义,则必须声明为访问参数,其类型是第一条规则中定义的命名类型的类范围类型。
5.
用户变量声明
:除形式参数外,标记类型的用户声明的任何变量都应使用第二条规则中定义的命名类型进行声明。
这些规则的作用如下表所示:
|规则|作用|
| ---- | ---- |
|规则1和2|确保所有标记类型与其他类型区分开来|
|规则3|是原语操作第一个形式参数要求的结果|
|规则4|保证如果形式参数的类型由标记类型定义,则允许在调用中使用扩展类型的对象作为实际参数|
|规则5|确保标记类型的用户只能声明真正多态的变量|
原语操作所有形式参数的类型要求意味着实际参数不能为null,并且不能被修改,因为访问参数的模式为“in”。这意味着标记类型不提供任何能够处理空指针的原语操作。所有使用上述第二条规则中描述的命名类型声明的变量,在声明时必须显式分配一个标称类型的对象。
访问参数的模式仅表示指针本身不能被修改以指向另一个对象,但对于所指向的对象可能发生的情况没有说明。因此,在以访问参数作为形式参数的函数中可能会发生副作用。为了解决这个问题,引入了一个注解来声明访问参数在子程序内部可以如何操作,即“access mode”。例如,“–# access mode (obj: in out; arg: in);”表示第一个参数指向的对象可以被读取和修改,而第二个参数指向的对象只能被读取。
为了确保模式为“in”的访问参数的对象不被修改,如果关联的模式注解允许修改,则不能将其用作子程序的实际参数。为了强制封装,即使标记类型的组件不是隐藏/私有的,SPARK2000也不允许解引用。
下面是一个简单的mermaid流程图,展示了变量声明和参数类型的关系:
graph LR
A[标记类型定义] --> B[名称以class结尾]
A --> C[全局声明访问类型]
C --> D[名称将class替换为object]
E[原语操作] --> F[第一个参数为访问参数]
F --> G[类型按规则1定义]
E --> H[其他参数为访问参数]
H --> I[类型为类范围类型]
J[用户声明变量] --> K[使用规则2定义的命名类型]
3.2 原始类(Primitive Classoo)
一些纯面向对象编程语言(如Smalltalk和Eiffel)以及一些面向对象规范符号(如MooZ)具有原始类(Primitive Classoo)的概念。一般来说,原始类用于引入语言中所有类共有的方法(如复制、相等性、类型查询等),以及与语言概念(如整数、数组、字符串等)、编译器和环境属性(如文件管理、异常、内存、参数等)直接相关的类型和设施。
一些提供面向对象编程功能的语言(如C++和Ada95)没有原始类,因为其功能已经由语言的非面向对象部分提供,或者在不同层次结构中定义的类之间没有共同的功能。而SPARK2000具有所有标记类型共有的机制,包括复制、比较和类型转换操作,以及显式的存储管理机制。
在SPARK2000中,原始类中定义的几乎所有操作都必须由工具自动生成。这是一个重要的要求,因为SPARK2000的提案不是扩展Ada95,而是提供一种尽可能类似于纯面向对象语言的机制。否则,就需要为SPARK2000实现一个特定的编译器。
这种方法的主要缺点是,在(预)处理后,用户最终得到的类型将比用户显式引入的原语操作更多。然而,这并不构成问题,因为用户引入的操作不会因为(预)处理而被修改,只是引入了新的子程序来重新定义原始类中定义的操作。
原始类概念提供的概念灵活性以及消除用户在为所有标记类型定义此类操作时可能引入的错误,足以证明在SPARK2000中包含这个概念是合理的。
SPARK2000引入了一个名为“Spark2000 class”的新类型。虽然可以假设它被所有标记类型隐式继承(就像真正的面向对象语言中的原始类一样),但SPARK2000决定不这样做。原因之一是Ada95不支持多重继承,另一个原因是在没有工具支持的情况下,仍然希望能够使用任何经过验证的Ada95编译器对SPARK2000程序进行类型检查。
“Spark2000 class”类型及其原语操作的规范如下:
with Ada.Finalization;
with Ada.Unchecked Deallocation;
package Spark2000 is
type Spark2000 class is new
Ada.Finalization.Controlled with private;
type Spark2000 object is access all
Spark2000 class’Class;
procedure Create (
obj: access Spark2000 class);
procedure Free (obj: access Spark2000 class);
procedure Copy to (
src: access Spark2000 class;
dst: in out Spark2000 object);
function Is equal (
obj1: access Spark2000 class;
obj2: access Spark2000 class’Class)
return boolean;
function Is same type (
obj1: access Spark2000 class;
obj2: access Spark2000 class’Class)
return boolean;
function Is ancestor (
obj1: access Spark2000 class;
obj2: access Spark2000 class’Class)
return boolean;
function Is descendant (
obj1: access Spark2000 class;
obj2: access Spark2000 class’Class)
return boolean;
function Is in same hierarchy (
obj1: access Spark2000 class;
obj2: access Spark2000 class’Class)
return boolean;
procedure Convert to parent (
src: access Spark2000 class;
dst: access Spark2000 class’Class);
procedure Convert from ancestor (
dst: access Spark2000 class;
src: access Spark2000 class’Class);
private
type Spark2000 class is new
Ada.Finalization.Controlled with
record null; end record;
procedure Initialize (
obj: in out Spark2000 class);
procedure Finalize (
obj: in out Spark2000 class);
end Spark2000;
“Spark2000 class”引入的三个操作(“Is descendant”、“Convert from descendant”和“Convert from hierarchy”)不需要任何标记记录重新定义,因为它们是根据其他操作定义的。以下是这些操作的实现:
package body Spark2000 is
--# function Is descendantPF (
--# obj1: Spark2000 class;
--# obj2: Spark2000 class’Class)
--# return boolean;
function Is descendant (
obj1: access Spark2000 class;
obj2: access
Spark2000.Spark2000 class’Class)
return boolean
--# public;
--# return Is descendantPF (obj1, obj2);
is begin
return Is ancestor (obj2, obj1);
end Is descendant;
--# function Convert from descendantPF (
--# dst: Spark2000 class;
--# src: Spark2000 class’Class)
--# return Spark2000 class’Class;
procedure Convert from descendant (
dst: access Spark2000 class;
src: access
Spark2000.Spark2000 class’Class)
--# public;
--# type conversion;
--# access mode (dst: in out; src: in);
--# derives dst from src;
--# post dst = Convert from descendantPF (
--# dst~, src);
is
temp: Spark2000.Spark2000 object := new
Spark2000.Spark2000 class;
begin
Create (temp);
if Is descendant (src, dst) then
Convert to parent (src, temp);
Convert from descendant (dst, temp);
else
Copy to (src,
Spark2000.Spark2000 object (dst));
end if;
Free (temp);
end Convert from descendant;
--# function Convert from hierarchyPF (
--# dst: Spark2000 class;
--# src: Spark2000 class’Class)
--# return Spark2000 class;
procedure Convert from hierarchy (
dst: access Spark2000 class;
src: access
Spark2000.Spark2000 class’Class)
--# public;
--# access mode (dst: in out; src: in);
--# derives dst from src,dst;
--# post dst = Convert from hierarchyPF (
--# dst~, src);
is begin
if Is ancestor (src, dst) then
Convert from ancestor (dst, src);
elsif Is descendant (src, dst) then
Convert from descendant (dst, src);
elsif Is same type (src, dst) then
Copy to (dst,
Spark2000.Spark2000 object (src));
end if;
end Convert from hierarchy;
end Spark2000;
这些操作的具体用途将在后续部分详细介绍。“Initialize”、“Free”和“Finalize”操作将在下一部分描述,“Copy to”和“Is equal”方法的重要性将在3.4节解释,“Is same type”、“Is ancestor”、“Is descendant”、“Is in same hierarchy”、“Convert to parent”、“Convert from ancestor”、“Convert from descendant”和“Convert from hierarchy”操作将在3.5节介绍。从“Ada.Finalization.Controlled”继承的“Adjust”操作不需要重新定义,因为在执行期间它永远不会被调用。
3.3 显式存储管理
在SPARK2000中,为了支持对象的分配和释放,需要显式地管理存储。这是因为使用指针意味着动态分配和释放内存,而在没有经过验证的垃圾回收系统的情况下,必须提供一种显式的内存管理机制。
以下是显式存储管理的流程说明:
1.
对象创建
:使用
Create
过程来创建一个新的对象。该过程会为对象分配内存,并进行必要的初始化操作。
2.
对象释放
:使用
Free
过程来释放对象占用的内存。在释放对象之前,需要确保对象不再被使用。
3.
对象复制
:使用
Copy to
过程来复制一个对象。该过程会创建一个新的对象,并将源对象的状态复制到新对象中。
下面是一个简单的mermaid流程图,展示了显式存储管理的流程:
graph LR
A[开始] --> B[创建对象]
B --> C[使用对象]
C --> D{是否需要复制对象?}
D -- 是 --> E[复制对象]
D -- 否 --> F{是否不再使用对象?}
E --> F
F -- 是 --> G[释放对象]
F -- 否 --> C
G --> H[结束]
3.4 对象复制和比较操作
为了支持对象的复制和比较,SPARK2000引入了一些原语操作。这些操作对于确保对象的状态一致性和正确性非常重要。
-
复制操作
:
Copy to过程用于将一个对象的状态复制到另一个对象中。该过程的实现需要确保复制的对象与源对象具有相同的状态。 -
比较操作
:
Is equal函数用于比较两个对象是否相等。该函数的实现需要根据对象的具体类型和状态来判断两个对象是否相等。
以下是这些操作的重要性说明:
|操作|重要性|
| ---- | ---- |
|
Copy to
|确保对象的状态可以被正确复制,避免数据丢失或不一致|
|
Is equal
|用于判断两个对象是否相等,在进行对象比较和查找时非常有用|
3.5 类型查询和类型转换操作
SPARK2000提供了一些操作来进行类型查询和类型转换。这些操作对于处理对象的层次结构和多态性非常重要。
- 类型查询操作 :
-
Is same type函数用于判断两个对象是否属于同一类型。 -
Is ancestor函数用于判断一个对象是否是另一个对象的祖先类型。 -
Is descendant函数用于判断一个对象是否是另一个对象的后代类型。 -
Is in same hierarchy函数用于判断两个对象是否属于同一层次结构。 -
类型转换操作 :
-
Convert to parent过程用于将一个对象转换为其祖先类型。 -
Convert from ancestor过程用于将一个对象从其祖先类型转换为其后代类型。 -
Convert from descendant和Convert from hierarchy过程根据对象的层次关系进行类型转换。
以下是这些操作的使用示例:
-- 判断两个对象是否属于同一类型
if Is same type (obj1, obj2) then
-- 执行相应操作
end if;
-- 将对象转换为其祖先类型
Convert to parent (src, dst);
3.6 应用示例
为了更好地理解上述规则和操作,下面给出一个
Points Hierarchy
的示例。假设我们有一个
Point class
作为基类,派生出
Point2D class
和
Point3D class
。
-- 定义Point class
with Spark2000;
package Point is
type Point class is new Spark2000.Spark2000 class with private;
type Point object is access all Point class’Class;
-- 原语操作
procedure Move (obj: access Point class; dx: Integer; dy: Integer);
private
type Point class is new Spark2000.Spark2000 class with
record
X: Integer;
Y: Integer;
end record;
end Point;
-- 实现Point class的原语操作
package body Point is
procedure Move (obj: access Point class; dx: Integer; dy: Integer) is
begin
obj.X := obj.X + dx;
obj.Y := obj.Y + dy;
end Move;
end Point;
-- 定义Point2D class
with Point;
package Point2D is
type Point2D class is new Point.Point class with private;
type Point2D object is access all Point2D class’Class;
-- 原语操作
overriding procedure Move (obj: access Point2D class; dx: Integer; dy: Integer);
private
type Point2D class is new Point.Point class with
record
-- 可以添加额外的成员
end record;
end Point2D;
-- 实现Point2D class的原语操作
package body Point2D is
overriding procedure Move (obj: access Point2D class; dx: Integer; dy: Integer) is
begin
-- 调用父类的Move方法
Point.Move (Point.Point object (obj), dx, dy);
-- 可以添加额外的操作
end Move;
end Point2D;
-- 定义Point3D class
with Point;
package Point3D is
type Point3D class is new Point.Point class with private;
type Point3D object is access all Point3D class’Class;
-- 原语操作
overriding procedure Move (obj: access Point3D class; dx: Integer; dy: Integer; dz: Integer);
private
type Point3D class is new Point.Point class with
record
Z: Integer;
end record;
end Point3D;
-- 实现Point3D class的原语操作
package body Point3D is
overriding procedure Move (obj: access Point3D class; dx: Integer; dy: Integer; dz: Integer) is
begin
-- 调用父类的Move方法
Point.Move (Point.Point object (obj), dx, dy);
obj.Z := obj.Z + dz;
end Move;
end Point3D;
在这个示例中,我们定义了一个
Point class
作为基类,派生出
Point2D class
和
Point3D class
。每个类都有自己的原语操作,并且可以重写父类的操作。通过使用上述的规则和操作,我们可以实现对象的创建、复制、比较、类型查询和类型转换等功能。
4. 结论
通过引入访问类型和显式的存储管理机制,SPARK2000实现了真正的多态性。这使得SPARK2000能够更好地支持面向对象编程,同时确保程序的正确性和可验证性。
在SPARK2000中,行为子类型关系和新的注解为模块化推理提供了支持,使得扩展类型可以在不影响祖先类型的情况下进行扩展。此外,原始类的概念和相关操作的自动生成,使得SPARK2000能够提供类似于纯面向对象语言的机制,同时避免了用户在定义这些操作时可能引入的错误。
未来的研究可以进一步探索如何优化SPARK2000的性能,以及如何更好地支持大规模的面向对象系统的开发。同时,还可以研究如何将SPARK2000与其他形式化方法和工具集成,以提高软件开发的效率和质量。
综上所述,SPARK2000为开发高完整性的面向对象软件提供了一个强大的工具和框架。通过合理使用其规则和操作,可以有效地实现程序的多态性和正确性验证。
超级会员免费看
32

被折叠的 条评论
为什么被折叠?



