第 11 章 OCaml 语言

9

类使用类似于模块语言的简短语言进行定义。

9.1 类类型

类类型是类型表达式的类级别等效项:它们指定类的通用形状和类型属性。

class-type::=[[?]标签名称:] 类型表达式->类类型
    class-body-type
 
class-body-type::= object [(类型表达式)] { 类字段规格 } end
   [[类型表达式 { ,类型表达式 } ]] 类类型路径
   letopen模块路径in类主体类型
 
class-field-spec::= inherit类主体类型
   val [mutable] [virtual] 实例变量名称:类型表达式
   valvirtualmutable实例变量名称:类型表达式
   method [private] [virtual] 方法名称:多态类型表达式
   methodvirtualprivate方法名称:多态类型表达式
   constraint类型表达式=类型表达式

另请参阅以下语言扩展:属性扩展节点

简单类表达式

表达式 类类型路径 等效于绑定到名称 类类型路径 的类类型。类似地,表达式 [ 类型表达式1 ,类型表达式n ] 类类型路径 等效于绑定到名称 类类型路径 的参数化类类型,其中类型参数分别被实例化为 类型表达式1, …类型表达式n

类函数类型

类类型表达式 类型表达式 -> 类类型 是类函数(从值到类的函数)的类型,这些函数以类型为 类型表达式 的值作为参数,并以类型为 类类型 的类作为结果返回。

类主体类型

类类型表达式 object [( 类型表达式 )] { 类字段规格 } end 是类主体的类型。它指定了它的实例变量和方法。在此类型中,类型表达式 与 self 类型匹配,因此为 self 类型提供了一个名称。

如果类主体为类主体类型中指定的每个组件提供定义,并且这些定义满足类主体类型中给出的类型要求,则该类主体将与类主体类型匹配。此外,类主体中存在的所有虚拟或公共方法也必须存在于类主体类型中(另一方面,可以省略一些实例变量和具体的私有方法)。虚拟方法将与具体方法匹配,这使得可以忘记它的实现。不可变实例变量将与可变实例变量匹配。

本地打开

从 OCaml 4.06 开始,类类型支持本地打开。

继承

继承构造 inherit 类主体类型 提供从其他类类型中包含方法和实例变量的方法。来自 类主体类型 的实例变量和方法类型被添加到当前类类型中。

实例变量规格

实例变量的规格写为 val [mutable] [virtual] 实例变量名称 : 类型表达式,其中 实例变量名称 是实例变量的名称,而 类型表达式 是它的预期类型。标志 mutable 指示此实例变量是否可以物理修改。标志 virtual 指示此实例变量未初始化。它可以通过继承在以后进行初始化。

实例变量规格将隐藏任何先前对具有相同名称的实例变量的规格。

方法规格

方法的规格写为 method [private] 方法名称 : 多态类型表达式,其中 方法名称 是方法的名称,而 多态类型表达式 是它的预期类型,可能为多态。标志 private 指示该方法无法从对象外部访问。

在公共方法规格中,多态性可以隐式保留:任何未绑定到类参数且未出现在类规格中的其他地方的类型变量都被假定为通用,并且在生成的方法类型中变为多态。编写显式多态类型将禁用此行为。

如果对同一方法存在多个规格,则它们必须具有兼容的类型。任何对方法的非私有规格都将强制它变为公共的。

虚拟方法规格

虚拟方法规格写为 method [private] virtual 方法名称 : 多态类型表达式,其中 方法名称 是方法的名称,而 多态类型表达式 是它的预期类型。

对类型参数的约束

构造 constraint 类型表达式1 = 类型表达式2 强制这两个类型表达式相等。这通常用于指定类型参数:这样,它们就可以绑定到特定的类型表达式。

9.2 类表达式

类表达式是值表达式的类级别等效项:它们求值为类,从而为类类型中表达的规格提供实现。

class-expr::= class-path
   [类型表达式 { ,类型表达式 } ]类路径
   (类表达式)
   (类表达式:类类型)
   类表达式 { 参数 }+
   fun { parameter }+->class-expr
   let [rec] let-binding { andlet-binding } inclass-expr
   objectclass-bodyend
   letopenmodule-pathinclass-expr
 
