Java中所有权类型的编码实现
1. 形式化定义
为了证明编码能够展现所有权类型系统的行为,我们对Java类型系统模型进行扩展,融入编码元素和运行时所有权信息。形式化定义(Tame FJOwn)借鉴了OGJ的方法,将上下文参数表示为类型参数。同时,通过支持存在类型,无需特殊机制处理所有权问题。
形式化系统的大部分内容相对标准,或遵循Tame FJ。与Tame FJ在模拟所有权方面的差异以灰色突出显示。此外,还添加了字段赋值、空值、堆和类型转换(用于模拟动态向下转换),并在其他方面做了一些小改进,但这些更改未作突出显示。为简洁起见,未描述与Tame FJ相同的部分。操作语义、格式良好的环境和堆、辅助函数以及将堆用作环境的规则被放在附录中。
Tame FJOwn的语法如下:
e ::= γ | null | e.f | e.f = e | e.<P, P >m(e) | new C< T ,⋆> | (T)e // 表达式
v ::= ι | null // 值
Q ::= class C<X T, O τ,Owner τ, This τ > N {T f; M}
M ::= <X T, O T > T m(T x) {return e;} // 方法声明
N ::= C<T, τ > | Object< τ, τ > // 类类型
R ::= N | X // 非存在类型
T, U ::= ∃Δ.N | ∃∅.X | // 类型
P ::= T | ⋆ | // 方法类型参数
X ,Y ::= X | O | v // 类型参数
Δ ::= X →[Bl Bu] // 类型环境
B ::= T | ⊥ // 边界
Γ ::= γ:T // 变量环境
γ ::= ι | x // 位置或变量
H ::= ι →{N; f→v} // 堆
T ::= T | τ // 类型和上下文
P ::= T | ⋆ // 方法参数
τ ::= World<> | O | v // 上下文
x, this // 变量
X, Y // 类型变量
O, Owner, This // 上下文变量
ι // 位置
C, Object, World // 类名
f, g // 字段名
m // 方法名
为方便起见,我们在语法上区分用于表示上下文的类型和类型参数与常规类型。使用
τ
表示上下文类型,
T
表示常规类型,
T
表示两者之一;对于参数,使用
O
表示上下文参数,
X
表示常规类型参数,
X
表示两者之一。实际上,类型系统对这两种类型的处理几乎相同。也可通过检查类型的顶级超类型来区分:上下文受
World
约束,其他类型受
Object
约束。
允许值(地址和空值,空值对应
World
)在运行时作为上下文(和类型)参数,以证明所有权层次结构的强制执行,但在源代码中不允许将值作为参数。同时,使用了一些类型简写,如
C
表示
C<>
,
R
表示
∃∅.R
。
2. 格式良好的类型
格式良好的类型定义如下:
X ∈Δ
Δ ⊢X ok (F-Var)
Δ ⊢World<> ok (F-World )
Δ ⊢⊥ok (F-Bottom)
Δ ⊢⋆ok (F-Star)
Δ ⊢Δ′ ok
Δ, Δ′ ⊢N ok
Δ ⊢∃Δ′.N ok (F-Exists)
Δ ⊢T, τ, τo ok
T = T, τ, τo, τt
Δ(τt) = [⊥T ]
class C<X T u> N{...}
Δ ⊢T <: [T /X ]T u
Δ ⊢C<T > ok (F-Class)
Δ ⊢τo ok
Δ(τt) = [⊥T ]
Δ ⊢Object< τ o,τ t > ok (F-Object)
在
F-Class
和
F-Object
中,不检查
This
位置的类型参数是否格式良好,而是检查其是否在环境中且下界为
⊥
。这确保它始终是作用域内的变量,且没有其他类型可以是其亚型,从而保证
This
上下文不能通过子类型化命名。
3. 类型检查
部分类型规则如下:
Expression typing: Δ; Γ ⊢e : T
Δ ⊢T ok
Δ; Γ ⊢null : T (T-Null )
Δ; Γ ⊢e : ∃Δ′.N
fType(f, N) = T ′
Δ; Γ ⊢e′ : T
Δ, Δ′ ⊢T <: T ′
Δ; Γ ⊢e.f = e′ : T (T-Assign)
Δ ⊢T , T ok
Δ ⊢∃O →[⊥T ].C<T ,T ,O> ok
Δ; Γ ⊢new C<T ,T ,⋆> : ∃O →[⊥T ].C<T ,T ,O> (T-New )
Class typing: ⊢Q ok
Δ = X→[⊥T u], Owner→[⊥τ o],This→[⊥Owner],O→[⊥τ u]
∅⊢Δ ok
Δ ⊢N, T ok
X = X, O, Owner, This
Δ; this:C<X > ⊢M ok in C
N = D<T ,Owner,This>
Δ ⊢N <: Object<Owner,This>
⊢class C<X T u, O τ u,Owner τ o,This Owner > N{T f; M} ok (T-Class)
对象创建(
T-New
)不接受任何(值)参数,即没有构造函数,运行时所有字段初始化为空值。这需要
null
和
T-Null
规则。以这种方式初始化对象是为了初始化
This
所拥有的字段。
new
表达式中
This
位置的实际类型参数必须始终为
⋆
,因此初始化时不指定实际参数。新对象被赋予存在类型,
This
参数被存在量化(上界为
Owner
参数),确保实际
This
参数不能直接命名。
T-New
中的额外格式良好前提比通常的前提更严格,确保类型参数在没有额外量化参数的环境中格式良好。
添加了标准的类型转换规则(
T-Cast
),与Featherweight Java不同,不区分向上、向下和无意义的类型转换。
在
T-Class
中,强制
This
的声明上界为
Owner
。最后两个前提确保声明的类属于
Object
层次结构,不是
World
的子类型,即不能用作上下文参数,并且
Owner
和
This
参数在继承方面是不变的。这是所有权编码的重要合理性条件,对应于继承和所有权的已知条件。
4. 操作语义
操作语义大多在附录中定义,与Tame FJ最显著的变化在于对象创建:
ι ̸∈dom(H)
fields(C) = f
H′ = H, ι →{C<T,T ,ι>; f→null}
new C<T ,T ,⋆>; H ; ι; H′ (R-New )
新对象的运行时类型(存储在堆中)通过将程序源中使用的
⋆
替换为新对象的地址形成。结合方法调用中的常规替换规则,类声明中
this
和
This
的出现被实例化地址(
ι
)替换,统一了对象的两种表示。结合
T-New
中的量化,对象实际上被封装在存在类型中,对象地址作为见证“类型”。
5. 讨论
所有权类型本质上是依赖的,因为它们反映了对象在堆中的位置。我们证明了所有权类型可以在类似Java的类型系统中编码为参数化类型,类似于幻类型。幻类型是类型参数从不作为类型使用的参数化类型,在Haskell中用于模拟类型中的值,避免了全依赖类型的复杂性和可判定性问题。这也正是我们的系统在处理所有权信息时所做的。因此,在某种程度上,所有权类型系统并不比Java等标准参数化类型系统更复杂。尽管所有权类型具有依赖性,但支持所有权类型系统并不需要全依赖类型的全部能力。然而,不应过度认为类型参数化是所有权类型的唯一或最佳基础模型。
Tame FJOwn可以容纳大部分所有权特性。内部类需要编码,将在下面讨论。最终字段路径在形式系统中不易编码。编码后可以容纳通用宇宙类型。所有权域需要对形式系统进行小扩展,为简单起见我们未做扩展:每个类有一个
This
类型参数列表而非单个参数,每个参数代表一个域。支持所有权域和内部类的扩展相对表面,仅修改类型参数的限制以及
T-New
中隐藏的类型参数。
内部类的编码需要对形式化定义进行小扩展。需要让内部类对象能够访问外部对象及其类型参数。扩展Tame FJOwn可以通过在类表和堆中采用类和对象的嵌套,或为每个类添加指向外部对象的字段以及外部类类型参数的类型参数来实现;对象创建会变得更复杂,但演算本身变化不大。例如,将迭代器作为内部类的示例编码如下:
class Iterator<D, L_Owner, L_This, It_Owner, It_This> {
List<D, L_Owner, L_This> out;
Node<D, L_This, ?> curNode;
Object<It_This, ?> privField;
Object<D, ?> next() {...}
}
class Client<Owner, This> {
<LT> void m(List<World, This, LT> l) {
Iterator<World, This, LT, This, ?> i
= new Iterator<World, This, LT, This, ?>();
i.out = l;
Object<World, ?> first = i.next();
}
}
我们必须为
l
的
This
参数使用(可能经过捕获转换的)类型变量
LT
,为
i
提供
l
的类型参数,并实例化
i
的
out
字段。
我们通过证明进展和保存定理,以常规方式证明了Tame FJOwn的类型安全性。证明大部分遵循Tame FJ,可从相关链接下载。在标准存在类型系统中,见证类型在运行时已知,类型安全性保证不会出现涉及见证类型的类型错误,尽管类型系统在类型检查时仅部分了解这些类型。对于Tame FJOwn采用这种方法意义不大,因为根据
T-New
所有见证类型都是
⋆
。静态类型包含的信息(所有权层次结构)比“见证类型”更多。类型安全性结果证明Tame FJOwn确实强制执行所有权层次结构,即不仅保证严格的类型安全性(类型良好的程序不会访问不存在的字段或方法),还保证对象位于其类型描述的上下文中。所有权信息在运行时通过将对象地址存储在
This
位置(在
R-New
中)表示,地址通过替换(在
R-Invk
中)传播到其他所有权位置。
在证明Tame FJOwn的类型安全性时,我们证明了单阶段类型检查器(对应于预处理器和Java类型检查器的集成)的安全性,而非两阶段类型检查器(对应于预处理然后进行Java类型检查)的安全性。这种方法理论上更直接,反映了我们对技术长期使用的设想。
以下是一个简单的流程图,展示对象创建的流程:
graph TD;
A[开始] --> B{ι是否在H的定义域内};
B -- 否 --> C[获取类C的字段f];
C --> D[更新堆H为H'];
D --> E[创建新对象];
B -- 是 --> F[结束];
E --> F;
6. 实现
我们使用本文描述的技术实现了支持所有权类型和通用宇宙类型的Java编译器。实现是简单的源到源翻译器,将源代码转换为普通Java代码,然后使用Java编译器进行类型检查和编译。大多数类型错误由Java编译器捕获,只有少数由翻译器处理。翻译器是JKit Java编译器解析器和AST元素的扩展。我们一次编码一个类,无需了解整个程序。生成的类可以协同工作,但与普通Java类不兼容。
我们的方法支持在几乎整个Java语言上使用所有权和宇宙类型,包括泛型、数组(仅支持数组元素的所有权信息,数组本身像基本类型一样不被认为有所有者)、接口、内部类(不包括匿名类)、静态成员和通配符。
实现只是原型,工业级编译器会将编码与Java类型检查集成,而不是采用两阶段过程。集成可以提供有意义的错误消息,并支持效果和封装属性。此外,要使语言可用,不仅需要编译器,还需要支持库,可通过支持不了解所有权的类(目前所有类必须使用所有权类型编写)或生成一组带有所有权注释的库(或两者结合)来实现。编译器可从相关链接下载。
7. 所有权类型
源语法与本文使用的大多相似。支持所有者、上下文参数、正交泛型、上下文和类型参数化方法、将最终方法参数作为上下文(用于动态别名)、以上下文通配符形式的存在量化,以及可以访问外部对象上下文和上下文参数的内部类。不支持将局部变量(方法参数除外)或字段作为上下文。支持标准类型转换,包括转换为通配符所有者,但不直接支持“存在所有者”。由于Java使用类型擦除实现泛型,类型转换的运行时检查不能确保所有权参数的正确性;对于所有权类型,类型转换仅确保程序在编译时可以通过类型检查。这在我们的形式化中不是问题,因为操作语义中不擦除类型参数。
编译器的主要功能是去除所有者和上下文参数,并用类型参数替换,在类声明和类型中都如此;在类型中,在
This
位置使用通配符。
Java在对象实例化时不允许使用通配符参数,为解决这个问题,我们在
This
位置使用
Owner
类型参数(因为它是唯一满足声明边界的类型参数),并立即转换为所需的通配符类型(继承上界),示例如下:
new world:Object() // 源语法
new Object<World, ?>() // 伪Java
(OwnedObject<World, ?>) new OwnedObject<World, World>() // Java
此外,如OGJ一样,我们需要在类层次结构的根添加一个继承自
Object
的
OwnedObject
类来存储编码的所有权参数。所有类必须继承这个类(而不是可能隐式继承的
Object
),所有
Object
的使用都要改为
OwnedObject
。在源语法中,对象的所有者在
extends
子句中是隐式的,因此超类类型的翻译必须与其他类型不同处理。由于添加了
OwnedObject
和
World
到运行时,必须在每个编码的类文件中导入这些类。
8. 通用宇宙类型
源语法对于通用宇宙很标准,例如
rep List<any Object>
。翻译比所有权类型简单,因为只需翻译类型,无需翻译上下文参数。面临的大多数问题与所有权类型类似但更简单:必须检查所有类型的宇宙修饰符(但不在
extends
子句中,因为所有权在继承方面是不变的,即超类必须是同级的),将
Object
翻译为
OwnedObject
,并注意数组类型。与所有权类型实现和Java一样,类型转换不安全,可能会不正确地更改宇宙修饰符;宇宙通常需要安全的向下转换。
9. 结论与未来工作
我们展示了如何使用Java泛型和通配符对所有权类型、通用宇宙类型、所有权域以及一系列所有权类型系统扩展进行编码。关键概念包括将上下文参数表示为类型参数、将
this
具体化作为类型参数、使用通配符隐藏
this
以及由此创建的幻所有权层次结构。这些进展为所有权类型的类型理论基础提供了见解,并为基于现有技术构建实用编译器提供了途径。
未来工作的主要方向是在形式工作和编译器中支持将所有者作为支配者以及其他封装策略和效果。这需要将翻译编译器与现有Java编译器集成,以提供更好的错误消息和更高效的类型检查。我们还希望对带有所有权类型信息的库进行编码,供编译器使用。另一种选择是改进编码,使编码类和未注释的Java库能够相互交互。
以下是支持的特性和不支持的特性列表:
| 支持特性 | 不支持特性 |
| — | — |
| 所有者、上下文参数 | 局部变量(方法参数除外)作为上下文 |
| 正交泛型 | 字段作为上下文 |
| 上下文和类型参数化方法 | “存在所有者” |
| 最终方法参数作为上下文 | |
| 上下文通配符形式的存在量化 | |
| 可访问外部对象上下文的内部类 | |
| 标准类型转换 | |
通过这些实现和研究,我们在Java中对所有权类型和相关概念的编码取得了一定进展,但仍有许多工作可以进一步完善和拓展。
Java中所有权类型的编码实现
10. 技术优势总结
- 类型系统集成 :将所有权类型编码为参数化类型,与Java类型系统深度融合,使得在Java编程中可以自然地引入所有权概念,而无需对现有类型系统进行大规模改造。
- 模拟与简化 :借鉴幻类型的思想,避免了全依赖类型的复杂性和可判定性问题,在处理所有权信息时更加高效和简洁。
- 广泛支持 :支持在几乎整个Java语言特性上使用所有权和宇宙类型,包括泛型、数组、接口等,具有很强的通用性。
11. 操作步骤梳理
下面详细梳理从源代码到最终编译运行的操作步骤:
1.
源代码编写
:使用支持的源语法编写包含所有权类型和通用宇宙类型的Java代码。例如:
class Iterator<D, L_Owner, L_This, It_Owner, It_This> {
List<D, L_Owner, L_This> out;
Node<D, L_This, ?> curNode;
Object<It_This, ?> privField;
Object<D, ?> next() {...}
}
class Client<Owner, This> {
<LT> void m(List<World, This, LT> l) {
Iterator<World, This, LT, This, ?> i
= new Iterator<World, This, LT, This, ?>();
i.out = l;
Object<World, ?> first = i.next();
}
}
-
源到源翻译
:使用实现的源到源翻译器将源代码转换为普通Java代码。在这个过程中,去除所有者和上下文参数,并用类型参数替换,在
This位置使用通配符。例如:
new world:Object() // 源语法
new Object<World, ?>() // 伪Java
(OwnedObject<World, ?>) new OwnedObject<World, World>() // Java
- Java编译 :使用Java编译器对翻译后的普通Java代码进行类型检查和编译。大多数类型错误由Java编译器捕获,只有少数由翻译器处理。
- 运行程序 :编译成功后,运行生成的Java程序。
12. 未来工作展望
未来工作将围绕以下几个关键方向展开:
-
集成现有编译器
:将翻译编译器与现有Java编译器集成,实现更高效的类型检查和更有意义的错误消息反馈。具体流程如下:
graph LR;
A[源代码] --> B[翻译编译器];
B --> C[集成Java编译器];
C --> D[类型检查];
D -- 通过 --> E[编译];
D -- 不通过 --> F[错误反馈];
F --> A;
E --> G[生成可运行程序];
-
库的支持
:为了使语言更具实用性,需要支持库。可以通过两种方式实现:
- 支持不了解所有权的类,使得现有的普通Java类也能在新的环境中使用。
- 生成一组带有所有权注释的库,为开发者提供更多的选择。
- 编码改进 :改进编码方式,使编码类和未注释的Java库能够更好地相互交互,打破现有兼容性的限制。
13. 总结与建议
通过上述对Java中所有权类型编码实现的介绍,我们可以看到这种技术为Java编程带来了新的特性和能力。然而,目前的实现还处于原型阶段,存在一些局限性,如与普通Java类的兼容性问题、对某些特性的支持不足等。
对于开发者来说,如果想要尝试使用所有权类型和通用宇宙类型,可以按照以下建议进行:
-
学习源语法
:熟悉支持的源语法,包括所有者、上下文参数、正交泛型等概念的使用方法。
-
使用编译器
:下载并使用提供的编译器进行代码的翻译和编译,注意编译器的使用说明和限制。
-
关注未来发展
:密切关注该技术的未来发展,特别是集成现有编译器、支持库等方面的进展,以便在更完善的环境中使用。
总之,Java中所有权类型的编码实现是一项有潜力的技术,随着不断的发展和完善,有望为Java编程带来更多的便利和创新。
以下是一个对比表格,展示当前实现与工业级需求的差距:
| 对比项 | 当前实现 | 工业级需求 |
| — | — | — |
| 类型检查 | 两阶段过程,部分类型错误由翻译器处理 | 集成式类型检查,提供有意义错误消息 |
| 兼容性 | 与普通Java类不兼容 | 与各种Java类良好交互 |
| 库支持 | 需所有类使用所有权类型编写 | 支持不了解所有权的类和注释库 |
| 错误处理 | 简单处理 | 详细、有针对性的错误反馈 |
通过这些对比,我们可以清晰地看到未来需要改进的方向,为进一步的研究和开发提供参考。
超级会员免费看
2143

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