class-field::= inheritclass-expr [aslowercase-ident]
   inherit!class-expr [aslowercase-ident]
   val [mutable] inst-var-name [:typexpr] =expr
   val! [mutable] inst-var-name [:typexpr] =expr
   val [mutable] virtualinst-var-name:typexpr
   valvirtualmutable实例变量名称:类型表达式
   method [private] method-name { parameter } [:typexpr] =expr
   method! [private] method-name { parameter } [:typexpr] =expr
   method [private] method-name:poly-typexpr=expr
   method! [private] method-name:poly-typexpr=expr
   method [private] virtualmethod-name:poly-typexpr
   methodvirtualprivate方法名称:多态类型表达式
   constraint类型表达式=类型表达式
   initializerexpr

参见以下语言扩展: 局部抽象类型属性扩展节点

简单类表达式

表达式 class-path 计算结果为绑定到名称 class-path 的类。类似地,表达式 [ typexpr1 ,typexprn ] class-path 计算结果为绑定到名称 class-path 的参数化类,其中类型参数分别实例化为 typexpr1, …typexprn

表达式 ( class-expr ) 计算结果与 class-expr 相同的模块。

表达式 ( class-expr : class-type ) 检查 class-type 是否与 class-expr 的类型匹配(即实现 class-expr 是否满足类型规范 class-type)。整个表达式计算结果与 class-expr 相同的类,不同之处在于 class-type 中未指定的组件将被隐藏,不再可访问。

类应用

类应用用(可能带标签的)表达式的并置表示。它表示构造函数为第一个表达式应用于给定参数的类。参数的计算方式与表达式应用相同,但构造函数本身只有在创建对象时才会计算。特别是,构造函数应用引起的副作用只有在对象创建时才会发生。

类函数

表达式 fun [[?]label-name:]pattern -> class-expr 计算结果为从值到类的函数。当此函数应用于值 v 时,此值将与模式 pattern 匹配,结果是 class-expr 在扩展环境中计算的结果。

从具有默认值的函数转换为仅具有模式的函数对于类函数和普通函数的工作方式完全相同。

表达式

是以下表达式的简写形式

fun parameter1 ->fun parametern -> expr

局部定义

letlet rec 结构为核心语言表达式局部绑定值名称。

如果局部定义出现在类定义的开头,它将在创建类时计算(就像定义在类外部一样)。否则,它将在调用对象构造函数时计算。

局部打开

从 OCaml 4.06 开始,类表达式支持局部打开。

类主体

class-body::=  [(pattern [:typexpr] )] { class-field }

表达式 object class-body end 表示类主体。这是对象的原型:它列出了此类对象的实例变量和方法。

类主体是一个类值:它不会立即计算。相反,它的组件在每次创建对象时都会计算。

在类主体中,模式 ( pattern [: typexpr] ) 与 self 匹配,因此为 self 和 self 类型提供绑定。self 只能在方法和初始化程序中使用。

self 类型不能是封闭的对象类型,以便类保持可扩展。

从 OCaml 4.01 开始,如果同一个方法或实例变量名在同一个类主体中定义了多次,则会发生错误。

继承

继承结构 inherit class-expr 允许从其他类中重用方法和实例变量。类表达式 class-expr 必须计算结果为类主体。来自此类主体的实例变量、方法和初始化程序将添加到当前类中。添加方法将覆盖之前定义的相同名称的任何方法。

祖先可以通过在继承结构中追加 as lowercase-ident 来绑定。 lowercase-ident 不是真正的变量,只能用于选择方法,即在表达式 lowercase-ident # method-name 中。这提供了对方法 method-name 的访问,即使它在当前类中被重新定义,也是在父类中定义的。此祖先绑定的范围限于当前类。祖先方法可以从子类调用,但只能间接调用。

实例变量定义

定义 val [mutable] inst-var-name = expr 添加一个实例变量 inst-var-name,其初始值为表达式 expr 的值。标志 mutable 允许通过方法对该变量进行物理修改。

实例变量只能在其定义后的方法和初始化程序中使用。

从 3.10 版本开始,对具有相同名称的可见实例变量的重新定义不会创建新的变量,而是合并,使用最后一个值进行初始化。它们必须具有相同的类型和可变性。但是,如果通过从接口中省略实例变量来隐藏它,它将与具有相同名称的其他实例变量保持区分。

虚拟实例变量定义

变量规范写为 val [mutable] virtual inst-var-name : typexpr。它指定变量是否可修改,并给出其类型。

虚拟实例变量在 3.10 版本中添加。

方法定义

方法定义写为 method method-name = expr。方法的定义将覆盖该方法的任何先前定义。如果任何定义如此说明,该方法将是公开的(即,不是私有的)。

私有方法,method private method-name = expr,是一种只能在自身上调用的方法(来自同一对象的另一个方法,定义在本类或其子类之一中)。此调用使用表达式 value-name # method-name 执行,其中 value-name 在类定义开始时直接绑定到自身。私有方法不会出现在对象类型中。方法可以同时具有公共和私有定义,但一旦存在公共定义,所有后续定义都将被设置为公共定义。

方法可以具有显式多态类型,允许它们在程序中以多态方式使用(即使对于同一个对象)。显式声明可以通过三种方式之一完成:(1)在方法定义中,紧跟方法名称之后,给出显式多态类型, method [private] method-name : { ' ident }+ . typexpr = expr;(2)通过虚拟方法定义对显式多态类型的转发声明;(3)通过继承导入此类声明,以及/或约束self的类型。

在方法体中可以使用一些特殊表达式来操作实例变量和复制自身

expr::=
 inst-var-name<-expr
 {< [ inst-var-name=expr { ;inst-var-name=expr } [;] ] >}

表达式 inst-var-name <- expr 通过用 expr 的值替换与 inst-var-name 关联的值来就地修改当前对象。当然,此实例变量必须已声明为可变的。

表达式 {< inst-var-name1 = expr1 ;; inst-var-namen = exprn >} 评估为当前对象的副本,其中实例变量 inst-var-name1,…,inst-var-namen 的值已替换为相应表达式 expr1,…,exprn 的值。

虚拟方法定义

方法规范写为 method [private] virtual method-name : poly-typexpr。它指定方法是公开的还是私有的,并给出其类型。如果方法意图是多态的,则类型必须是显式多态的。

显式覆盖

从 Ocaml 3.12 开始,关键字 inherit!val!method!inheritvalmethod 具有相同的语义,但它们另外要求它们引入的定义必须是覆盖的。也就是说,method! 要求 method-name 已在本类中定义,val! 要求 inst-var-name 已在本类中定义,而 inherit! 要求 class-expr 覆盖某些定义。如果没有此类覆盖发生,则会发出错误信号。

作为副作用,这 3 个关键字避免了警告 ‍7(方法覆盖)和 ‍13(实例变量覆盖)。请注意,警告 ‍7 默认情况下处于禁用状态。

类型参数的约束

结构 constraint typexpr1 = typexpr2 强制这两个类型表达式相等。这通常用于指定类型参数:以这种方式,它们可以绑定到特定的类型表达式。

初始化程序

类初始化程序 initializer expr 指定一个表达式,该表达式将在每次从类创建对象时被评估,一旦其所有实例变量都被初始化。

9.3 类定义

class-definition::= classclass-binding { andclass-binding }
 
class-binding::=[virtual] [[type-parameters]] class-name { parameter } [:class-type]  =class-expr
 
type-parameters::= 'ident { ,'ident }

类定义 class class-binding { and class-binding } 是递归的。每个 class-binding 定义一个 class-name,它可以在整个表达式中使用,除了继承之外。它也可以用于继承,但仅在定义其自身的定义中使用。

类绑定将类名 class-name 绑定到表达式 class-expr 的值。它还将类类型 class-name 绑定到类的类型,并定义两个类型缩写:class-name# class-name。第一个是此类对象的类型,而第二个更通用,因为它与属于子类的任何对象的类型统一(参见第 ‍11.4 节)。

虚拟类

如果类的方法之一是虚拟的(即出现在类类型中,但实际上没有定义),则该类必须标记为虚拟的。无法从虚拟类创建对象。

类型参数

类类型参数对应于类类型和类绑定定义的两个类型缩写的类型参数。它们必须使用类型约束绑定到类定义中的实际类型。为了使缩写格式正确,推断的类类型的类型变量必须是类型参数,或者在约束子句中绑定。

9.4 类规范

class-specification::= classclass-spec { andclass-spec }
 
class-spec::=[virtual] [[type-parameters]] class-name: class-type

这是签名中类定义的对应部分。如果类规范具有相同的类型参数并且它们的类型匹配,则类规范将与类定义匹配。

9.5 类类型定义

classtype-definition::= classtypeclasstype-def { andclasstype-def }
 
classtype-def::=[virtual] [[type-parameters]] class-name=class-body-type

类类型定义 class class-name = class-body-type 为类体类型 class-body-type 定义了一个缩写 class-name。 类似类定义,还定义了两个类型缩写 class-name# class-name。 定义可以通过一些类型参数进行参数化。 如果类类型体中的任何方法是虚方法,则定义必须标记为 virtual

如果两个类类型定义具有相同的类型参数,并且它们展开为匹配的类型,则它们匹配。