外观
一种面向时空可组合性的编程范式
约 140 字小于 1 分钟
论文中英对照编程范式可组合性
阅读说明 · 对照版使用提示
中英对照版:每个中文段落上方灰底小字即为对应英文原文,逐段对照。悬停任一侧句子,另一侧对应片段同步高亮;单击可锁定,按 Esc 取消。按 / 可直接搜索正文。
一种面向时空可组合性的编程范式
A Programming Paradigm for Spatiotemporal Composability
Yifan Shi1,2, Wei Zhang1, Tianyi Cui2
Yifan Shi¹,²,Wei Zhang¹,Tianyi Cui²
1Peking University 2DeepSeek-AI
¹北京大学 ²DeepSeek-AI
摘要
Abstract
Modern software—from plugin systems to self-evolving agent harnesses—increasingly requires dynamic composition, yet its formal foundations remain underdeveloped. We identify two orthogonal dimensions of the problem: temporal composability, the ability to completely revert a component’s side effects upon removal, and spatial composability, the ability to declare and reactively manage inter-component dependencies. We address the two dimensions by lifting classical effect and coeffect concepts to runtime mechanisms. In particular, we formalize revertible effects, in which every context transformation carries an inverse that the runtime holds, establishing temporal composability local to one component. We formalize reactive coeffects, in which every context change is classified against a component’s coeffect specification to drive its activation and deactivation, establishing spatial composability local to one component. We then unify the effect context and the coeffect context into a single context type and mediate every effect and coeffect through it, yielding a discipline we call the context paradigm; the mediation induces an observational equivalence up to which the effects of distinct components interleave without disturbing one another. Combining these mechanisms into the notion of a component, we give a calculus of dynamic composition whose metatheory carries spatiotemporal compos- ability from a single component to a whole system of interleaved components. We implement these ideas in Cordis, a meta-framework of spatiotemporal composability that provides a core library with effect tracking and coeffect resolution, as well as a declarative component loader with configuration reconciliation and hot module replacement. 1
现代软件——从插件系统到自演化的智能体运行时(agent harness)——日益需要动态组合,然而其形式化基础仍不完善。我们识别出该问题的两个正交维度:时间可组合性,即在一个组件被移除时能够完全撤销其副作用;以及空间可组合性,即能够声明并以响应式方式管理组件间的依赖关系。我们通过将经典的效应与余效应概念提升为运行时机制来应对这两个维度。具体地,我们形式化了可撤销效应,其中每一次上下文变换都携带一个由运行时持有的逆操作,从而确立了局限于单个组件的时间可组合性。我们形式化了响应式余效应,其中上下文的每一次变化都对照组件的余效应规约进行分类,以驱动该组件的激活与停用,从而确立了局限于单个组件的空间可组合性。随后,我们将效应上下文与余效应上下文统一为单一的上下文类型,并经由它中介每一个效应与余效应,由此得到一种我们称之为上下文范式的规范;这种中介诱导出一个观测等价,在该等价意义下,不同组件的效应相互交错而不彼此干扰。将这些机制组合为组件这一概念,我们给出了一个动态组合演算,其元理论把时空可组合性从单个组件提升到由相互交错的组件构成的整个系统。我们在 Cordis 中实现了这些思想,Cordis 是一个面向时空可组合性的元框架,它提供一个具备效应追踪与余效应求解的核心库,以及一个具备配置调和与模块热替换的声明式组件加载器。
目录
Contents
1. 引言
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.1. Dimensions of Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2. Motivating Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2.1. Plugin Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2.2. Self-Evolving Agent Harnesses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.3. The Coarse-Grained Workaround . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3. Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2. Preliminaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1. Effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2. Coeffects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3. Relationship to Dynamic Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3. Revertible Effects and Reactive Coeffects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.1. Revertible Effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.1.1. Effect Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.1.2. Effect Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.1.3. Effect Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.2. Reactive Coeffects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.2.1. Coeffect Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2.2. Specification and Notification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.2.3. Isolation and Interception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3. The Context Paradigm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.3.1. Unified Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.3.2. Observational Equivalence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.4. Attaining Independence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4.1. Effect Independence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4.2. Coeffect Commutativity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4. A Calculus of Dynamic Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.1. Components and Fibers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.2. The Calculus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.2.1. Orchestration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.2.2. Lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.2.3. Confinement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.3. Metatheory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2 4.3.1. Preservation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.3.2. Temporal Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 4.3.3. Spatial Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.3.4. Progress . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 4.3.5. Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.4. Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5. Implementation and Case Study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 5.1. Core Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 5.1.1. Effect Tracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 5.1.2. Coeffect Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 5.1.3. Component Lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.1.4. Context Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.2. Component Loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.2.1. Declarative Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 5.2.2. Hot Module Replacement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 5.3. Case Study: Koishi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 6. Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.1. System Boundary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.2. Service Multiplexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 6.3. Access Control and Sandboxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.4. Language Independence and Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.5. Mutual Dependencies and Component Granularity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 6.6. Dependency Typing and Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 6.7. Co-Design with Languages and Operating Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 7. Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 7.1. Effect and Coeffect Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 7.2. Programming Paradigms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 7.3. Temporal Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 7.4. Spatial Composability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 8. Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 3 1. Introduction
Composition—assembling complex systems from simpler parts—is a foundational principle of software engineering [1]. Traditionally, composition is static: function calls, module imports, and class inheritance are resolved at compile time and remain fixed throughout execution. However, modern software increasingly demands dynamic composition, where components are loaded, unloaded, and reconfigured at runtime. Plugin architectures [2] and self-evolving agent harnesses both require systems that can safely add and remove functionality on the fly, yet current practice defers to coarse-grained mechanisms [3] that reconfigure only by restarting, discarding runtime state. Despite the growing practical importance of dynamic composition, its theoretical foundations remain underdeveloped, compared to the rich formal frameworks available for static composition.
组合——由较简单的部分装配出复杂系统——是软件工程的一项基本原则 [1]。传统上,组合是静态的:函数调用、模块导入与类继承都在编译期完成解析,并在整个执行过程中保持不变。然而,现代软件日益要求动态组合,即组件在运行时被加载、卸载和重新配置。插件体系结构 [2] 与自演化智能体运行时都要求系统能够在运行过程中安全地增删功能,但当前的实践却诉诸粗粒度机制 [3],只能通过重启来完成重新配置,并丢弃运行时状态。尽管动态组合的实践重要性不断上升,但与静态组合所拥有的丰富形式化框架相比,其理论基础仍不完善。
1.1. 可组合性的维度
1.1. Dimensions of Composability
To characterize the requirements of dynamic composition, we identify two orthogonal dimen- sions beyond the well-studied algebraic aspects of composition:
为了刻画动态组合的需求,我们在组合已被充分研究的代数层面之外,识别出两个正交的维度:
- • Temporal composability addresses the time dimension: upon removal of a component, the modifications the component made to the shared environment must be completely and safely reversed. This requires tracking every resource allocation, event registration, and state mutation the component performs, and guaranteeing their orderly reclamation upon removal.
时间可组合性处理时间维度:在一个组件被移除时,该组件对共享环境所做的修改必须被完全且安全地逆转。这需要追踪该组件执行的每一次资源分配、事件注册与状态变更,并保证它们在组件移除时得到有序回收。
- • Spatial composability addresses the space dimension: components must be able to declare, discover, and resolve their dependencies on one another in a structured and verifiable manner. This requires managing dependency topology and coordinating com- ponent lifecycles in response to dependency changes.
空间可组合性处理空间维度:组件必须能够以结构化且可验证的方式,声明、发现并解析彼此之间的依赖。这需要管理依赖拓扑,并针对依赖的变化协调组件的生命周期。
In the static setting, temporal composability reduces to lexical scoping (e.g., RAII [4], bracket patterns [5]), and spatial composability reduces to module import resolution [6]. In the dynamic setting, where components arrive and depart at runtime, both dimensions become significantly harder: temporal composability must handle long-lived, stateful effects whose scope is not lexically bounded; and spatial composability must handle dependencies that appear, disappear, or change identity during execution.
在静态情形下,时间可组合性退化为词法作用域(例如 RAII [4]、bracket 模式 [5]),空间可组合性退化为模块导入解析 [6]。在动态情形下,组件于运行时到来与离去,两个维度都显著变难:时间可组合性必须处理作用域不受词法约束的长生命周期的有状态效应;空间可组合性则必须处理在执行过程中出现、消失或改变身份的依赖。
1.2. 动机示例
1.2. Motivating Examples
1.2.1. 插件系统
1.2.1. Plugin Systems
Plugin systems are a canonical instance of dynamic composition. We use Visual Studio Code (VSCode), one of the most widely-used extensible IDEs, as a representative example.
插件系统是动态组合的一个典型实例。我们以 Visual Studio Code(VSCode)——使用最广泛的可扩展 IDE 之一——作为代表性示例。
Temporal limitation. VSCode runs all extensions in a shared process called the extension host. Although extensions can be installed dynamically, this host provides no mechanism to unload an individual extension’s code at runtime. Once an extension’s activate function has executed, disabling or uninstalling it requires restarting the entire host, affecting all loaded extensions. Purely declarative extensions such as themes, keybindings, and snippets carry no 4 code and can be removed freely. Among the top 100 extensions by install count, however, 87 contain executable code 1 and will therefore require such a restart upon removal. Although VSCode provides a deactivate hook, it serves only as a graceful shutdown callback during the host process’ termination, and thus does not enable live removal. Moreover, the hook separates effect disposal from effect creation (in activate), violating locality of concern and making complete cleanup difficult to verify.
时间维度的局限。 VSCode 在一个称为扩展宿主的共享进程中运行所有扩展。尽管扩展可以被动态安装,但该宿主并未提供在运行时卸载单个扩展代码的机制。一旦某个扩展的 activate 函数执行过,禁用或卸载它就需要重启整个宿主,从而影响所有已加载的扩展。主题、键位绑定与代码片段这类纯声明式扩展不包含代码,可以被自由移除。然而,在安装量排名前 100 的扩展中,有 87 个包含可执行代码¹,因此在移除时也将需要这样一次重启。虽然 VSCode 提供了 deactivate 钩子,但它仅充当宿主进程终止时的优雅关闭回调,因而无法实现在线移除。此外,该钩子把效应的处置与效应的创建(在 activate 中)分离开来,违背了关注点局部性,并使完整清理难以验证。
Spatial limitation. VSCode does provide extensionDependencies for declaring dependencies between extensions, but it sees little use: among the top 100 extensions by install count, only 7 declare extensionDependencies on non-built-in extensions. 1 This scarcity reflects the shape of the extension API, which exposes fixed, surface-level extension points such as commands, views, and language features. Extensions contribute to the host through these points rather than depending on one another, so inter-extension dependencies rarely arise. Moreover, VSCode’s mechanism for inter-extension interaction provides no structural contract: it exposes an extension’s functionality to others through vscode.extensions.getExtension(...).exports, but the returned value is untyped (any by default), so the dependent cannot rely on a checked interface. In short, VSCode steers extensions toward a fixed set of host-provided extension points, and offers no safe, structured way for them to depend on one another.
空间维度的局限。 VSCode 确实提供了 extensionDependencies 用于声明扩展之间的依赖,但它很少被使用:在安装量排名前 100 的扩展中,只有 7 个对非内置扩展声明了 extensionDependencies。¹ 这种稀缺性反映了扩展 API 的形态——它只暴露固定的、表面层次的扩展点,例如命令、视图与语言特性。扩展通过这些扩展点对宿主作出贡献,而不是相互依赖,因此扩展间的依赖很少出现。此外,VSCode 的扩展间交互机制不提供任何结构性契约:它通过 vscode.extensions.getExtension(...).exports 把一个扩展的功能暴露给其他扩展,但返回值是无类型的(默认为 any),因此依赖方无法依赖一个受检接口。简而言之,VSCode 把扩展引导到一组由宿主提供的固定扩展点上,而没有为它们之间的相互依赖提供安全、结构化的方式。
These two limitations are not unique to VSCode; they recur across plugin systems generally [2, 7], differing only in degree.
这两种局限并非 VSCode 所独有;它们普遍地重现在各类插件系统中 [2, 7],只是程度不同。
脚注 1:数据于 2026 年 6 月 9 日取自 Visual Studio Code Marketplace。
1.2.2. 自演化智能体运行时
1.2.2. Self-Evolving Agent Harnesses
Modern AI agents rely on runtime agent harnesses [8–10]. These systems may compose diverse tool suites [11] and execution environments, govern permissions and sandboxing, maintain session state and persistence, provide context management and memory systems [12], orches- trate subagents and multi-agent workflows [13], and expose interfaces to users and automation. A future harness may generate and deploy modifications to its own components while contin- uously serving requests. Model-synthesized reusable tools provide a narrower precursor to component-level self-modification [14]. Each such modification is itself an instance of dynamic composition.
现代 AI 智能体依赖运行时的智能体运行时(agent harness)[8–10]。这些系统可能组合多样的工具集 [11] 与执行环境,管理权限与沙箱化,维护会话状态与持久化,提供上下文管理与记忆系统 [12],编排子智能体与多智能体工作流 [13],并向用户与自动化流程暴露接口。未来的智能体运行时可能在持续服务请求的同时,生成并部署对其自身组件的修改。模型合成的可复用工具为组件级的自我修改提供了一条更窄的前驱路径 [14]。每一次此类修改本身都是动态组合的一个实例。
Because these modifications occur continuously and with limited or no human oversight, dynamic composability becomes indispensable. Without temporal composability, each self- modification forces a full restart that discards all process-local accumulated state; at such frequency the cumulative unavailability becomes substantial, and in-flight tasks are disrupted repeatedly; even worse, a faulty self-modification can disable the very process needed to recover. Without spatial composability, each module must itself detect and adapt to changes in the modules it depends on as they appear, disappear, or change identity, and can do so only by ad hoc means; even worse, a naive code-replacement strategy may silently break dependents or introduce circular dependencies that surface only at reload time.
由于这些修改持续发生,并且只有有限的人工监督或完全没有人工监督,动态可组合性变得不可或缺。没有时间可组合性,每一次自我修改都迫使系统完整重启,从而丢弃所有进程局部累积的状态;以这样的频率,累积的不可用时间变得相当可观,正在执行中的任务被反复打断;更糟的是,一次有缺陷的自我修改可能使恢复所必需的那个进程本身失效。没有空间可组合性,每个模块都必须自行检测并适应其依赖模块在出现、消失或改变身份时发生的变化,而且只能通过临时手段做到这一点;更糟的是,朴素的代码替换策略可能悄无声息地破坏依赖方,或引入仅在重载时才显现的循环依赖。
1.2.3. 粗粒度变通方案
1.2.3. The Coarse-Grained Workaround
One reason dynamic composability has received limited formal attention is that operating systems and container orchestrators already provide a coarse-grained substitute. Operating systems yield temporal composability at the granularity of a process; container orchestrators 1Data retrieved from the Visual Studio Code Marketplace on June 9, 2026. 5 [3] yield spatial composability at the granularity of a service. In practice, most software tolerates the lack of fine-grained composability by deferring to these coarse-grained mechanisms: a misbehaving module is handled by restarting the process, and a service dependency is managed by the container orchestrator.
动态可组合性之所以只受到有限的形式化关注,一个原因是操作系统与容器编排器已经提供了一种粗粒度的替代方案。操作系统以进程为粒度提供时间可组合性;容器编排器 [3] 以服务为粒度提供空间可组合性。在实践中,大多数软件通过诉诸这些粗粒度机制来容忍细粒度可组合性的缺失:一个行为异常的模块通过重启进程来处理,一项服务依赖则交由容器编排器管理。
However, this workaround imposes substantial costs. Temporally, each restart discards all process-local accumulated state (e.g., caches, connections, partial computations), and rebuild- ing it takes seconds to minutes [15]; maintaining availability in the interim requires redundant replicas, incurring resource overhead to compensate for the inability to recover a single component. Spatially, container-level orchestration cannot express dependencies between com- ponents sharing an address space, and introduces network overhead for interactions that could be local function calls. Both mechanisms operate at the boundary of processes and containers, yet modern systems increasingly compose at a finer level. This granularity mismatch demands a compositional abstraction that manages effects and dependencies at the same level as the components themselves.
然而,这种变通方案带来可观的代价。在时间维度上,每次重启都会丢弃所有进程局部累积的状态(例如缓存、连接、部分完成的计算),而重建它们需要数秒到数分钟 [15];在此期间维持可用性需要冗余副本,从而为了弥补无法恢复单个组件这一缺陷而招致资源开销。在空间维度上,容器级的编排无法表达共享同一地址空间的组件之间的依赖,并且为本可以是局部函数调用的交互引入了网络开销。这两种机制都作用于进程与容器的边界,而现代系统却日益在更细的层次上进行组合。这种粒度错配要求一种组合式抽象,能够在与组件本身相同的层次上管理效应与依赖。
1.3. 贡献
1.3. Contributions
The two dimensions of dynamic composability concern, respectively, how computations modify and how they depend on their environment. These two directions are what effect systems [16, 17] and coeffect systems [18, 19] formalize: effects provide the formal vocabulary for reasoning about environmental modifications, and coeffects for reasoning about environmental requirements. However, existing formulations restrict reasoning to compile-time analysis over lexically fixed scopes, and do not extend to dynamic scenarios where components arrive and depart at runtime. By lifting effects to a revertible runtime model and coeffects to a reactive dependency resolution mechanism, we obtain a unified formal foundation for dynamic composability, one that is language-agnostic and applicable to any software architecture requiring dynamic composition. We make the following contributions:
动态可组合性的两个维度分别关注计算如何修改其所处的环境,以及它们如何依赖于环境。这两个方向正是效应系统 [16, 17] 与余效应系统 [18, 19] 所形式化的内容:效应为关于环境修改的推理提供形式化词汇,余效应则为关于环境需求的推理提供形式化词汇。然而,现有的表述把推理限制在编译期对词法固定作用域的分析上,并不能扩展到组件于运行时到来与离去的动态场景。通过将效应提升为可撤销的运行时模型、将余效应提升为响应式的依赖解析机制,我们获得了动态可组合性的统一形式化基础,它是语言无关的,并适用于任何需要动态组合的软件体系结构。我们作出如下贡献:
- 1. We formalize revertible effects (Section 3.1): every context transformation carries an explicit inverse that the runtime holds, and both tracking and recovery preserve composition, so the context is recovered upon component removal. This establishes local temporal composability.
我们形式化可撤销效应(第 3.1 节):每一次上下文变换都携带一个由运行时持有的显式逆操作,追踪与恢复都保持组合性,因此上下文在组件移除时被恢复。这确立了局部的时间可组合性。
- 2. We formalize reactive coeffects ( Section 3.2): a component declares the coeffects it requires as a specification, and each change of the context is classified against that speci- fication as activating, deactivating, or neutral, driving the component’s activation and deactivation. This establishes local spatial composability.
我们形式化响应式余效应(第 3.2 节):组件把它所要求的余效应声明为一份规约,上下文的每一次变化都对照该规约被分类为激活、停用或中性,从而驱动组件的激活与停用。这确立了局部的空间可组合性。
- 3. We introduce the context paradigm (Section 3.3): the effect context and the coeffect con- text are unified into a single context type, every effect and coeffect is mediated through it, and the mediation induces an observational equivalence up to which the effects of distinct components attain independence.
我们引入上下文范式(第 3.3 节):效应上下文与余效应上下文被统一为单一的上下文类型,每一个效应与余效应都通过它来中介,这种中介诱导出一个观测等价,在该等价之下不同组件的效应获得独立性。
- 4. We develop a calculus of dynamic composition ( Section 4), which combines the two mechanisms into the notion of a component and gives them an operational semantics. The metatheory then carries spatiotemporal composability from a single component to a whole system of interleaved components.
我们发展了一个动态组合演算(第 4 节),它把这两种机制组合为组件这一概念,并赋予它们一套操作语义。其元理论随后把时空可组合性从单个组件提升到由相互交错的组件构成的整个系统。
- 5. We implement these ideas in Cordis (Section 5), a meta-framework of spatiotemporal composability that provides a core library realizing the formal model with effect tracking and coeffect resolution, as well as a declarative component loader with configuration reconciliation and hot module replacement. 6
我们在 Cordis 中实现这些思想(第 5 节),Cordis 是一个面向时空可组合性的元框架,它提供一个实现了该形式化模型、具备效应追踪与余效应求解的核心库,以及一个具备配置调和与模块热替换的声明式组件加载器。
2. 预备知识
2. Preliminaries
This section provides a concise overview of effect and coeffect systems—the two theoretical pillars underlying our work. We assume familiarity with basic type theory and category theory; the goal here is to fix notation and introduce the key abstractions that Section 3 will operationalize as runtime mechanisms.
本节简要概述效应系统与余效应系统——本文工作的两块理论基石。我们假定读者熟悉基础类型论与范畴论;此处的目的是固定记号,并引入若干关键抽象,第 3 节将把它们落实为运行时机制。
2.1. 效应
2.1. Effects
In the simply typed lambda calculus (STLC) [20, 21], a typing judgment Γ ⊢ 𝑡 : 𝑇 states that term 𝑡 has type 𝑇 under context Γ. An effect system refines the type to describe what side effects a computation may produce, yielding judgments of the form
在简单类型 lambda 演算(STLC)[20, 21] 中,类型判定 Γ ⊢ 𝑡 : 𝑇 表示项 𝑡 在上下文 Γ 下具有类型 𝑇。效应系统对类型加以精化,用以描述一个计算可能产生哪些副作用,从而得到如下形式的判定
Γ ⊢ 𝑡 : 𝑇effect (1)
Γ ⊢ 𝑡 : 𝑇effect (1)
Here, the result type is annotated with an element of an effect algebra that describes which side effects the computation may produce, enabling compositional reasoning about stateful computations. This approach originates with Lucassen and Gifford [22], who introduced a kinded type system distinguishing types, effects, and regions to discover scheduling constraints in parallel programs.
此处,结果类型被标注上一个效应代数的元素,该元素描述该计算可能产生哪些副作用,从而使得关于有状态计算的组合式推理成为可能。这一思路源自 Lucassen 与 Gifford [22],他们提出了一种带种类的类型系统,区分类型、效应与区域,用以发掘并行程序中的调度约束。
Monadic effects. Moggi [16] first modeled computational effects categorically via monads; Wadler [23] popularized the approach in Haskell. A monad (𝑇, 𝜂, 𝜇) on a category 𝒞︀ encap- sulates an effectful computation as a value of type 𝑇(𝐴), with 𝜂 : 𝐴 → 𝑇(𝐴) lifting pure values and 𝜇 : 𝑇(𝑇(𝐴)) → 𝑇(𝐴) sequencing nested computations. Classic instances include the Maybe monad (for partiality), State monad (for mutable state), and IO monad (for external interaction).
单子式效应。 Moggi [16] 首次通过单子在范畴论层面为计算效应建模;Wadler [23] 在 Haskell 中推广了这一思路。范畴 𝒞︀ 上的单子 (𝑇, 𝜂, 𝜇) 将一个带效应的计算封装为类型 𝑇(𝐴) 的值,其中 𝜂 : 𝐴 → 𝑇(𝐴) 提升纯值,𝜇 : 𝑇(𝑇(𝐴)) → 𝑇(𝐴) 对嵌套计算定序。经典实例包括 Maybe 单子(刻画部分性)、State 单子(刻画可变状态)与 IO 单子(刻画外部交互)。
Algebraic effects. Plotkin and Power [17, 24] showed that algebraic operations determine monads, establishing a framework in which effect interfaces are decoupled from their implementations. An effect signature Σ declares a set of operations (e.g., get : () → 𝑆, put : 𝑆 → () for state); programs invoke operations freely without committing to a particular interpretation. Plotkin and Pretnar [25] subsequently introduced effect handlers, which interpret operations by providing continuation semantics:
代数效应。 Plotkin 与 Power [17, 24] 证明代数运算决定单子,从而确立了这样一个框架:效应接口与其实现相解耦。效应签名 Σ 声明一组运算(例如状态所对应的 get : () → 𝑆、put : 𝑆 → ());程序可以自由调用这些运算,而不必事先承诺某种特定的解释。Plotkin 与 Pretnar [25] 随后引入效应处理器,通过提供延续语义来解释这些运算:
handle 𝑒 with { op(𝑣, 𝜅) ↦ … } (2)
handle 𝑒 with { op(𝑣, 𝜅) ↦ … } (2)
The handler receives the operation argument 𝑣 and the delimited continuation 𝜅, which it may invoke zero, one, or multiple times, enabling exceptions, coroutines, and non-determinism within a uniform framework [26]. Languages such as Koka [27, 28], Eff [29], and OCaml 5 [30] have adopted algebraic effects with varying design trade-offs.
处理器接收运算参数 𝑣 与定界延续 𝜅,并可将其调用零次、一次或多次,从而在同一个框架内实现异常、协程与非确定性 [26]。Koka [27, 28]、Eff [29] 与 OCaml 5 [30] 等语言以各自不同的设计取舍采纳了代数效应。
2.2. 余效应
2.2. Coeffects
Dually to effects, a coeffect system [18, 31] enriches the context rather than the type, yielding judgments of the form
与效应相对偶,余效应系统 [18, 31] 精化的是上下文而非类型,得到如下形式的判定
Γcoeffect ⊢ 𝑡 : 𝑇 (3)
Γcoeffect ⊢ 𝑡 : 𝑇 (3)
Here, the context is annotated with an element of a coeffect algebra describing what the computation requires from its environment, such as resources to access, permissions to hold, 7 or services to depend on. While effects model a program’s impact on the world, coeffects model the world’s constraints on the program.
此处,上下文被标注上一个余效应代数的元素,该元素描述该计算对其环境有何要求,例如需要访问的资源、需要持有的权限,或者需要依赖的服务。效应刻画程序对世界的影响,而余效应刻画世界对程序的约束。
Comonadic coeffects. The idea of using comonads to structure context-dependent computation was first developed by Uustalu and Vene [32], who proposed symmetric (semi)monoidal comonads as the dual of Moggi’s monadic framework for effects, capturing notions such as dataflow and attribute evaluation. Petricek et al. [18] built on this foundation to propose coeffects as a unified static analysis of context-dependence. A comonad (𝐷, 𝜀, 𝛿) captures context-dependent computation: 𝜀 : 𝐷(𝐴) → 𝐴 extracts the current value from a con- text, and 𝛿 : 𝐷(𝐴) → 𝐷(𝐷(𝐴)) duplicates context for nested access. The Environment comonad 𝐷(𝑋) = 𝐸 × 𝑋 models dependence on a fixed environment 𝐸; the Stream comonad 𝐷(𝑋) = ℕ → 𝑋 models dependence on temporal data.
余单子式余效应。 用余单子来组织依赖上下文的计算,这一思想最早由 Uustalu 与 Vene [32] 发展,他们提出对称(半)幺半余单子作为 Moggi 效应单子框架的对偶,用以刻画数据流与属性求值等概念。Petricek 等人 [18] 在这一基础上提出余效应,将其作为上下文依赖的一种统一静态分析。余单子 (𝐷, 𝜀, 𝛿) 刻画依赖上下文的计算:𝜀 : 𝐷(𝐴) → 𝐴 从上下文中抽取当前值,𝛿 : 𝐷(𝐴) → 𝐷(𝐷(𝐴)) 复制上下文以供嵌套访问。环境余单子 𝐷(𝑋) = 𝐸 × 𝑋 刻画对固定环境 𝐸 的依赖;流余单子 𝐷(𝑋) = ℕ → 𝑋 刻画对时序数据的依赖。
Graded coeffects. For finer-grained tracking, graded coeffect systems use a pre-ordered semiring 𝒮︀ = (𝑆, ≤, +, ×, 0, 1) as the coeffect algebra [33], a discipline later unified with graded effects by Gaboardi et al. [19]. Elements of 𝑆 annotate each variable binding to quantify its usage: 0 for unused, 1 for linear use, 𝑛 for bounded use, ∞ for unrestricted use. The semiring operations compose coeffects sequentially (×) and in parallel ( +), enabling precise resource tracking, sensitivity analysis [34], and information-flow control [35, 36] within a unified alge- braic framework [37].
分级余效应。 为了进行更细粒度的追踪,分级余效应系统使用预序半环 𝒮︀ = (𝑆, ≤, +, ×, 0, 1) 作为余效应代数 [33];Gaboardi 等人 [19] 后来将这一规范与分级效应统一起来。𝑆 的元素标注每一个变量绑定,以量化它的使用方式:0 表示未使用,1 表示线性使用,𝑛 表示有界使用,∞ 表示无限制使用。半环运算分别按序贯方式(×)与并行方式(+)组合余效应,从而在统一的代数框架 [37] 内实现精确的资源追踪、敏感性分析 [34] 与信息流控制 [35, 36]。
2.3. 与动态可组合性的关系
2.3. Relationship to Dynamic Composability
Effect and coeffect systems organize reasoning about computation along two complementary directions: effects describe how a computation modifies its environment, whereas coeffects describe how it depends on its environment. These two directions correspond to the two dimen- sions of dynamic composability identified in Section 1:
效应系统与余效应系统沿两个互补的方向组织关于计算的推理:效应描述一个计算如何改变其环境,而余效应描述它如何依赖于其环境。这两个方向正对应于第 1 节所指出的动态可组合性的两个维度:
- • Temporal composability demands that a component’s modifications to the shared envi- ronment be revertible upon unloading. The relevant effects are the stateful ones, which durably transform that environment; undoing such a transformation requires it to admit an inverse.
时间可组合性要求组件对共享环境所做的修改在卸载时是可撤销的。与之相关的效应是有状态的那些效应,它们持久地改变该环境;要撤销这样一个变换,就要求它承认一个逆操作。
- • Spatial composability demands that inter-component dependencies be declared and managed reactively. Such dependencies are the very thing coeffects capture, and manag- ing them amounts to resolving each against what the environment supplies.
空间可组合性要求组件之间的依赖被声明,并以响应式的方式加以管理。这类依赖正是余效应所刻画的东西,而管理它们就等于把每一条依赖对着环境所提供的内容加以解析。
However, classical effect and coeffect systems are static instruments: effects are tracked within lexically fixed scopes and discharged by compile-time handlers; coeffect annotations are verified against contexts determined before execution. Dynamic composition, by contrast, requires these guarantees to hold for components that arrive and depart at runtime, against contexts that evolve continuously. No fixed lexical scope can delimit a plugin loaded after deployment; no compile-time context can anticipate dependencies that emerge from runtime configuration.
然而,经典的效应系统与余效应系统都是静态工具:效应在词法上固定的作用域内被追踪,并由编译期处理器加以消解;余效应标注则针对执行之前即已确定的上下文来验证。相比之下,动态组合要求这些保证对于那些在运行时到来与离去、并且面向持续演化的上下文的组件依然成立。没有任何固定的词法作用域能够界定一个在部署之后才被加载的插件;也没有任何编译期上下文能够预见那些由运行时配置所产生的依赖。
This motivates a shift in perspective: rather than extending static type systems with more annotations, we reify the conceptual structures of effects and coeffects so that a runtime can operate on them directly, establishing dynamically the guarantees these systems provide stat- ically. 8
这促使我们转换视角:与其用更多的标注去扩展静态类型系统,不如把效应与余效应的概念结构具体化(reify),使运行时能够直接对它们进行操作,从而动态地建立起这些系统在静态情形下所提供的那些保证。
3. 可撤销效应与响应式余效应
3. Revertible Effects and Reactive Coeffects
This section lifts the concepts of effects and coeffects introduced in Section 2 to runtime mechanisms, constructing a theory of dynamic composition. The central idea is to turn the typing contexts carrying effects and coeffects into context types, runtime-operable types that reify the context as a first-class entity. Section 3.1 models an effect as a context transformation paired with an inverse that the runtime holds, establishing temporal composability local to one component; Section 3.2 models a coeffect as a declared dependency against which every context change is classified, establishing its spatial counterpart. Each local guarantee stops where other components enter. Toward the global form of both, Section 3.3 unifies the two contexts into one and introduces the context paradigm: every effect and coeffect is mediated through the unified context, and the mediation induces the observational equivalence up to which every later equality is read. Section 3.4 then establishes effect independence and coeffect commutativity, under which the effects of distinct components interleave without disturbing one another.
本节把第 2 节引入的效应与余效应概念提升为运行时机制,构造一套动态组合的理论。其核心思想是把承载着效应与余效应的类型上下文转变为上下文类型——即可以由运行时操作的类型,它们把上下文具体化为一等实体。第 3.1 节把效应建模为一个上下文变换,并配以一个由运行时持有的逆操作,从而确立单个组件局部的时间可组合性;第 3.2 节把余效应建模为一个被声明的依赖,每一次上下文变化都对着它加以分类,从而确立其空间对应物。每一种局部保证都在其他组件介入之处终止。为了走向两者的全局形式,第 3.3 节把两种上下文统一为一种,并引入上下文范式:每一个效应与余效应都经由这一统一上下文中介,而这种中介诱导出一个观测等价,此后每一个等式都在该等价意义下解读。第 3.4 节进而确立效应独立性与余效应可交换性,在这些条件之下,不同组件的效应可以交错发生而不相互干扰。
3.1. 可撤销效应
3.1. Revertible Effects
Temporal composability is the ability to load and unload components at runtime such that, upon unloading, the shared environment is recovered to its pre-composition state. This requires that every modification a component makes to the environment be both trackable and recoverable. We therefore model an effect as a function of type Γ → Γ × (Γ → Γ): applied to the current context, it yields the modified context together with an explicit inverse. Supplying that inverse is what lets the effect be reverted, and returning it to the runtime is what makes the effect trackable. We call such effects revertible: by composing these inverses during execution, local temporal composability becomes a structural guarantee.
时间可组合性是这样一种能力:在运行时加载与卸载组件,并且在卸载时,共享环境被恢复到组合之前的状态。这要求组件对环境所做的每一处修改都既可被追踪、又可被恢复。因此我们把一个效应建模为一个类型为 Γ → Γ × (Γ → Γ) 的函数:把它作用于当前上下文,便得到被修改后的上下文,以及一个显式的逆操作。提供该逆操作,正是使效应可被撤销的原因;而把它交回运行时,则是使效应可被追踪的原因。我们称这样的效应是可撤销的:通过在执行过程中组合这些逆操作,局部的时间可组合性就成为一个结构性保证。
3.1.1. 效应上下文
3.1.1. Effect Context
Given any impure function 𝑓 : 𝑋 ⇝ 𝑌 , we transform it into a pure form 𝑓 : Γ × 𝑋 → Γ × 𝑌 , where Γ is the context type. On this pure form, all possible side effects can be represented as transformations on Γ: for any fixed input 𝑥 : 𝑋, the induced map 𝛾 ↦ pr1(𝑓(𝛾, 𝑥)) : Γ → Γ captures the side effect of 𝑓 independently of the return value. Effects on Γ therefore live in the monoid of transformations Γ → Γ under composition ∘, where each monoid axiom has a direct reading as a property of effects:
给定任意不纯函数 𝑓 : 𝑋 ⇝ 𝑌,我们把它转换为纯形式 𝑓 : Γ × 𝑋 → Γ × 𝑌,其中 Γ 是上下文类型。在这一纯形式下,所有可能的副作用都可以表示为 Γ 上的变换:对于任意固定的输入 𝑥 : 𝑋,所诱导出的映射 𝛾 ↦ pr1(𝑓(𝛾, 𝑥)) : Γ → Γ 刻画了 𝑓 的副作用,且与其返回值无关。因此,Γ 上的效应位于变换 Γ → Γ 在复合运算 ∘ 之下所构成的幺半群之中,其中每一条幺半群公理都可以直接解读为效应的一条性质:
- • Closure: the sequential composition of two effects is again an effect;
封闭性:两个效应的序贯复合仍然是一个效应;
- • Associativity: a composite effect is independent of how it is bracketed;
结合律:一个复合效应与它如何加括号无关;
- • Identity: idΓ, the identity function on Γ, acts as the unit of composition.
单位元:idΓ,即 Γ 上的恒等函数,充当复合运算的单位元。
To model effects that can be undone, we pair each transformation 𝑓 with another transfor- mation 𝑔 that undoes 𝑓, and call 𝑔 a left inverse of 𝑓, abbreviated to inverse throughout the paper. Undoing is one-sided: what an inverse is held to is 𝑔 ∘ 𝑓 and never 𝑓 ∘ 𝑔. Pairs of transformations carry a multiplication of their own:
为了对可以被撤销的效应建模,我们为每一个变换 𝑓 配上另一个撤销 𝑓 的变换 𝑔,并称 𝑔 为 𝑓 的一个左逆,全文简称逆。撤销是单侧的:逆所被要求满足的是 𝑔 ∘ 𝑓,而绝不是 𝑓 ∘ 𝑔。变换对自身带有一种乘法:
Definition 1. Define the twisted composition of pairs of context transformations by
定义 1. 定义上下文变换对上的扭曲复合(twisted composition)如下:
(𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2) ≔ (𝑓1 ∘ 𝑓2, 𝑔2 ∘ 𝑔1) (4)
(𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2) ≔ (𝑓1 ∘ 𝑓2, 𝑔2 ∘ 𝑔1) (4)
As for ∘ itself, the left operand acts after the right, and the inverses accumulate in the opposite order. It makes (Γ → Γ) × (Γ → Γ) a monoid with unit (idΓ, idΓ), the product of the monoid of transformations with its opposite, which we call the twisted composition monoid 𝔗Γ over Γ. 9
与 ∘ 本身一样,左操作数在右操作数之后起作用,而逆则以相反的顺序累加。它使 (Γ → Γ) × (Γ → Γ) 成为以 (idΓ, idΓ) 为单位元的幺半群,即变换幺半群与其反幺半群的乘积,我们称之为 Γ 上的扭曲复合幺半群 𝔗Γ。
To track effects within the context itself, we introduce the following definition:
为了在上下文自身内部追踪效应,我们引入下述定义:
Definition 2. Given a context Γ, define its effect context as:
定义 2. 给定上下文 Γ,定义它的效应上下文为:
𝜕Γ ≔ Γ × (Γ → Γ) (5)
𝜕Γ ≔ Γ × (Γ → Γ) (5)
It can be understood as a pair (𝛾, 𝜑), where:
它可以被理解为一个二元组 (𝛾, 𝜑),其中:
- • 𝛾 : Γ is the current context state;
𝛾 : Γ 是当前的上下文状态;
- • 𝜑 : Γ → Γ is the accumulator, the composite of the inverses of the effects performed so far, and the function that recovers the context to its initial state.
𝜑 : Γ → Γ 是累加器,即迄今为止所执行效应的逆操作的复合,也就是把上下文恢复到其初始状态的函数。
In particular, the initial effect context can be represented as (𝛾0, idΓ).
特别地,初始效应上下文可以表示为 (𝛾0, idΓ)。
We also write 𝜕2Γ for 𝜕(𝜕Γ) = 𝜕Γ × (𝜕Γ → 𝜕Γ); iterating 𝜕 this way yields the tower Γ, 𝜕Γ, 𝜕2Γ, ⋯.
我们也把 𝜕(𝜕Γ) = 𝜕Γ × (𝜕Γ → 𝜕Γ) 记作 𝜕2Γ;如此迭代 𝜕 便得到塔 Γ, 𝜕Γ, 𝜕2Γ, ⋯。
Given the presence of the accumulator 𝜑, all effects performed on 𝜕Γ can be tracked and the context can be recovered. We now give the concrete constructions for tracking and recovery.
由于累加器 𝜑 的存在,在 𝜕Γ 上所执行的所有效应都能被追踪,并且上下文能够被恢复。下面我们给出追踪与恢复的具体构造。
Definition 3. Define the transformation trackΓ on pairs of context functions:
定义 3. 在上下文函数对上定义变换 trackΓ:
trackΓ : (Γ → Γ) × (Γ → Γ) → 𝜕Γ → 𝜕Γ trackΓ = (𝑓, 𝑔) ↦ (𝛾, 𝜑) ↦ (𝑓(𝛾), 𝜑 ∘ 𝑔) (6)
trackΓ : (Γ → Γ) × (Γ → Γ) → 𝜕Γ → 𝜕Γ
trackΓ = (𝑓, 𝑔) ↦ (𝛾, 𝜑) ↦ (𝑓(𝛾), 𝜑 ∘ 𝑔)
(6)This transformation converts a forward function 𝑓 together with a candidate inverse 𝑔 into a transformation of the effect context 𝜕Γ. Applying trackΓ(𝑓, 𝑔) to a state (𝛾, 𝜑) transforms 𝛾 by 𝑓 and composes the inverse 𝑔 onto 𝜑, thereby tracking the effect of 𝑓 in the context.
该变换把一个前向函数 𝑓 连同一个候选逆 𝑔 转换为效应上下文 𝜕Γ 上的一个变换。把 trackΓ(𝑓, 𝑔) 作用于状态 (𝛾, 𝜑),会以 𝑓 变换 𝛾,并把逆 𝑔 复合到 𝜑 之上,从而在上下文中追踪 𝑓 的效应。
Theorem 4. For every (𝑓, 𝑔) ∈ (Γ → Γ) × (Γ → Γ), write 𝑓′ ≔ trackΓ(𝑓, 𝑔); then the following diagram commutes, that is,
定理 4. 对于任意 (𝑓, 𝑔) ∈ (Γ → Γ) × (Γ → Γ),记 𝑓′ ≔ trackΓ(𝑓, 𝑔);则下述图表交换,即
pr1 ∘ 𝑓′ = 𝑓 ∘ pr1 (7)
pr1 ∘ 𝑓′ = 𝑓 ∘ pr1 (7)
𝑓 𝑓′ pr1 pr1track Γ Γ 𝜕Γ 𝜕Γ
𝑓
𝑓′
pr1 pr1track
Γ Γ
𝜕Γ 𝜕ΓProof. For all (𝛾, 𝜑) ∈ 𝜕Γ:
证明. 对于任意 (𝛾, 𝜑) ∈ 𝜕Γ:
(pr1 ∘ trackΓ(𝑓, 𝑔))(𝛾, 𝜑) = pr1(𝑓(𝛾), 𝜑 ∘ 𝑔) = 𝑓(𝛾) = (𝑓 ∘ pr1)(𝛾, 𝜑) □
(pr1 ∘ trackΓ(𝑓, 𝑔))(𝛾, 𝜑) = pr1(𝑓(𝛾), 𝜑 ∘ 𝑔)
= 𝑓(𝛾)
= (𝑓 ∘ pr1)(𝛾, 𝜑) □Theorem 4 ensures that tracking leaves the forward behavior untouched: on the context state, trackΓ(𝑓, 𝑔) acts as 𝑓 does, whatever candidate inverse it carries.
定理 4 确保追踪不会触及前向行为:在上下文状态上,trackΓ(𝑓, 𝑔) 的作用与 𝑓 一致,无论它携带的是哪一个候选逆。
Theorem 5. trackΓ is a monoid homomorphism from 𝔗Γ into 𝜕Γ → 𝜕Γ. That is,
定理 5. trackΓ 是从 𝔗Γ 到 𝜕Γ → 𝜕Γ 的幺半群同态。也就是说,
- 1. trackΓ(idΓ, idΓ) = id𝜕Γ;
trackΓ(idΓ, idΓ) = id𝜕Γ;
- 2. for all (𝑓1, 𝑔1), (𝑓2, 𝑔2) ∈ 𝔗Γ,
对于任意 (𝑓1, 𝑔1), (𝑓2, 𝑔2) ∈ 𝔗Γ,
trackΓ((𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2)) = trackΓ(𝑓1, 𝑔1) ∘ trackΓ(𝑓2, 𝑔2) (8) 10
trackΓ((𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2)) = trackΓ(𝑓1, 𝑔1) ∘ trackΓ(𝑓2, 𝑔2) (8)
Proof.
证明.
- 1. The unit is carried to the unit, since trackΓ(idΓ, idΓ)(𝛾, 𝜑) = (𝛾, 𝜑 ∘ idΓ) = (𝛾, 𝜑).
单位元被送到单位元,因为 trackΓ(idΓ, idΓ)(𝛾, 𝜑) = (𝛾, 𝜑 ∘ idΓ) = (𝛾, 𝜑)。
- 2. For the multiplication, take any (𝛾, 𝜑) ∈ 𝜕Γ:
关于乘法,取任意 (𝛾, 𝜑) ∈ 𝜕Γ:
(trackΓ(𝑓1, 𝑔1) ∘ trackΓ(𝑓2, 𝑔2))(𝛾, 𝜑) = trackΓ(𝑓1, 𝑔1)(𝑓2(𝛾), 𝜑 ∘ 𝑔2) = (𝑓1(𝑓2(𝛾)), 𝜑 ∘ 𝑔2 ∘ 𝑔1) = trackΓ(𝑓1 ∘ 𝑓2, 𝑔2 ∘ 𝑔1)(𝛾, 𝜑) □
(trackΓ(𝑓1, 𝑔1) ∘ trackΓ(𝑓2, 𝑔2))(𝛾, 𝜑) = trackΓ(𝑓1, 𝑔1)(𝑓2(𝛾), 𝜑 ∘ 𝑔2)
= (𝑓1(𝑓2(𝛾)), 𝜑 ∘ 𝑔2 ∘ 𝑔1)
= trackΓ(𝑓1 ∘ 𝑓2, 𝑔2 ∘ 𝑔1)(𝛾, 𝜑) □Theorem 5 ensures that tracking one pair at a time agrees with tracking their twisted composite at once, so a sequence of tracked effects can be reasoned about as a single tracked effect.
定理 5 确保一次追踪一个变换对,与一次性追踪它们的扭曲复合,两者是一致的,因此一串被追踪的效应可以被当作单个被追踪的效应来推理。
Definition 6. Define the transformation recoverΓ on 𝜕Γ:
定义 6. 在 𝜕Γ 上定义变换 recoverΓ:
recoverΓ : 𝜕Γ → 𝜕Γ recoverΓ = (𝛾, 𝜑) ↦ (𝜑(𝛾), idΓ) (9)
recoverΓ : 𝜕Γ → 𝜕Γ
recoverΓ = (𝛾, 𝜑) ↦ (𝜑(𝛾), idΓ)
(9)This transformation applies the recovery function 𝜑 to the current state 𝛾 and resets 𝜑 to the identity. The following diagram illustrates how recover recovers the context to its initial state after a sequence of effects 𝑓′ 𝑖 ≔ trackΓ(𝑓𝑖, 𝑔𝑖), 𝑖 = 1, ⋯, 𝑛, has been applied to 𝜕Γ:
该变换把恢复函数 𝜑 作用于当前状态 𝛾,并把 𝜑 重置为恒等。下述图表展示了在一串效应 𝑓′𝑖 ≔ trackΓ(𝑓𝑖, 𝑔𝑖), 𝑖 = 1, ⋯, 𝑛 被作用于 𝜕Γ 之后,recover 如何把上下文恢复到它的初始状态:
𝑓1 𝑓𝑛 𝑓′ 1 𝑓′ 𝑛 track track track recover Γ Γ Γ Γ 𝜕Γ 𝜕Γ 𝜕Γ 𝜕Γ
𝑓1 𝑓𝑛
𝑓′
1 𝑓′
𝑛
track track track
recover
Γ Γ Γ Γ
𝜕Γ 𝜕Γ 𝜕Γ 𝜕ΓThe diagram shows that the tracked effects followed by recover carry the initial effect context back to itself. Each tracking step in fact preserves the result of recovery itself, from whatever state it is taken:
该图表表明,被追踪的效应之后紧接 recover,会把初始效应上下文带回其自身。事实上,每一个追踪步骤都保持恢复本身的结果,无论该结果是从哪一个状态取得的:
Theorem 7. For every (𝛾, 𝜑) ∈ 𝜕Γ and every pair (𝑓, 𝑔) with 𝑔(𝑓(𝛾)) = 𝛾,
定理 7. 对于任意 (𝛾, 𝜑) ∈ 𝜕Γ 以及任意满足 𝑔(𝑓(𝛾)) = 𝛾 的变换对 (𝑓, 𝑔),
recoverΓ(trackΓ(𝑓, 𝑔)(𝛾, 𝜑)) = recoverΓ(𝛾, 𝜑) (10)
recoverΓ(trackΓ(𝑓, 𝑔)(𝛾, 𝜑)) = recoverΓ(𝛾, 𝜑) (10)
Proof.
证明.
recoverΓ(trackΓ(𝑓, 𝑔)(𝛾, 𝜑)) = recoverΓ(𝑓(𝛾), 𝜑 ∘ 𝑔) = (𝜑(𝑔(𝑓(𝛾))), idΓ) = (𝜑(𝛾), idΓ) = recoverΓ(𝛾, 𝜑) □
recoverΓ(trackΓ(𝑓, 𝑔)(𝛾, 𝜑)) = recoverΓ(𝑓(𝛾), 𝜑 ∘ 𝑔)
= (𝜑(𝑔(𝑓(𝛾))), idΓ)
= (𝜑(𝛾), idΓ) = recoverΓ(𝛾, 𝜑) □Theorem 7 ensures that a tracked effect whose inverse reverts it does not move the result of recovery: recovering after the step returns what recovering before it would have. Recovery reads a state through the quantity 𝜑(𝛾) alone, so the guarantee amounts to preserving 𝜑(𝛾); we refer to 𝜑(𝛾) = 𝛾0 as the soundness invariant of a state in 𝜕Γ. In particular, starting from the initial effect context (𝛾0, idΓ), every state reached by tracked effects whose inverses revert them satisfies the invariant, and recovery carries each such state back to (𝛾0, idΓ). 11
定理 7 确保一个被其逆所撤销的被追踪效应不会移动恢复的结果:在该步之后进行恢复所得,与该步之前进行恢复本会所得是相同的。恢复只通过量 𝜑(𝛾) 来读取一个状态,因此该保证就等于保持 𝜑(𝛾);我们把 𝜑(𝛾) = 𝛾0 称为 𝜕Γ 中一个状态的可靠性不变式。特别地,从初始效应上下文 (𝛾0, idΓ) 出发,由那些被其逆所撤销的被追踪效应所到达的每一个状态都满足该不变式,而恢复把每一个这样的状态带回到 (𝛾0, idΓ)。
The preservation along a sequence follows from Theorem 5 in one application. Let (𝑓1, 𝑔1), ⋯, (𝑓𝑛, 𝑔𝑛) be applied in order from (𝛾, 𝜑), and write 𝛿0 = 𝛾 and 𝛿𝑖 = 𝑓𝑖(𝛿𝑖−1) for the intermediate context states. By Theorem 5, the composite trackΓ(𝑓𝑛, 𝑔𝑛) ∘ ⋯ ∘ trackΓ(𝑓1, 𝑔1) is a single tracking step, trackΓ of the twisted composite (𝑓𝑛 ∘ ⋯ ∘ 𝑓1, 𝑔1 ∘ ⋯ ∘ 𝑔𝑛). If each inverse reverts its own step, 𝑔𝑖(𝛿𝑖) = 𝛿𝑖−1, then the composite inverse carries 𝛿𝑛 back to the start: (𝑔1 ∘ ⋯ ∘ 𝑔𝑛)(𝛿𝑛) = 𝛿0 = 𝛾. The twisted composite therefore meets the hypothesis of Theorem 7 at 𝛾, and one application of the theorem gives
沿一整串序列的保持性可由定理 5 的一次应用得出。设 (𝑓1, 𝑔1), ⋯, (𝑓𝑛, 𝑔𝑛) 从 (𝛾, 𝜑) 起依次被应用,并记 𝛿0 = 𝛾 与 𝛿𝑖 = 𝑓𝑖(𝛿𝑖−1) 为中间的上下文状态。由定理 5,复合 trackΓ(𝑓𝑛, 𝑔𝑛) ∘ ⋯ ∘ trackΓ(𝑓1, 𝑔1) 是一个单一的追踪步骤,即扭曲复合 (𝑓𝑛 ∘ ⋯ ∘ 𝑓1, 𝑔1 ∘ ⋯ ∘ 𝑔𝑛) 的 trackΓ。若每一个逆都撤销它自己那一步,即 𝑔𝑖(𝛿𝑖) = 𝛿𝑖−1,则复合逆把 𝛿𝑛 带回到起点:(𝑔1 ∘ ⋯ ∘ 𝑔𝑛)(𝛿𝑛) = 𝛿0 = 𝛾。因此该扭曲复合在 𝛾 处满足定理 7 的假设,一次应用该定理即得
recoverΓ((trackΓ(𝑓𝑛, 𝑔𝑛) ∘ ⋯ ∘ trackΓ(𝑓1, 𝑔1))(𝛾, 𝜑)) = recoverΓ(𝛾, 𝜑) (11)
recoverΓ((trackΓ(𝑓𝑛, 𝑔𝑛) ∘ ⋯ ∘ trackΓ(𝑓1, 𝑔1))(𝛾, 𝜑)) = recoverΓ(𝛾, 𝜑) (11)
A pair with 𝑔 ∘ 𝑓 = idΓ meets the hypothesis at every state.
满足 𝑔 ∘ 𝑓 = idΓ 的变换对在每一个状态处都满足该假设。
3.1.2. 效应函数
3.1.2. Effect Functions
The track/recover model of the previous section has two limitations:
上一节的 track/recover 模型有两点局限:
- 1. trackΓ(𝑓, 𝑔) fixes 𝑔 before any context state is seen, so one uniform 𝑔 has to meet the hypothesis of Theorem 7 at every state the effect is applied at. Reverting needs less: an inverse for the one state where 𝑓 is applied, which may differ from state to state. A per- state inverse cannot be fixed in the argument position before the state is seen; it has to be returned at the point of application.
trackΓ(𝑓, 𝑔) 在看到任何上下文状态之前就固定了 𝑔,因此一个统一的 𝑔 必须在效应被应用的每一个状态处都满足定理 7 的假设。而撤销所需要的其实更少:只需要针对 𝑓 被应用的那个状态给出一个逆,而这个逆可以随状态不同而不同。逐状态的逆无法在状态被看到之前就固定在参数位置上;它必须在应用的时刻被返回。
- 2. recoverΓ is all-or-nothing: it cannot selectively undo one effect while retaining others.
recoverΓ 是全有或全无的:它无法在保留其他效应的同时有选择地撤销其中的某一个效应。
To address the two issues, we enhance the model at the input and output sides respectively:
为了分别解决这两个问题,我们在输入侧与输出侧对该模型加以增强:
- 1. On the input side, we not only transform Γ but also return an inverse function alongside it, so that the inverse is supplied where the effect is applied: Γ → Γ × (Γ → Γ), i.e., Γ → 𝜕Γ;
在输入侧,我们不仅变换 Γ,还随之返回一个逆函数,使得逆在效应被应用之处被提供:Γ → Γ × (Γ → Γ),即 Γ → 𝜕Γ;
- 2. On the output side, we not only transform 𝜕Γ but also return an inverse function along- side it, so that one effect can be undone while the others are retained: 𝜕Γ → 𝜕Γ × (𝜕Γ → 𝜕Γ), i.e., 𝜕Γ → 𝜕2Γ.
在输出侧,我们不仅变换 𝜕Γ,还随之返回一个逆函数,使得可以在保留其他效应的同时撤销某一个效应:𝜕Γ → 𝜕Γ × (𝜕Γ → 𝜕Γ),即 𝜕Γ → 𝜕2Γ。
The two changes give the input and the output the same shape, i.e., a map from a context to the transformed context paired with an inverse: Γ → 𝜕Γ on the input side and 𝜕Γ → 𝜕2Γ on the output side. One type family therefore covers both levels, and we define it at each context as the effect function type 𝔈Γ, refined by a witness to 𝔈∗ Γ:
这两处改动使输入侧与输出侧具有相同的形状,即一个从上下文到"被变换后的上下文连同其逆"的映射:输入侧为 Γ → 𝜕Γ,输出侧为 𝜕Γ → 𝜕2Γ。于是同一个类型族覆盖了两个层次,我们在每一个上下文处把它定义为效应函数类型 𝔈Γ,并由一个见证将其精化为 𝔈∗Γ:
Definition 8. Define the effect function 𝔈Γ and witnessed effect function 𝔈∗ Γ as:
定义 8. 定义效应函数 𝔈Γ 与带见证的效应函数 𝔈∗Γ 如下:
𝔈Γ ≔ Γ → Γ × (Γ → Γ) 𝔈∗ Γ ≔ (𝑒 : Γ → Γ × (Γ → Γ)) × ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → ((𝛿, 𝑔) = 𝑒(𝛾) → 𝑔(𝛿) = 𝛾)) (12)
𝔈Γ ≔ Γ → Γ × (Γ → Γ)
𝔈∗Γ ≔ (𝑒 : Γ → Γ × (Γ → Γ))
× ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → ((𝛿, 𝑔) = 𝑒(𝛾) → 𝑔(𝛿) = 𝛾))
(12)where 𝑒(𝛾) yields a pair (𝛿, 𝑔) representing:
其中 𝑒(𝛾) 产生一个二元组 (𝛿, 𝑔),表示:
- • 𝛿 : Γ is the new context;
𝛿 : Γ 是新的上下文;
- • 𝑔 : Γ → Γ is the inverse function of the current effect.
𝑔 : Γ → Γ 是当前效应的逆函数。
The witness holds each returned inverse to one equation, 𝑔(𝛿) = 𝛾: the inverse is required to revert the effect only at the state where it was applied. An element of 𝔈∗ Γ may therefore choose a different inverse at every state. A single 𝑔 with 𝑔 ∘ 𝑓 = idΓ meets the equation at every state at once, so the assignment (𝑓, 𝑔) ↦ 𝛾 ↦ (𝑓(𝛾), 𝑔) carries such a pair into 𝔈∗ Γ, and Theorem 11 shows this assignment to be a homomorphism. The following commutative diagram states 12
见证要求每一个被返回的逆满足同一个等式 𝑔(𝛿) = 𝛾:逆只需要在效应被应用的那个状态上撤销该效应。因此,𝔈∗Γ 的一个元素可以在每一个状态处选择一个不同的逆。单个满足 𝑔 ∘ 𝑓 = idΓ 的 𝑔 一次性地在所有状态处满足该等式,于是映射 (𝑓, 𝑔) ↦ 𝛾 ↦ (𝑓(𝛾), 𝑔) 把这样的一个变换对送入 𝔈∗Γ,而定理 11 表明该映射是一个同态。下述交换图表陈述:
the same condition: the inverse 𝑒 returns reverts the transformation at the state where 𝑒 was applied:
同样的条件:逆 𝑒 在 𝑒 被应用时所处的状态上撤销该变换:
𝑓 𝑔 𝑒 pr1 pr2 Γ Γ 𝜕Γ
𝑓
𝑔
𝑒 pr1
pr2
Γ Γ
𝜕ΓSince effect functions 𝔈Γ are no longer endomorphisms on the context, they cannot be directly composed. We therefore define a new operation for effect composition:
由于效应函数 𝔈Γ 不再是上下文上的自同态,它们无法直接复合。因此我们为效应复合定义一个新的运算:
Definition 9. Given functions 𝑓, 𝑔 ∈ 𝔈Γ, define their effect composition 𝑓 ⋄ 𝑔 as:
定义 9. 给定函数 𝑓, 𝑔 ∈ 𝔈Γ,定义它们的效应复合 𝑓 ⋄ 𝑔 为:
𝑓 ⋄ 𝑔 : Γ → 𝜕Γ 𝑓 ⋄ 𝑔 = 𝛾 ↦ 𝐥𝐞𝐭 (𝛿, 𝑠) = 𝑔(𝛾) 𝐢𝐧 𝐥𝐞𝐭 (𝜀, 𝑡) = 𝑓(𝛿) 𝐢𝐧 (𝜀, 𝑠 ∘ 𝑡)
𝑓 ⋄ 𝑔 : Γ → 𝜕Γ
𝑓 ⋄ 𝑔 = 𝛾 ↦
𝐥𝐞𝐭 (𝛿, 𝑠) = 𝑔(𝛾) 𝐢𝐧
𝐥𝐞𝐭 (𝜀, 𝑡) = 𝑓(𝛿) 𝐢𝐧
(𝜀, 𝑠 ∘ 𝑡)(13)
(13)
Theorem 10. Effect composition carries the monoid structure of 𝔗Γ over to 𝔈Γ. That is,
定理 10. 效应复合把 𝔗Γ 上的幺半群结构带到 𝔈Γ 上。即,
- 1. (𝔈Γ, ⋄) is a monoid with unit 𝜂Γ ≔ 𝛾 ↦ (𝛾, idΓ);
(𝔈Γ, ⋄) 是一个以 𝜂Γ ≔ 𝛾 ↦ (𝛾, idΓ) 为单位的幺半群;
- 2. the assignment (𝑓, 𝑔) ↦ 𝛾 ↦ (𝑓(𝛾), 𝑔) is a monoid homomorphism from 𝔗Γ into 𝔈Γ.
指派 (𝑓, 𝑔) ↦ 𝛾 ↦ (𝑓(𝛾), 𝑔) 是从 𝔗Γ 到 𝔈Γ 的幺半群同态。
Proof.
证明.
- 1. Associativity and the unit laws follow componentwise from those of ∘.
结合律与单位律由 ∘ 的相应性质按分量得出。
- 2. Write 𝑒𝑖 = 𝛾 ↦ (𝑓𝑖(𝛾), 𝑔𝑖); then (𝑒1 ⋄ 𝑒2)(𝛾) = (𝑓1(𝑓2(𝛾)), 𝑔2 ∘ 𝑔1), which is the image of (𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2), and (idΓ, idΓ) maps to 𝜂Γ. □
记 𝑒𝑖 = 𝛾 ↦ (𝑓𝑖(𝛾), 𝑔𝑖);则 (𝑒1 ⋄ 𝑒2)(𝛾) = (𝑓1(𝑓2(𝛾)), 𝑔2 ∘ 𝑔1),它正是 (𝑓1, 𝑔1) ∘ (𝑓2, 𝑔2) 的像,且 (idΓ, idΓ) 映到 𝜂Γ。□
Theorem 11. Witnessing survives effect composition, and a uniform inverse witnesses at every state. That is,
定理 11. 见证性在效应复合下得以保持,并且一个统一的逆在每一个状态上都构成见证。即,
- 1. 𝔈∗ Γ is a submonoid of 𝔈Γ;
𝔈∗Γ 是 𝔈Γ 的子幺半群;
- 2. the homomorphism of Theorem 10 carries every pair with 𝑔 ∘ 𝑓 = idΓ into 𝔈∗ Γ.
定理 10 的同态把每一个满足 𝑔 ∘ 𝑓 = idΓ 的对子送入 𝔈∗Γ。
Proof.
证明.
- 1. The unit lies in 𝔈∗ Γ since idΓ(𝛾) = 𝛾. For closure, take 𝑓, 𝑔 ∈ 𝔈∗ Γ and any 𝛾 ∈ Γ, and let (𝛿, 𝑠) = 𝑔(𝛾), (𝜀, 𝑡) = 𝑓(𝛿), so that (𝑓 ⋄ 𝑔)(𝛾) = (𝜀, 𝑠 ∘ 𝑡). Then 𝑠(𝛿) = 𝛾 and 𝑡(𝜀) = 𝛿, therefore (𝑠 ∘ 𝑡)(𝜀) = 𝑠(𝛿) = 𝛾.
单位元属于 𝔈∗Γ,因为 idΓ(𝛾) = 𝛾。关于封闭性,取 𝑓, 𝑔 ∈ 𝔈∗Γ 与任意 𝛾 ∈ Γ,令 (𝛿, 𝑠) = 𝑔(𝛾)、(𝜀, 𝑡) = 𝑓(𝛿),于是 (𝑓 ⋄ 𝑔)(𝛾) = (𝜀, 𝑠 ∘ 𝑡)。此时 𝑠(𝛿) = 𝛾 且 𝑡(𝜀) = 𝛿,因此 (𝑠 ∘ 𝑡)(𝜀) = 𝑠(𝛿) = 𝛾。
- 2. 𝑔 ∘ 𝑓 = idΓ gives 𝑔(𝑓(𝛾)) = 𝛾 at every 𝛾, so the image of such a pair is witnessed at every state. □
𝑔 ∘ 𝑓 = idΓ 给出在每一个 𝛾 上都有 𝑔(𝑓(𝛾)) = 𝛾,故这样的对子的像在每一个状态上都被见证。□
Just as track lifts a pair of transformations on Γ to 𝜕Γ, we define effect to lift 𝔈Γ to 𝔈𝜕Γ:
正如 track 把 Γ 上的一对变换提升到 𝜕Γ,我们定义 effect 把 𝔈Γ 提升到 𝔈𝜕Γ:
Definition 12. Define the effect function transformation effectΓ as:
定义 12. 定义效应函数变换 effectΓ 为:
effectΓ : 𝔈Γ → 𝜕Γ → 𝜕2Γ effectΓ = 𝑒 ↦ (𝛾, 𝜑) ↦ 𝐥𝐞𝐭 (𝛿, 𝑔) = 𝑒(𝛾) 𝐢𝐧 ((𝛿, 𝜑 ∘ 𝑔), trackΓ(𝑔, pr1 ∘ 𝑒))
effectΓ : 𝔈Γ → 𝜕Γ → 𝜕2Γ
effectΓ = 𝑒 ↦ (𝛾, 𝜑) ↦ 𝐥𝐞𝐭 (𝛿, 𝑔) = 𝑒(𝛾) 𝐢𝐧
((𝛿, 𝜑 ∘ 𝑔), trackΓ(𝑔, pr1 ∘ 𝑒))(14)
(14)
Since effectΓ(𝑒) is itself 𝔈𝜕Γ, what it returns is an inverse in the sense of Definition 8 read one level up. That inverse is itself a track of the pair obtained by swapping the two directions of the effect. The ordinary tracking rule applies once more: undoing the effect is an effect in its 13 own right, transforming the state by 𝑔, and the way to undo that is to perform the effect again, which is what pr1 ∘ 𝑒 does. The inverse therefore composes onto the accumulator it is handed, exactly as track prescribes.
由于 effectΓ(𝑒) 本身是 𝔈𝜕Γ 中的元素,它返回的是一个按定义 8 往上一层解读的逆。该逆本身又是对交换该效应两个方向所得对子所作的 track。通常的跟踪规则再一次适用:撤销该效应本身就是一个效应,它按 𝑔 变换状态,而撤销它的办法就是再次执行该效应,这正是 pr1 ∘ 𝑒 所做的。因此该逆复合到交给它的累加器上,恰如 track 所规定的那样。
We can now prove properties for effect analogous to those of track.
我们现在可以证明 effect 与 track 相类似的性质。
Theorem 13. effect preserves the ⋄ operation. That is, ∀𝑓, 𝑔 ∈ 𝔈Γ:
定理 13. effect 保持 ⋄ 运算。即,∀𝑓, 𝑔 ∈ 𝔈Γ:
effectΓ(𝑓) ⋄ effectΓ(𝑔) = effectΓ(𝑓 ⋄ 𝑔) (15)
effectΓ(𝑓) ⋄ effectΓ(𝑔) = effectΓ(𝑓 ⋄ 𝑔) (15)
Proof. Take any (𝛾, 𝜑) ∈ 𝜕Γ, and let (𝛿, 𝑠) = 𝑔(𝛾) and (𝜀, 𝑡) = 𝑓(𝛿), so that (𝑓 ⋄ 𝑔)(𝛾) = (𝜀, 𝑠 ∘ 𝑡) and pr1 ∘ (𝑓 ⋄ 𝑔) = (pr1 ∘ 𝑓) ∘ (pr1 ∘ 𝑔). Then
证明. 取任意 (𝛾, 𝜑) ∈ 𝜕Γ,令 (𝛿, 𝑠) = 𝑔(𝛾)、(𝜀, 𝑡) = 𝑓(𝛿),于是 (𝑓 ⋄ 𝑔)(𝛾) = (𝜀, 𝑠 ∘ 𝑡) 且 pr1 ∘ (𝑓 ⋄ 𝑔) = (pr1 ∘ 𝑓) ∘ (pr1 ∘ 𝑔)。则
(effectΓ(𝑓) ⋄ effectΓ(𝑔))(𝛾, 𝜑) = ((𝜀, 𝜑 ∘ 𝑠 ∘ 𝑡), trackΓ(𝑠, pr1 ∘ 𝑔) ∘ trackΓ(𝑡, pr1 ∘ 𝑓)) = ((𝜀, 𝜑 ∘ 𝑠 ∘ 𝑡), trackΓ(𝑠 ∘ 𝑡, (pr1 ∘ 𝑓) ∘ (pr1 ∘ 𝑔))) = effectΓ(𝑓 ⋄ 𝑔)(𝛾, 𝜑)
(effectΓ(𝑓) ⋄ effectΓ(𝑔))(𝛾, 𝜑) = ((𝜀, 𝜑 ∘ 𝑠 ∘ 𝑡), trackΓ(𝑠, pr1 ∘ 𝑔) ∘ trackΓ(𝑡, pr1 ∘ 𝑓))
= ((𝜀, 𝜑 ∘ 𝑠 ∘ 𝑡), trackΓ(𝑠 ∘ 𝑡, (pr1 ∘ 𝑓) ∘ (pr1 ∘ 𝑔)))
= effectΓ(𝑓 ⋄ 𝑔)(𝛾, 𝜑)where the first step unfolds Definition 12 at (𝛾, 𝜑) and at (𝛿, 𝜑 ∘ 𝑠), the second is Theorem 5, and the third folds Definition 12. □
其中第一步是在 (𝛾, 𝜑) 与 (𝛿, 𝜑 ∘ 𝑠) 处展开定义 12,第二步是定理 5,第三步是折叠定义 12。□
How the two levels relate is what the following diagram shows. Its upper triangle is the witness condition of 𝑒, according to Definition 8, and its lower triangle is the question of whether 𝑒′ is witnessed the way 𝑒 is.
两个层次之间的关系由下图给出。其上三角形按定义 8 是 𝑒 的见证条件,其下三角形则是 𝑒′ 是否像 𝑒 那样被见证的问题。
𝑓 𝑓′ 𝑔 𝑔′ 𝑒 𝑒′ pr1 pr1 pr2 pr2 effect Γ Γ 𝜕Γ 𝜕Γ 𝜕2Γ
𝑓
𝑓′
𝑔
𝑔′
𝑒
𝑒′
pr1
pr1
pr2
pr2
effect
Γ Γ
𝜕Γ 𝜕Γ
𝜕2ΓBetween the levels, the projection pr1 relates each lifted map to the map it lifts, as it does for trackΓ in Theorem 4.
在两层之间,投影 pr1 把每个被提升的映射与它所提升的映射联系起来,正如定理 4 中对 trackΓ 所做的那样。
Theorem 14. Let 𝑒 ∈ 𝔈Γ, write 𝑓 ≔ pr1 ∘ 𝑒, and let 𝑒′ ≔ effectΓ(𝑒) with forward map 𝑓′ ≔ pr1 ∘ 𝑒′. Then
定理 14. 设 𝑒 ∈ 𝔈Γ,记 𝑓 ≔ pr1 ∘ 𝑒,并令 𝑒′ ≔ effectΓ(𝑒),其正向映射为 𝑓′ ≔ pr1 ∘ 𝑒′。则
- 1. pr1 ∘ 𝑓′ = 𝑓 ∘ pr1;
pr1 ∘ 𝑓′ = 𝑓 ∘ pr1;
- 2. for each (𝛾, 𝜑) ∈ 𝜕Γ, the lifted inverse 𝑔′ ≔ pr2(𝑒′(𝛾, 𝜑)) and the inverse 𝑔 ≔ pr2(𝑒(𝛾)) witnessed there satisfy pr1 ∘ 𝑔′ = 𝑔 ∘ pr1.
对每个 (𝛾, 𝜑) ∈ 𝜕Γ,被提升的逆 𝑔′ ≔ pr2(𝑒′(𝛾, 𝜑)) 与在该处被见证的逆 𝑔 ≔ pr2(𝑒(𝛾)) 满足 pr1 ∘ 𝑔′ = 𝑔 ∘ pr1。
Proof.
证明.
- 1. By Definition 12, 𝑓′(𝛾, 𝜑) = (𝑓(𝛾), 𝜑 ∘ 𝑔), whose state is 𝑓(𝛾) = (𝑓 ∘ pr1)(𝛾, 𝜑).
由定义 12,𝑓′(𝛾, 𝜑) = (𝑓(𝛾), 𝜑 ∘ 𝑔),其状态为 𝑓(𝛾) = (𝑓 ∘ pr1)(𝛾, 𝜑)。
- 2. This is Theorem 4 applied to 𝑔′ = trackΓ(𝑔, 𝑓). □
这是定理 4 应用于 𝑔′ = trackΓ(𝑔, 𝑓)。□
Whether the lower triangle closes is settled by computing what the lifted inverse returns:
下三角形是否闭合并由计算被提升的逆所返回的东西决定:
Theorem 15. Let 𝑒 ∈ 𝔈∗ Γ and write 𝑓 ≔ pr1 ∘ 𝑒. Fix (𝛾, 𝜑) ∈ 𝜕Γ, let (𝛿, 𝑔) = 𝑒(𝛾), and write (Δ, 𝑔′) for the value of effectΓ(𝑒) at (𝛾, 𝜑). Then 14
定理 15. 设 𝑒 ∈ 𝔈∗Γ 并记 𝑓 ≔ pr1 ∘ 𝑒。固定 (𝛾, 𝜑) ∈ 𝜕Γ,令 (𝛿, 𝑔) = 𝑒(𝛾),并记 (Δ, 𝑔′) 为 effectΓ(𝑒) 在 (𝛾, 𝜑) 处的值。则
𝑔′(Δ) = (𝛾, 𝜑 ∘ 𝑔 ∘ 𝑓) (16)
𝑔′(Δ) = (𝛾, 𝜑 ∘ 𝑔 ∘ 𝑓) (16)
The state is recovered exactly. The accumulator is restored as well, equivalently effectΓ(𝑒) ∈ 𝔈∗ 𝜕Γ, if and only if 𝑔 ∘ 𝑓 = idΓ; and in every case (𝜑 ∘ 𝑔 ∘ 𝑓)(𝛾) = 𝜑(𝛾), so the soundness invariant is preserved.
状态被精确恢复。累加器同样被还原,等价地 effectΓ(𝑒) ∈ 𝔈∗𝜕Γ,当且仅当 𝑔 ∘ 𝑓 = idΓ;并且在任何情况下都有 (𝜑 ∘ 𝑔 ∘ 𝑓)(𝛾) = 𝜑(𝛾),因而可靠性不变式得以保持。
Proof. By Definition 12, Δ = (𝛿, 𝜑 ∘ 𝑔) and 𝑔′ = trackΓ(𝑔, 𝑓), so
证明. 由定义 12,Δ = (𝛿, 𝜑 ∘ 𝑔) 且 𝑔′ = trackΓ(𝑔, 𝑓),故
𝑔′(Δ) = (𝑔(𝛿), 𝜑 ∘ 𝑔 ∘ 𝑓) = (𝛾, 𝜑 ∘ 𝑔 ∘ 𝑓)
𝑔′(Δ) = (𝑔(𝛿), 𝜑 ∘ 𝑔 ∘ 𝑓) = (𝛾, 𝜑 ∘ 𝑔 ∘ 𝑓)
using 𝑔(𝛿) = 𝛾. Membership in 𝔈∗ 𝜕Γ requires this to equal (𝛾, 𝜑) at every input; taking 𝜑 = idΓ turns the equality of accumulators into 𝑔 ∘ 𝑓 = idΓ, and that condition conversely gives the equality of accumulators for every 𝜑. Finally (𝜑 ∘ 𝑔 ∘ 𝑓)(𝛾) = 𝜑(𝑔(𝛿)) = 𝜑(𝛾). □
其中用到 𝑔(𝛿) = 𝛾。属于 𝔈∗𝜕Γ 要求它在每一个输入处都等于 (𝛾, 𝜑);取 𝜑 = idΓ 就把累加器的相等化为 𝑔 ∘ 𝑓 = idΓ,而该条件反过来又给出每一个 𝜑 下累加器的相等。最后 (𝜑 ∘ 𝑔 ∘ 𝑓)(𝛾) = 𝜑(𝑔(𝛿)) = 𝜑(𝛾)。□
The lower triangle therefore closes only when the inverse witnessed at 𝛾 reverts 𝑓 at every state, so effectΓ does not carry 𝔈∗ Γ into 𝔈∗ 𝜕Γ. What holds in every case is agreement at 𝛾: recoverΓ(𝑔′(Δ)) = recoverΓ(𝛾, 𝜑), which is the whole of what Theorem 7 assumes of an accumulator, so reverting leaves the recovery target untouched.
因此,下三角形仅当在 𝛾 处被见证的逆在每一个状态上都撤销 𝑓 时才闭合,所以 effectΓ 并不把 𝔈∗Γ 送入 𝔈∗𝜕Γ。在任何情况下都成立的是在 𝛾 处的一致:recoverΓ(𝑔′(Δ)) = recoverΓ(𝛾, 𝜑),这正是定理 7 对一个累加器所作的全部假设,因此撤销不会触及恢复目标。
3.1.3. 效应迭代器
3.1.3. Effect Iterators
What a component loads by is not one effect but a sequence of them, and what its unloading reverts is the whole sequence. Reverting effects in the reverse order of application requires nothing further, because each inverse then meets the state its own application produced:
一个组件加载所凭借的不是一个效应,而是一串效应;其卸载所撤销的则是整串效应。按应用的逆序撤销这些效应无需任何额外的东西,因为此时每个逆遇到的正是它自身应用所产生的状态:
Theorem 16. Let 𝑒1, ⋯, 𝑒𝑛 ∈ 𝔈∗ Γ be applied in order from (𝛾0, idΓ) and reverted in the reverse order. Then
定理 16. 设 𝑒1, ⋯, 𝑒𝑛 ∈ 𝔈∗Γ 从 (𝛾0, idΓ) 起依次应用,并按逆序撤销。则
- 1. each revert recovers the context state its application ran against;
每一次撤销都恢复其应用所针对的上下文状态;
- 2. every intermediate state satisfies the soundness invariant.
每一个中间状态都满足可靠性不变式。
Proof. Each step is an application or a revert. An application carries (𝛾, 𝜑) to (𝛿, 𝜑 ∘ 𝑔) with 𝑔(𝛿) = 𝛾, so it preserves 𝜑(𝛾) by Theorem 7, whose hypothesis is exactly the witness of 𝔈∗ Γ. Reverting in the reverse order hands each inverse the state its own application produced, so by Theorem 15 that revert recovers the preceding state exactly and preserves 𝜑(𝛾) as well; neither conclusion depends on the accumulator the inverse receives. □
证明. 每一步或是应用或是撤销。一次应用把 (𝛾, 𝜑) 变为 (𝛿, 𝜑 ∘ 𝑔) 且 𝑔(𝛿) = 𝛾,故由定理 7 它保持 𝜑(𝛾),而定理 7 的前提恰是 𝔈∗Γ 的见证。按逆序撤销把每个逆交给它自身应用所产生的状态,故由定理 15,该撤销精确恢复前一个状态,并且同样保持 𝜑(𝛾);这两个结论都不依赖于该逆所接收到的累加器。□
The sequence itself deserves a reification. An effect iterator performs it one effect at a time, each of whose iterations yields the modified context, an inverse, and a continuation:
这串效应本身值得被具体化。效应迭代器一次执行一个效应,其每一次迭代都产出修改后的上下文、一个逆和一个延续:
Definition 17. Define the effect iterator ℑΓ and witnessed effect iterator ℑ∗ Γ as the following recursive types:
定义 17. 定义效应迭代器 ℑΓ 与带见证的效应迭代器 ℑ∗Γ 为如下递归类型:
ℑΓ ≔ 𝜇ℑ. Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ) ℑ∗ Γ ≔ 𝜇ℑ. (𝑒 : Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)) × ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → (𝑜 : 𝖬𝖺𝗒𝖻𝖾(ℑ)) → ((𝛿, 𝑔, 𝑜) = 𝑒(𝛾) → 𝑔(𝛿) = 𝛾))
ℑΓ ≔ 𝜇ℑ. Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)
ℑ∗Γ ≔ 𝜇ℑ. (𝑒 : Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ))
× ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → (𝑜 : 𝖬𝖺𝗒𝖻𝖾(ℑ)) → ((𝛿, 𝑔, 𝑜) = 𝑒(𝛾) → 𝑔(𝛿) = 𝛾))(17)
(17)
where 𝑒(𝛾) yields a triple (𝛿, 𝑔, 𝑜) representing:
其中 𝑒(𝛾) 产出一个三元组 (𝛿, 𝑔, 𝑜),表示:
- • 𝛿 is the new context;
𝛿 是新的上下文;
- • 𝑔 is the inverse function of the current effect;
𝑔 是当前效应的逆函数;
- • 𝑜 indicates the continuation:
𝑜 指示延续:
- ‣ 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 signals iteration termination;
𝖭𝗈𝗍𝗁𝗂𝗇𝗀 表示迭代终止;
- ‣ 𝖩𝗎𝗌𝗍(𝑖) provides the next iteration. 15
𝖩𝗎𝗌𝗍(𝑖) 提供下一次迭代。
The witness holds each iteration to the constraint Definition 8 places on a single effect, and the continuation a witnessed iterator yields is again witnessed.
见证要求每一次迭代都满足定义 8 对单个效应所施加的约束,并且一个带见证的迭代器所产出的延续仍然是带见证的。
The effect iterator transformation effectiter Γ extends effectΓ to the iterator structure through recursive invocation:
效应迭代器变换 effectiterΓ 通过递归调用把 effectΓ 扩展到迭代器结构上:
Definition 18. Define the effect iterator transformation effectiter Γ as:
定义 18. 定义效应迭代器变换 effectiterΓ 为:
effectiter Γ : ℑΓ → 𝜕Γ → 𝜕2Γ effectiter Γ = 𝑖 ↦ (𝛾, 𝜑) ↦ 𝐥𝐞𝐭 (𝛿, 𝑔, 𝑜) = 𝑖(𝛾) 𝐢𝐧 𝐥𝐞𝐭 𝑡 = trackΓ(𝑔, pr1 ∘ 𝑖) 𝐢𝐧 𝐦𝐚𝐭𝐜𝐡 𝑜 | 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 ⇒ ((𝛿, 𝜑 ∘ 𝑔), 𝑡) | 𝖩𝗎𝗌𝗍(𝑖′) ⇒ 𝐥𝐞𝐭 (𝑠, 𝑟) = effectiter Γ (𝑖′)(𝛿, 𝜑 ∘ 𝑔) 𝐢𝐧 (𝑠, 𝑡 ∘ 𝑟)
effectiterΓ : ℑΓ → 𝜕Γ → 𝜕2Γ
effectiterΓ = 𝑖 ↦ (𝛾, 𝜑) ↦
𝐥𝐞𝐭 (𝛿, 𝑔, 𝑜) = 𝑖(𝛾) 𝐢𝐧
𝐥𝐞𝐭 𝑡 = trackΓ(𝑔, pr1 ∘ 𝑖) 𝐢𝐧
𝐦𝐚𝐭𝐜𝐡 𝑜
| 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 ⇒ ((𝛿, 𝜑 ∘ 𝑔), 𝑡)
| 𝖩𝗎𝗌𝗍(𝑖′) ⇒ 𝐥𝐞𝐭 (𝑠, 𝑟) = effectiterΓ (𝑖′)(𝛿, 𝜑 ∘ 𝑔) 𝐢𝐧
(𝑠, 𝑡 ∘ 𝑟)(18)
(18)
At each iteration, the inverse 𝑔 is composed onto 𝜑 in application order, so the accumulator 𝜑 ∘ 𝑔1 ∘ ⋯ ∘ 𝑔𝑘 reverts the effects in LIFO order when applied ( Theorem 16). Because effectiter Γ lands in the same 𝜕Γ → 𝜕2Γ as effectΓ does, an iterator is an effect in its own right and can be used wherever an effect can, and Section 4 reads a component’s whole loading as one iterator. The 𝖬𝖺𝗒𝖻𝖾(ℑ) continuation makes a boundary available between any two consecutive iterations, at which the context is whatever the iterations so far have made it and the accumulator recovers those and nothing more. In this sense the effect iterator is a reified delimited continuation, the structure that mainstream languages expose through the yield operator [38], so the model maps directly onto the generators they already provide.
在每一次迭代中,逆 𝑔 按应用顺序复合到 𝜑 上,因此累加器 𝜑 ∘ 𝑔1 ∘ ⋯ ∘ 𝑔𝑘 在被应用时以后进先出(LIFO)的顺序撤销这些效应(定理 16)。由于 effectiterΓ 落在与 effectΓ 相同的 𝜕Γ → 𝜕2Γ 上,迭代器本身在其自身权利下就是一个效应,因而可以用在任何可以用效应的地方,而第 4 节把一个组件的整个加载读作一个迭代器。𝖬𝖺𝗒𝖻𝖾(ℑ) 延续在任意两次相邻迭代之间提供了一个边界,在该处上下文正是此前各次迭代所造就的样子,而累加器恢复的恰是这些迭代且仅此而已。在这个意义上,效应迭代器是一个被具体化的定界延续,即主流语言通过 yield 运算符 [38] 所暴露的那种结构,因此该模型可以直接映射到它们已经提供的生成器上。
A plain effect function is the degenerate case: an 𝑒 ∈ 𝔈Γ embeds as the iterator whose first iteration already yields 𝖭𝗈𝗍𝗁𝗂𝗇𝗀,
一个普通的效应函数是退化情形:𝑒 ∈ 𝔈Γ 嵌入为这样一个迭代器,其第一次迭代就已经产出 𝖭𝗈𝗍𝗁𝗂𝗇𝗀,
𝛾 ↦ 𝐥𝐞𝐭 (𝛿, 𝑔) = 𝑒(𝛾) 𝐢𝐧 (𝛿, 𝑔, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀) (19)
𝛾 ↦ 𝐥𝐞𝐭 (𝛿, 𝑔) = 𝑒(𝛾) 𝐢𝐧 (𝛿, 𝑔, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀) (19)
and the embedding carries 𝔈∗ Γ into ℑ∗ Γ, the two witnesses asking the same equation. Every notion defined at iterators below is read at an effect function through this embedding.
并且该嵌入把 𝔈∗Γ 送入 ℑ∗Γ,两个见证所要求的是同一个等式。下文在迭代器上定义的每一个概念,都通过该嵌入在效应函数上解读。
Together, these constructions constitute revertible effects: each effect function in 𝔈∗ Γ explicitly provides its own inverse, effect tracks the effect on 𝜕Γ, and the ⋄ operation composes effect functions while preserving revertibility. What they deliver is local temporal composability, local in that the guarantee is read of one component’s effects taken by themselves. We take that to be the following criterion: for every sequence of effect functions a component applies, the accumulator recovers the context it began at ( Theorem 7), and reverting the sequence hands each inverse the state its own application ran against ( Theorem 16). Loading a component is running one iterator and accumulating its inverses in 𝜑; unloading it is applying 𝜑. Two things the criterion leaves out, and both arrive once several components are in play: reverting out of the order the accumulator imposes, and a sequence that interleaves the effects of others. Both are supplied by independence, a condition on the effects rather than a property of the construction (Section 3.4).
这些构造合在一起构成了可撤销效应:𝔈∗Γ 中的每个效应函数都显式地提供它自己的逆,effect 在 𝜕Γ 上跟踪该效应,而 ⋄ 运算在保持可撤销性的同时复合效应函数。它们所带来的是局部的时间可组合性;之所以说是局部的,是因为该保证是就一个组件自身的效应而言来解读的。我们把这一点当作如下判据:对于组件所应用的每一个效应函数序列,累加器都能恢复它所起始的上下文(定理 7),而撤销该序列会把每一个逆交给它自身应用时所针对的状态(定理 16)。加载一个组件就是运行一个迭代器并把它的各个逆累积到 𝜑 中;卸载它就是应用 𝜑。该判据遗漏了两件事,二者都在若干组件同时出现时才到来:不按累加器所强加的顺序撤销,以及一个与他人效应相互交错的序列。两者都由独立性提供,独立性是对效应的一个条件,而不是该构造的一项性质(第 3.4 节)。
3.2. 响应式余效应
3.2. Reactive Coeffects
Spatial composability is the ability for components to declare dependencies on one another and for the system to resolve, provide, and withdraw those dependencies at runtime. This requires 16 that dependency satisfaction be re-evaluated whenever the shared context changes, so that a component activates when its dependencies become available and deactivates when they are withdrawn. We therefore model dependencies of a component as a specification and classify each change to the context, against that specification, as activating, deactivating, or neutral. Classifying against the specification is what detects a change in satisfaction; responding to that classification is what drives activation and deactivation. We call such coeffects reactive: by classifying context changes and driving activation and deactivation from them, local spatial composability becomes a structural guarantee.
空间可组合性是组件之间相互声明依赖、并由系统在运行时解析、提供和撤回这些依赖的能力。这要求每当共享上下文发生变化时就重新评估依赖的满足情况,使得组件在其依赖变为可用时激活、在其依赖被撤回时失活。因此我们把一个组件的依赖建模为一份规约,并针对该规约把上下文的每一次变化分类为激活的、失活的或中性的。按规约分类正是检测满足性变化的东西;对该分类作出响应正是驱动激活与失活的东西。我们称这样的余效应为响应式的:通过对上下文变化分类并由此驱动激活与失活,局部的空间可组合性成为一项结构性保证。
3.2.1. 余效应上下文
3.2.1. Coeffect Context
Traditional inversion-of-control (IoC) containers [39] typically model dependencies as simple key-value mappings. This section formalizes IoC as a coeffect context that synergizes with revertible effects to provide a mathematical foundation for dynamic composition.
传统的控制反转(IoC)容器 [39] 通常把依赖建模为简单的键值映射。本节把 IoC 形式化为一个余效应上下文,它与可撤销效应协同,为动态组合提供数学基础。
Definition 19. Given a type family 𝒱︀ : 𝐾 → Type, define the coeffect context as the dependent partial function type:
定义 19. 给定一个类型族 𝒱︀ : 𝐾 → Type,把余效应上下文定义为依赖的部分函数类型:
Σ ≔ (𝑘 : 𝐾) ⇀ 𝒱︀𝑘 (20)
Σ ≔ (𝑘 : 𝐾) ⇀ 𝒱︀𝑘 (20)
where 𝜎 : Σ is a finite partial function assigning to each 𝑘 ∈ dom(𝜎) ⊆ 𝐾 a value of type 𝒱︀𝑘. We write:
其中 𝜎 : Σ 是一个有限部分函数,它为每一个 𝑘 ∈ dom(𝜎) ⊆ 𝐾 指派一个类型为 𝒱︀𝑘 的值。我们记:
- • 𝜎(𝑘) for application (defined when 𝑘 ∈ dom(𝜎));
𝜎(𝑘) 表示应用(当 𝑘 ∈ dom(𝜎) 时有定义);
- • 𝜎[𝑘 ↦ 𝑣] for the table binding 𝑣 at 𝑘 and agreeing with 𝜎 elsewhere;
𝜎[𝑘 ↦ 𝑣] 表示在 𝑘 处绑定 𝑣、而在其余各处与 𝜎 一致的表;
- • 𝜎 ∖ 𝑘 for restriction (defined when 𝑘 ∈ dom(𝜎));
𝜎 ∖ 𝑘 表示限制(当 𝑘 ∈ dom(𝜎) 时有定义);
- • 𝑘 ∈ dom(𝜎) for membership.
𝑘 ∈ dom(𝜎) 表示成员关系。
The use of a type family 𝒱︀ ensures that each dependency key 𝑘 is associated with a specific value type 𝒱︀𝑘, providing static type safety for dependency access. Extension and restriction carry preconditions, imposed by the operations below: a dependency cannot be provided twice (𝑘 ∉ dom(𝜎) for extension) nor revoked if absent (𝑘 ∈ dom(𝜎) for restriction). A violated precondition is signaled as an error and produces no transition, so the effect algebra, which describes the transitions that do occur, applies to these operations unchanged. A reader prefer- ring to internalize the failure may read every Σ ⇀ Σ below as Σ → 𝖬𝖺𝗒𝖻𝖾(Σ) and compose in the 𝖬𝖺𝗒𝖻𝖾 monad (Section 2.1), at the cost of replacing each identity by the partial identity on the operation’s domain. Based on this context structure, we define two core operations:
使用类型族 𝒱︀ 确保每一个依赖键 𝑘 都关联一个具体的值类型 𝒱︀𝑘,从而为依赖访问提供静态类型安全。扩张与限制带有前提,由下面的运算施加:一个依赖不能被提供两次(扩张时要求 𝑘 ∉ dom(𝜎)),也不能在其缺席时被撤销(限制时要求 𝑘 ∈ dom(𝜎))。被违反的前提以错误的形式发出信号,并且不产生任何转移,因此描述确实发生的那些转移的效应代数可以不加改变地适用于这些运算。希望把失败内化到内部的读者,可以把下面每一个 Σ ⇀ Σ 读作 Σ → 𝖬𝖺𝗒𝖻𝖾(Σ) 并在 𝖬𝖺𝗒𝖻𝖾 单子中复合(第 2.1 节),代价是把每一个恒等替换为该运算定义域上的部分恒等。基于这一上下文结构,我们定义两个核心运算:
Definition 20. The get and set operations on Σ are defined as:
定义 20. Σ 上的 get 与 set 运算定义为:
get : (𝑘 : 𝐾) → Σ ⇀ 𝒱︀𝑘 get = 𝑘 ↦ 𝜎 ↦ 𝜎(𝑘) set : (𝑘 : 𝐾) × 𝒱︀𝑘 → Σ ⇀ Σ × (Σ ⇀ Σ) set = (𝑘, 𝑣) ↦ 𝜎 ↦ (𝜎[𝑘 ↦ 𝑣], 𝜆𝜎′.𝜎′ ∖ 𝑘)
get : (𝑘 : 𝐾) → Σ ⇀ 𝒱︀𝑘
get = 𝑘 ↦ 𝜎 ↦ 𝜎(𝑘)
set : (𝑘 : 𝐾) × 𝒱︀𝑘 → Σ ⇀ Σ × (Σ ⇀ Σ)
set = (𝑘, 𝑣) ↦ 𝜎 ↦ (𝜎[𝑘 ↦ 𝑣], 𝜆𝜎′.𝜎′ ∖ 𝑘)(21)
(21)
where get(𝑘) requires 𝑘 ∈ dom(𝜎) and set(𝑘, 𝑣) requires 𝑘 ∉ dom(𝜎) as preconditions.
其中 get(𝑘) 要求 𝑘 ∈ dom(𝜎)、set(𝑘, 𝑣) 要求 𝑘 ∉ dom(𝜎) 作为前提。
Notably, set(𝑘, 𝑣) has type 𝔈∗ Σ, i.e., an effect function on the coeffect context. We can therefore directly apply the effect machinery from Section 3.1: effectΣ provides automatic tracking and recovery of dependency registrations. This is the synergy between reactive coeffects and revertible effects: coeffect operations are effects, and effects are revertible. 17
值得注意的是,set(𝑘, 𝑣) 具有类型 𝔈∗Σ,即余效应上下文上的一个效应函数。因此我们可以直接应用第 3.1 节的效应机制:effectΣ 提供对依赖注册的自动跟踪与恢复。这正是响应式余效应与可撤销效应之间的协同:余效应运算就是效应,而效应是可撤销的。
3.2.2. 规约与通知
3.2.2. Specification and Notification
The preceding definitions describe how individual dependencies are registered and accessed. Accessing an absent dependency, however, is a runtime failure. A component should therefore activate only once all the dependencies it declares are present, rather than accessing them opti- mistically and failing when one is missing. This raises two questions: whether a component’s declared dependencies are jointly satisfied, and how the system should respond when that status changes. The coeffect context Σ carries a natural observational structure that makes both questions tractable: for any coeffect specification 𝑑 ⊆ 𝐾, define the satisfaction predicate:
前面的定义描述了单个依赖如何被注册和访问。然而,访问一个缺席的依赖是一次运行时失败。因此一个组件应当只在它所声明的全部依赖都齐备时才激活,而不是乐观地访问它们并在缺少某一个时失败。这提出两个问题:一个组件所声明的依赖是否被联合满足,以及当该状态发生变化时系统应当如何响应。余效应上下文 Σ 带有一种自然的观测结构,使这两个问题都变得易于处理:对任意余效应规约 𝑑 ⊆ 𝐾,定义满足性谓词:
𝜎 ⊧ 𝑑 ≔ ∀𝑘 ∈ 𝑑. 𝑘 ∈ dom(𝜎) (22)
𝜎 ⊧ 𝑑 ≔ ∀𝑘 ∈ 𝑑. 𝑘 ∈ dom(𝜎) (22)
This predicate is decidable (since dom(𝜎) is finite). Since all mutations to 𝜎 pass through effect functions (whose inverses recover the previous domain), changes to satisfaction are detectable at each effect boundary. This is the algebraic basis of reactivity: the effect system guarantees that every coeffect change is observed.
该谓词是可判定的(因为 dom(𝜎) 有限)。由于对 𝜎 的所有修改都要经过效应函数(其逆会恢复先前的定义域),满足性的变化可以在每一个效应边界处被检测到。这就是响应式的代数基础:效应系统保证每一次余效应变化都被观察到。
Definition 21. A coeffect specification is:
定义 21. 一份余效应规约是:
𝔇Σ ≔ 𝖲𝖾𝗍(𝐾) (23)
𝔇Σ ≔ 𝖲𝖾𝗍(𝐾) (23)
representing the set of dependencies a component declares from the environment.
表示一个组件从环境所声明的依赖集合。
What makes this specification reactive is how it classifies state transitions. Any effect that transforms 𝜎 to 𝜎′ can be classified by a specification 𝑑 ∈ 𝔇Σ according to whether 𝑑’s satis- faction status is altered:
使这份规约成为响应式的是它对状态转移的分类方式。任何把 𝜎 变换为 𝜎′ 的效应,都可以由规约 𝑑 ∈ 𝔇Σ 按照 𝑑 的满足状态是否被改变来分类:
Definition 22. Given a coeffect specification 𝑑 ⊆ 𝐾 and states 𝜎, 𝜎′ ∈ Σ, define:
定义 22. 给定余效应规约 𝑑 ⊆ 𝐾 与状态 𝜎, 𝜎′ ∈ Σ,定义:
notify𝑑(𝜎, 𝜎′) ≔ { activating if 𝜎 ⊭ 𝑑 ∧ 𝜎′ ⊧ 𝑑 deactivating if 𝜎 ⊧ 𝑑 ∧ 𝜎′ ⊭ 𝑑 neutral otherwise
notify𝑑(𝜎, 𝜎′) ≔
{ activating if 𝜎 ⊭ 𝑑 ∧ 𝜎′ ⊧ 𝑑
deactivating if 𝜎 ⊧ 𝑑 ∧ 𝜎′ ⊭ 𝑑
neutral otherwise(24)
(24)
An activating transition triggers the execution of the component’s effects, tracked as Sec- tion 3.1 prescribes, and a deactivating transition triggers recovery by applying the accumulator. The activation and deactivation so triggered receive their operational semantics in Section 4.
激活类转移触发组件效应的执行,并按第 3.1 节的规定加以跟踪;失活类转移则通过应用累加器触发恢复。如此触发的激活与失活在第 4 节获得其操作语义。
What set and notify deliver together is local spatial composability, local in the same sense as before, the guarantee being read of one component’s coeffects taken by themselves. We take that to be the following criterion: a component activates only at a state satisfying its specification, so it never reads a binding that is absent, and every change to the context is classified against that specification, so a loss of satisfaction is detected where it happens and drives a deactivation. Both halves are immediate from the definitions above, satisfaction being a precondition checked where the component would activate and notify𝑑 being defined at every transition; one direction of the coeffect ordering comes with the first half, a component activating only after the components that provide its declared keys. Two things the criterion leaves out, and both arrive once several components are in play: withdrawing a binding only after the deacti- vations it causes have finished, and keeping the bindings an activation reads unmoved while the activation runs. Both are conditions on other components rather than on the one acting, so they belong to the global form of the guarantee, which Section 4.3.3 establishes. 18
set 与 notify 共同带来的是局部的空间可组合性;这里"局部"的含义与之前相同,即该保证是就一个组件自身的余效应而言来解读的。我们把这一点当作如下判据:一个组件只在满足其规约的状态下激活,因此它从不读取一个缺席的绑定;并且上下文的每一次变化都针对该规约被分类,因此满足性的丧失在其发生之处被检测到并驱动一次失活。两半都直接来自上面的定义:满足性是在组件将要激活之处被检查的前提,而 notify𝑑 定义在每一次转移上;余效应序关系的其中一个方向来自前一半,即一个组件只在其所声明键的提供者之后激活。该判据遗漏了两件事,二者都在若干组件同时出现时才到来:只有在某次撤回所引起的失活全部结束之后才撤回该绑定,以及在一次激活运行期间保持它所读取的绑定不被移动。两者都是对他人的条件,而不是对行动者自身的条件,因此它们属于该保证的全局形式,由第 4.3.3 节确立。
3.2.3. 隔离与拦截
3.2.3. Isolation and Interception
The basic coeffect context Σ models a flat dependency table. In practice, however, the system may need to bind distinct values to the same logical dependency for different components. This section extends the coeffect context with two mechanisms: coeffect isolation (the same key resolves differently in different contexts) and coeffect interception (cross-cutting behavior on dependency access).
基本的余效应上下文 Σ 刻画的是一张扁平的依赖表。然而在实践中,系统可能需要为不同组件把不同的值绑定到同一个逻辑依赖上。本节用两种机制扩展余效应上下文:余效应隔离(同一个键在不同上下文中解析到不同的结果)与余效应拦截(依赖访问上的横切行为)。
Realization. The two mechanisms differ from get and set in what they act on. A provision writes the shared table every component reads, so it is an effect on that table and carries an inverse to withdraw it. Isolation and interception instead adjust how a key is resolved for the components under one context, leaving the table itself as it stands. Typing an operation as an effect fixes its denotation, a successor state paired with an inverse, but not its realization, which determines how that inverse is carried out.
实现。这两种机制与 get 和 set 的区别在于它们所作用的对象不同。一次提供会写入所有组件都读取的共享表,所以它是对该表的一个效应,并携带一个用于撤回它的逆操作。隔离与拦截调整的则是某个键在某个上下文之下如何被解析,而表本身原封不动。把一个操作刻画为效应,就固定了它的指称——一个后继状态配上一个逆操作——但没有固定它的实现,而实现决定了这个逆操作如何被执行。
Definition 23. An effect function on a context admits two realizations:
定义 23. 上下文上的一个效应函数容许两种实现:
- • In-place realization mutates the context and returns a nontrivial inverse; the successor aliases the input, and recovery runs the inverse to undo the mutation.
原地实现修改上下文并返回一个非平凡的逆操作;后继状态与输入互为别名,恢复时运行该逆操作以撤销这次修改。
- • Derived realization leaves the input intact and returns a fresh context deriving from it, with the identity as its inverse; recovery discards the derived context. A context derived from another is what the recursive structure of Definition 28 carries.
派生实现保持输入不变,返回一个由它派生出来的新上下文,并以恒等映射作为其逆操作;恢复时丢弃这个派生上下文。从一个上下文派生出另一个上下文,正是定义 28 的递归结构所承载的内容。
In a purely functional setting the two coincide, and an imperative host may choose either per operation; Section 5.1.2 implements both. Isolation and interception are given derived realization outright: each produces a fresh context whose own table differs from the inherited one, so each is typed below as a map from context to context rather than as an effect function. Nothing in the shared table changes, so there is no effect to track and nothing for Definition 12 to lift, and recovery discards the derived context along with the adjustment it carried. Assignment on a derived table overrides whatever the inherited table held at the key, which is why neither operation carries a precondition.
在纯函数式的设定下两者重合,而命令式的宿主可以为每个操作任选其一;第 5.1.2 节两种都实现了。隔离与拦截被直接赋予派生实现:各自产生一个新上下文,其自身的表不同于所继承的表,因此下面把二者都刻画为从上下文到上下文的映射,而不是效应函数。共享表中没有任何东西改变,于是没有需要追踪的效应,也没有定义 12 需要提升的东西;恢复时连同所携带的调整一起丢弃派生上下文。在派生表上做赋值会覆盖所继承的表在该键处原有的内容,这就是为什么这两个操作都不带前置条件。
Coeffect Isolation. By introducing isolation realms, coeffect isolation allows the same depen- dency to bind to different values in different contexts. This has broad applications in multi- tenant systems, testing environments, and component sandboxes.
余效应隔离。通过引入隔离域,余效应隔离允许同一个依赖在不同上下文中绑定到不同的值。这在多租户系统、测试环境与组件沙箱中有着广泛的应用。
Definition 24. Define the coeffect context with isolation as:
定义 24. 定义带隔离的余效应上下文为:
Σiso ≔ (𝐾 ⇀ 𝑅) × ((𝑟 : 𝑅) ⇀ 𝒱︀𝑟) (25)
Σiso ≔ (𝐾 ⇀ 𝑅) × ((𝑟 : 𝑅) ⇀ 𝒱︀𝑟) (25)It can be represented as a pair (𝜌, 𝜎), where:
它可以表示为一个二元组 (𝜌, 𝜎),其中:
- • 𝜌 : 𝐾 ⇀ 𝑅 is the isolation realm table, assigning a realm identifier to each isolated key; a key outside dom(𝜌) resolves to its own realm, so we write 𝜌(𝑘) = 𝑘 there (𝑅 ⊇ 𝐾);
𝜌 : 𝐾 ⇀ 𝑅 是隔离域表,为每个被隔离的键指派一个域标识符;dom(𝜌) 之外的键解析到它自己的域,因此在那里我们记作 𝜌(𝑘) = 𝑘(𝑅 ⊇ 𝐾);
- • 𝜎 : (𝑟 : 𝑅) ⇀ 𝒱︀𝑟 is the dependency table, a partial dependent function from realm identi- fiers to typed values.
𝜎 : (𝑟 : 𝑅) ⇀ 𝒱︀𝑟 是依赖表,一个从域标识符到带类型值的部分依赖函数。
The two-layer mapping structure decouples the logical layer from the storage layer, making dependency access context-aware. When accessing a key 𝑘, the system first resolves 𝜌(𝑘) to obtain a realm identifier 𝑟, then accesses 𝜎(𝑟) for the actual value.
两层映射结构把逻辑层与存储层解耦,使依赖访问具备上下文感知能力。当访问一个键 𝑘 时,系统先解析 𝜌(𝑘) 得到一个域标识符 𝑟,再访问 𝜎(𝑟) 取得实际的值。
Definition 25. The get, set, and isolate operations on Σiso are: 19
定义 25. Σiso 上的 get、set 与 isolate 操作为:
get : (𝑘 : 𝐾) → Σiso ⇀ 𝒱︀𝜌(𝑘) get = 𝑘 ↦ (𝜌, 𝜎) ↦ 𝜎(𝜌(𝑘)) set : (𝑘 : 𝐾) × 𝒱︀𝜌(𝑘) → Σiso ⇀ Σiso × (Σiso ⇀ Σiso) set = (𝑘, 𝑣) ↦ (𝜌, 𝜎) ↦ ((𝜌, 𝜎[𝜌(𝑘) ↦ 𝑣]), 𝜆(𝜌′, 𝜎′).(𝜌′, 𝜎′ ∖ 𝜌′(𝑘))) isolate : 𝐾 × 𝑅 → Σiso → Σiso isolate = (𝑘, 𝑟) ↦ (𝜌, 𝜎) ↦ (𝜌[𝑘 ↦ 𝑟], 𝜎) (26)
get : (𝑘 : 𝐾) → Σiso ⇀ 𝒱︀𝜌(𝑘)
get = 𝑘 ↦ (𝜌, 𝜎) ↦ 𝜎(𝜌(𝑘))
set : (𝑘 : 𝐾) × 𝒱︀𝜌(𝑘) → Σiso ⇀ Σiso × (Σiso ⇀ Σiso)
set = (𝑘, 𝑣) ↦ (𝜌, 𝜎) ↦ ((𝜌, 𝜎[𝜌(𝑘) ↦ 𝑣]), 𝜆(𝜌′, 𝜎′).(𝜌′, 𝜎′ ∖ 𝜌′(𝑘)))
isolate : 𝐾 × 𝑅 → Σiso → Σiso
isolate = (𝑘, 𝑟) ↦ (𝜌, 𝜎) ↦ (𝜌[𝑘 ↦ 𝑟], 𝜎)
(26)where get and set carry the preconditions of Definition 20 transported along 𝜌, namely 𝜌(𝑘) ∈ dom(𝜎) and 𝜌(𝑘) ∉ dom(𝜎). The context that isolate(𝑘, 𝑟) derives assigns the realm 𝑟 to 𝑘 and inherits the dependency table unchanged, so a key already isolated is reassigned rather than refused.
其中 get 与 set 携带定义 20 沿 𝜌 输送过来的前置条件,即 𝜌(𝑘) ∈ dom(𝜎) 与 𝜌(𝑘) ∉ dom(𝜎)。isolate(𝑘, 𝑟) 所派生的上下文把域 𝑟 指派给 𝑘,并不加改动地继承依赖表,因此一个已经被隔离的键会被重新指派,而不是被拒绝。
The coeffect isolation mechanism essentially implements a runtime ad-hoc polymorphism system. Through isolation realm identifiers, the same dependency key can resolve to entirely different values in different contexts, and this polymorphism can be dynamically adjusted at runtime. Compared to traditional dependency injection, coeffect isolation provides finer- grained control, enabling customized isolation for specific components; set remains an effect function (𝔈∗ Σiso) and thus inherits revertibility, whereas isolate needs none, deriving a context instead of writing the shared table.
余效应隔离机制本质上实现了一个运行时的特设多态系统。通过隔离域标识符,同一个依赖键可以在不同上下文中解析到完全不同的值,而且这种多态可以在运行时动态调整。与传统的依赖注入相比,余效应隔离提供了更细粒度的控制,能够为特定的组件做定制化的隔离;set 仍然是效应函数(𝔈∗Σiso),因而继承了可撤销性,而 isolate 不需要任何东西,它派生出一个上下文而不是写入共享表。
Coeffect Interception. The second mechanism, coeffect interception, attaches cross-cutting metadata to dependency access, adding behavior without modifying the dependency value. This metadata can be either context-carried or component-declared, so we extend both the coeffect context and the coeffect specification:
余效应拦截。第二种机制——余效应拦截——把横切元数据附加到依赖访问上,在不修改依赖值的前提下增添行为。这种元数据既可以由上下文携带,也可以由组件声明,因此我们同时扩展余效应上下文与余效应规格:
Definition 26. Define the coeffect context and specification with interception as:
定义 26. 定义带拦截的余效应上下文与规格为:
Σinter ≔ ((𝑘 : 𝐾) → ℳ︀𝑘) × ((𝑘 : 𝐾) ⇀ (ℳ︀𝑘 → 𝒱︀𝑘)) 𝔇inter ≔ (𝑘 : 𝐾) ⇀ ℳ︀𝑘 (27)
Σinter ≔ ((𝑘 : 𝐾) → ℳ︀𝑘) × ((𝑘 : 𝐾) ⇀ (ℳ︀𝑘 → 𝒱︀𝑘))
𝔇inter ≔ (𝑘 : 𝐾) ⇀ ℳ︀𝑘
(27)The context Σinter is a pair (𝜄, 𝜎): 𝜄 is the context-carried metadata installed on the context itself, empty (𝜖𝑘) by default; and 𝜎 maps each key 𝑘 to a provider function from metadata ℳ︀𝑘 to value 𝒱︀𝑘. A specification 𝑑 ∈ 𝔇inter carries the component-declared metadata, assigning each key its metadata 𝑑(𝑘), with dom(𝑑) serving as the dependency set. Each key equips its metadata with a monoid (ℳ︀𝑘, ⊕𝑘, 𝜖𝑘): the merge ⊕𝑘 is associative with identity 𝜖𝑘 (the empty metadata).
上下文 Σinter 是一个二元组 (𝜄, 𝜎):𝜄 是安装在上下文本身之上的、由上下文携带的元数据,默认为空(𝜖𝑘);𝜎 把每个键 𝑘 映射为一个从元数据 ℳ︀𝑘 到值 𝒱︀𝑘 的提供者函数。一个规格 𝑑 ∈ 𝔇inter 携带组件声明的元数据,为每个键指派其元数据 𝑑(𝑘),并以 dom(𝑑) 作为依赖集。每个键为其元数据配备一个幺半群 (ℳ︀𝑘, ⊕𝑘, 𝜖𝑘):合并运算 ⊕𝑘 满足结合律,单位元是 𝜖𝑘(空元数据)。
Definition 27. The get, set, and intercept operations on Σinter are:
定义 27. Σinter 上的 get、set 与 intercept 操作为:
get : (𝑘 : 𝐾) × ℳ︀𝑘 → Σinter ⇀ 𝒱︀𝑘 get = (𝑘, 𝜇) ↦ (𝜄, 𝜎) ↦ 𝜎(𝑘)(𝜇 ⊕𝑘 𝜄(𝑘)) set : (𝑘 : 𝐾) × (ℳ︀𝑘 → 𝒱︀𝑘) → Σinter ⇀ Σinter × (Σinter ⇀ Σinter) set = (𝑘, 𝜓) ↦ (𝜄, 𝜎) ↦ ((𝜄, 𝜎[𝑘 ↦ 𝜓]), 𝜆(𝜄′, 𝜎′).(𝜄′, 𝜎′ ∖ 𝑘)) intercept : (𝑘 : 𝐾) × ℳ︀𝑘 → Σinter → Σinter intercept = (𝑘, 𝜈) ↦ (𝜄, 𝜎) ↦ (𝜄[𝑘 ↦ 𝜄(𝑘) ⊕𝑘 𝜈], 𝜎) (28)
get : (𝑘 : 𝐾) × ℳ︀𝑘 → Σinter ⇀ 𝒱︀𝑘
get = (𝑘, 𝜇) ↦ (𝜄, 𝜎) ↦ 𝜎(𝑘)(𝜇 ⊕𝑘 𝜄(𝑘))
set : (𝑘 : 𝐾) × (ℳ︀𝑘 → 𝒱︀𝑘) → Σinter ⇀ Σinter × (Σinter ⇀ Σinter)
set = (𝑘, 𝜓) ↦ (𝜄, 𝜎) ↦ ((𝜄, 𝜎[𝑘 ↦ 𝜓]), 𝜆(𝜄′, 𝜎′).(𝜄′, 𝜎′ ∖ 𝑘))
intercept : (𝑘 : 𝐾) × ℳ︀𝑘 → Σinter → Σinter
intercept = (𝑘, 𝜈) ↦ (𝜄, 𝜎) ↦ (𝜄[𝑘 ↦ 𝜄(𝑘) ⊕𝑘 𝜈], 𝜎)
(28)where get and set carry the preconditions of Definition 20 on the provider table, namely 𝑘 ∈ dom(𝜎) and 𝑘 ∉ dom(𝜎). The context that intercept(𝑘, 𝜈) derives merges 𝜈 onto the metadata inherited at 𝑘 and inherits the provider table unchanged. 20
其中 get 与 set 携带定义 20 在提供者表上的前置条件,即 𝑘 ∈ dom(𝜎) 与 𝑘 ∉ dom(𝜎)。intercept(𝑘, 𝜈) 所派生的上下文把 𝜈 合并到 𝑘 处所继承的元数据上,并不加改动地继承提供者表。
When a component with specification 𝑑 accesses key 𝑘, the system evaluates 𝜎(𝑘)(𝑑(𝑘) ⊕𝑘 𝜄(𝑘)): the component-declared metadata is merged with the context-carried metadata 𝜄, and the provider function is applied to the result. This merge follows each key’s own semantics (e.g. scalar fields are overwritten, set-valued fields unioned) and is right-biased, so 𝜄(𝑘) takes priority and can override the component’s declaration, letting an enclosing context constrain how a component uses a coeffect without modifying that component (e.g. Section 6.3).
当规格为 𝑑 的组件访问键 𝑘 时,系统求值 𝜎(𝑘)(𝑑(𝑘) ⊕𝑘 𝜄(𝑘)):组件声明的元数据与上下文携带的元数据 𝜄 相合并,提供者函数再作用于合并的结果。这次合并遵循每个键自身的语义(例如标量字段被覆盖,集合值字段取并集),并且是右偏的,因此 𝜄(𝑘) 优先,可以覆盖组件的声明,从而让外层上下文得以约束一个组件如何使用某个余效应,而无须修改该组件本身(例如第 6.3 节)。
3.3. 上下文范式
3.3. The Context Paradigm
Section 3.1 and Section 3.2 each act on a context, the first as the carrier of effects and the second as the carrier of coeffects. Section 3.3.1 constructs a unified context carrying both, gives each of its keys a set of operations, and establishes the context paradigm by constraining the stages of an effect iterator ( Definition 30). Section 3.3.2 then makes the operations the standard of comparison: two context states are observationally equivalent when no sequence of operations distinguishes them, and every equality of Section 3.1 is re-read up to that equivalence.
第 3.1 节与第 3.2 节各自作用于一个上下文,前者把上下文作为效应的载体,后者把它作为余效应的载体。第 3.3.1 节构造一个同时承载二者的统一上下文,为其中每个键给出一组操作,并通过约束效应迭代器的各个阶段来确立上下文范式(定义 30)。第 3.3.2 节随后把这些操作确立为比较的标准:当没有任何操作序列能区分两个上下文状态时,二者观测等价;第 3.1 节中的每个等式都要在这一等价关系下重新解读。
3.3.1. 统一上下文
3.3.1. Unified Context
For a context Γ, the effect context 𝜕Γ (Section 3.1) provides a higher-level abstraction, carrying the previous-level context and that level’s accumulator ( Definition 2). Making this structure recursive and combining it with the coeffect context Σ yields the following type:
对于一个上下文 Γ,效应上下文 𝜕Γ(第 3.1 节)提供了更高层的抽象,它承载上一层的上下文以及该层的累加器(定义 2)。把这一结构做成递归的,并与余效应上下文 Σ 相结合,便得到如下类型:
Definition 28. The context type Γ∞ is defined as:
定义 28. 上下文类型 Γ∞ 定义为:
Γ∞ ≔ 𝜇Γ. Γ × (Γ → Γ) × Σ (29)
Γ∞ ≔ 𝜇Γ. Γ × (Γ → Γ) × Σ (29)where the three projections are:
其中三个投影分别是:
- • Γ: the current context state (recursive);
Γ:当前的上下文状态(递归的);
- • Γ → Γ: the accumulator, which reverts this level’s effects;
Γ → Γ:累加器,用于撤销本层的效应;
- • Σ: the coeffect context carrying dependency information.
Σ:承载依赖信息的余效应上下文。
Under this definition, effect maps 𝔈Γ∞ to itself, unifying the 𝜕-tower into a single self- similar type. The coeffect context Σ is structurally integrated: dependency operations (set, get) act on Σ, and the accumulator holds their inverses. Since the type family 𝒱︀ underlying Σ is unconstrained, any state the system needs to share across components can be encoded as a dependency with an appropriate value type— Σ subsumes all shared mutable states, not just inter-component dependencies. Every interaction between a component and its environment passes through this single entity.
在此定义下,效应 𝔈 把 Γ∞ 映射到自身,把 𝜕 塔统一为一个自相似的类型。余效应上下文 Σ 在结构上被整合进来:依赖操作(set、get)作用于 Σ,而累加器持有它们的逆操作。由于支撑 Σ 的类型族 𝒱︀ 不受约束,系统需要在组件之间共享的任何状态都可以被编码为一个带有合适值类型的依赖——Σ 涵盖了所有共享可变状态,而不仅仅是组件间的依赖。组件与其环境之间的每一次交互都要经由这同一个实体。
Passing through one entity is a discipline only where there is nothing else to pass through, so what a component may do with a bound value has to be fixed as well. A key therefore carries more than a value type:
只有在确实没有别的东西可经由时,"经由同一个实体"才算得上一种纪律,因此组件能对一个已绑定的值做什么,也必须被固定下来。于是,一个键所承载的不只是一个值类型:
Definition 29. A coeffect at a key 𝑘 is a pair (𝒱︀𝑘, 𝒜︀𝑘), where 𝒱︀𝑘 is the value type of Definition 19 and 𝒜︀𝑘 is a set of coeffect operations, the operations the value bound at 𝑘 provides to a component holding it. An operation 𝑎 ∈ 𝒜︀𝑘 carries an argument type 𝑋𝑎 and an outcome type 𝐵𝑎, and acts on the value alone:
定义 29. 键 𝑘 处的一个余效应是一个二元组 (𝒱︀𝑘, 𝒜︀𝑘),其中 𝒱︀𝑘 是定义 19 的值类型,𝒜︀𝑘 是一组余效应操作,即 𝑘 处所绑定的值提供给持有它的组件的那些操作。一个操作 𝑎 ∈ 𝒜︀𝑘 带有参数类型 𝑋𝑎 与结果类型 𝐵𝑎,并且只作用于该值本身:
𝑎 : 𝑋𝑎 → 𝒱︀𝑘 ⇀ 𝒱︀𝑘 × (𝒱︀𝑘 ⇀ 𝒱︀𝑘) × 𝐵𝑎 (30) 21
𝑎 : 𝑋𝑎 → 𝒱︀𝑘 ⇀ 𝒱︀𝑘 × (𝒱︀𝑘 ⇀ 𝒱︀𝑘) × 𝐵𝑎 (30)its first two constituents forming an effect function on 𝒱︀𝑘 witnessed as Definition 8 requires, and its third an outcome. The operations induce the equivalence ≃𝑘 on 𝒱︀𝑘 up to which values at 𝑘 are compared (Section 3.3.2). An operation acts on the coeffect context through its lift
它的前两个成分构成 𝒱︀𝑘 上的一个效应函数,其见证方式如定义 8 所要求,第三个成分是一个结果。这些操作在 𝒱︀𝑘 上诱导出等价关系 ≃𝑘,𝑘 处的取值就在该等价关系下被比较(第 3.3.2 节)。一个操作通过它的提升作用于余效应上下文:
𝑎Σ(𝑥)(𝜎) ≔ 𝐥𝐞𝐭 (𝑣, 𝑔, 𝑏) = 𝑎(𝑥)(𝜎(𝑘)) 𝐢𝐧 (𝜎[𝑘 ↦ 𝑣], 𝜆𝜎′.𝜎′[𝑘 ↦ 𝑔(𝜎′(𝑘))], 𝑏) (31)
𝑎Σ(𝑥)(𝜎) ≔ 𝐥𝐞𝐭 (𝑣, 𝑔, 𝑏) = 𝑎(𝑥)(𝜎(𝑘)) 𝐢𝐧 (𝜎[𝑘 ↦ 𝑣], 𝜆𝜎′.𝜎′[𝑘 ↦ 𝑔(𝜎′(𝑘))], 𝑏) (31)defined when 𝑘 ∈ dom(𝜎), whose first two constituents are an effect function on Σ. Typing an operation of 𝑘 on 𝒱︀𝑘 is what confines it to the binding at 𝑘: the lift reads and writes that binding and leaves every other key as it stands, so no side condition is needed to say so. Where isolation is in force the binding it reaches is the one the realm resolves to (Definition 24), two keys sharing a realm sharing one binding. An operation whose behavior turns on another key reads that key’s value into its argument 𝑋𝑎, and the reactive discipline of Section 3.2.2 is what holds the binding in place for as long as the component that read it runs ( Theorem 70), the value moving only by operations of that key. The pair is not yet the whole of a coeffect: once the independence of two operations has been defined, Section 3.4.2 completes the pair with a third constituent, a witness certifying that the operations of 𝒜︀𝑘 are pairwise independent.
该式在 𝑘 ∈ dom(𝜎) 时有定义,其前两个成分是 Σ 上的一个效应函数。把 𝑘 的一个操作刻画在 𝒱︀𝑘 上,就是把该操作限定在 𝑘 处的绑定上:这一提升读写该绑定,而让其他每个键保持原样,因此不需要任何附加条件来额外说明这一点。在隔离生效的地方,它所触及的绑定就是那个域所解析到的绑定(定义 24),共享同一个域的两个键共享同一个绑定。一个行为取决于另一个键的操作,会把那个键的值读入它的参数 𝑋𝑎;而第 3.2.2 节的响应式纪律,负责在读取该值的组件运行期间把绑定保持在原位(定理 70),该值只因该键自身的操作而变动。这个二元组还不是余效应的全部:一旦定义了两个操作之间的独立性,第 3.4.2 节就用第三个成分把它补全——一个见证,用以证明 𝒜︀𝑘 中的操作两两独立。
What a component performs is a sequence of stages in which each may depend on what the ones before it yielded: an operation on a value some key binds, or the provision of a binding of its own. Iterators of that shape, one stage per iteration, are the form the discipline takes at an effect function.
一个组件所执行的是一个阶段序列,其中每个阶段都可能依赖于前面的阶段所产生的结果:对某个键所绑定的值做一次操作,或者提供一个它自己的绑定。这种形状的迭代器——每次迭代一个阶段——就是该纪律在一个效应函数上所采取的形式。
Definition 30. For key sets 𝑃 ⊆ 𝑆 ⊆ 𝐾, the context-mediated iterators ℑ𝒜︀ Σ(𝑆, 𝑃) form the least set of iterators on Σ that contains the unit 𝜎 ↦ (𝜎, idΣ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀) and, whenever each named continuation is 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 or 𝖩𝗎𝗌𝗍 of a member, contains
定义 30. 对于键集 𝑃 ⊆ 𝑆 ⊆ 𝐾,上下文中介的迭代器 ℑ𝒜︀Σ(𝑆, 𝑃) 构成 Σ 上迭代器的最小集合,它包含单位元 𝜎 ↦ (𝜎, idΣ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀),并且只要每个被命名的延续是 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 或某个成员的 𝖩𝗎𝗌𝗍,就还包含
𝜎 ↦ 𝐥𝐞𝐭 (𝛿, 𝑠, 𝑏) = 𝑎Σ(𝑥)(𝜎) 𝐢𝐧 (𝛿, 𝑠, 𝑐𝑏) for 𝑘 ∈ 𝑆, 𝑎 ∈ 𝒜︀𝑘, 𝑥 : 𝑋𝑎, (𝑐𝑏)𝑏∈𝐵𝑎 𝜎 ↦ 𝐥𝐞𝐭 (𝛿, 𝑠) = set(𝑘, 𝑣)(𝜎) 𝐢𝐧 (𝛿, 𝑠, 𝑐) for 𝑘 ∈ 𝑃, 𝑣 : 𝒱︀𝑘, 𝑐 (32)
𝜎 ↦ 𝐥𝐞𝐭 (𝛿, 𝑠, 𝑏) = 𝑎Σ(𝑥)(𝜎) 𝐢𝐧 (𝛿, 𝑠, 𝑐𝑏) for 𝑘 ∈ 𝑆, 𝑎 ∈ 𝒜︀𝑘, 𝑥 : 𝑋𝑎, (𝑐𝑏)𝑏∈𝐵𝑎
𝜎 ↦ 𝐥𝐞𝐭 (𝛿, 𝑠) = set(𝑘, 𝑣)(𝜎) 𝐢𝐧 (𝛿, 𝑠, 𝑐) for 𝑘 ∈ 𝑃, 𝑣 : 𝒱︀𝑘, 𝑐
(32)An operation stage performs one coeffect operation and chooses what follows it by the outcome, so an argument may depend on the outcomes already obtained. A provision stage installs one binding, at a key no operation can create, an operation presupposing the binding it acts on, and yields the restriction set pairs with the extension (Definition 20). The stages occurring in a member are its own and those of every iterator its continuations reach.
一个操作阶段执行一次余效应操作,并按其结果选择后续,因此某个参数可以依赖于已经得到的结果。一个提供阶段安装一个绑定,所安装的键是任何操作都无法创建的——一个操作以它所作用的那个绑定为前提——并给出限制集对以及相应的扩展(定义 20)。一个成员中出现的阶段,是它自己的阶段,加上它的延续所到达的每个迭代器的那些阶段。
Membership in this class is the formal content of mediating every interaction through the context. What falls outside it is a map reading anything else, whether a key it performs no stage at or a location no key binds; an allocator drawing handles from a counter the context does not carry is the second case, and becomes context-mediated once the counter is bound at a key of its own.
属于这一类是"通过上下文中介每一次交互"这一说法的形式内容。落在它之外的,是读取任何其他东西的映射:无论是一个它没有执行任何阶段的键,还是一个没有键绑定的位置;一个从上下文并未携带的计数器中取句柄的分配器属于第二种情形,而一旦该计数器被绑定到它自己的某个键上,它就成为上下文中介的了。
Hierarchical composition. The recursive structure of Γ∞ supports hierarchical control: a parent context aggregates multiple child-level effects, forming a tree-shaped control structure that maintains modularity while enabling unified cross-level management. The effect transfor- mation realizes a literal “plug-in” metaphor:
层次化组合。Γ∞ 的递归结构支持层次化的控制:父上下文聚合多个子层的效应,形成树形的控制结构,在保持模块化的同时实现统一的跨层管理。效应转换实现了一个字面意义上的"插入式"隐喻:
- • Loading a component corresponds to executing its effects (plugging in);
加载一个组件对应于执行它的效应(插入);
- • Unloading a component corresponds to reverting its effects (unplugging, without affect- ing other running components); 22
卸载一个组件对应于撤销它的效应(拔出,且不影响其他正在运行的组件);
- • Components at different levels of the hierarchy are independently loadable and unload- able; a parent context aggregates and manages the effects of all its children, enabling arbitrarily nested composition.
处于层次结构不同层级的组件可以独立地加载与卸载;父上下文聚合并管理其所有子上下文的效应,从而实现任意嵌套的组合。
3.3.2. 观测等价
3.3.2. Observational Equivalence
The recovery guarantee of Section 3.1 asserts an equality of states ( Theorem 7), which is an idealization, because the physical state cannot be recovered as it stood. For example, free releases a block to the allocator without restoring the layout the heap had before malloc; and a generative name is not restored by the inverse that discards it, since the next creation draws a fresh one [40]. The equalities of Section 3 are therefore to be read up to an equivalence ≃, and we take ≃ to be an observational equivalence: two states are related when no observer can distinguish them. Comparing behavior rather than representation is the established route to program equivalence [41], and the relation such a comparison yields depends on what the observer is given to work with [42]. What an observer of a context is given is the coeffects it carries, and what an observer of a value is given is the operations of its key ( Definition 29), so the relation at each key is generated from those operations, and the relation on a context is assembled from the relations at its keys. Both constructions are the business of this subsection, and quotienting by the result is what makes the independence of Section 3.4.1 attainable.
第 3.1 节的恢复保证断言了状态之间的相等(定理 7),这是一种理想化,因为物理状态无法原样恢复。例如,free 把一块内存释放给分配器,却没有恢复 malloc 之前堆所拥有的布局;一个生成式名字也不会因为丢弃它的那个逆操作而被恢复,因为下一次创建会取用一个全新的名字 [40]。因此,第 3 节中的各个等式都要在某个等价关系 ≃ 之下来解读,而我们取 ≃ 为观测等价:当没有观察者能够区分两个状态时,二者相关。比较行为而不是比较表示,是通往程序等价的既定路径 [41],而这种比较所得到的关系,取决于观察者被给予什么可供操作 [42]。一个上下文的观察者所被给予的是它所携带的余效应,一个值的观察者所被给予的是其键上的那些操作(定义 29),因此每个键上的关系由这些操作生成,而上下文上的关系则由各键上的关系组装而成。这两种构造正是本小节的任务,而对所得结果取商,才使得第 3.4.1 节的独立性得以达成。
An observer of a value runs the operations of its key and reads their outcomes.
一个值的观察者运行其键上的操作并读取它们的结果。
Definition 31. Let 𝑉 carry a set 𝒜︀ of operations in the sense of Definition 29. A test over 𝒜︀ is a finite word whose letters are forward maps and yielded inverses of the effect functions 𝑎(𝑥), over every 𝑎 ∈ 𝒜︀ and every argument 𝑥 : 𝑋𝑎, each letter applied to the value the letters before it left; its outcomes are those the letters that are forward maps yield along the way, and it is undefined where a precondition fails. Values 𝑣, 𝑣′ : 𝑉 are indistinguishable, written 𝑣 ≈𝒜︀ 𝑣′, when every test over 𝒜︀ is defined at both or at neither and yields the same outcomes at both.
定义 31. 设 𝑉 在定义 29 的意义下带有一组操作 𝒜︀。𝒜︀ 上的一个测试是一个有限的字,其字母是效应函数 𝑎(𝑥) 的正向映射与所产出的逆操作,遍历每个 𝑎 ∈ 𝒜︀ 与每个参数 𝑥 : 𝑋𝑎,每个字母作用于它之前的字母所留下的值;它的结果是那些作为正向映射的字母沿途产出的结果,在前置条件失败之处无定义。值 𝑣, 𝑣′ : 𝑉 不可区分,记作 𝑣 ≈𝒜︀ 𝑣′,当且仅当 𝒜︀ 上的每个测试在二者处都有定义或都无定义,并且在二者处产出相同的结果。
The equivalence at a key is indistinguishability under its own operations:
一个键上的等价关系,就是在它自身的操作之下的不可区分性:
≃𝑘 ≔ ≈𝒜︀𝑘 (33)
≃𝑘 ≔ ≈𝒜︀𝑘
(33)An operation respects an equivalence when, at related values, it is defined at both or at neither and, where defined, yields related successors, inverses carrying related values to related values, and equal outcomes.
一个操作尊重某个等价关系,是指:在相关的值处,它要么在二者处都有定义,要么在二者处都无定义;并且在有定义时,所产出的后继状态相关、逆操作把相关的值映射到相关的值,且结果相等。
Lemma 32. Each ≃𝑘 is an equivalence that every operation of 𝒜︀𝑘 respects, and it is the coarsest such relation. That is,
引理 32. 每个 ≃𝑘 都是一个等价关系,𝒜︀𝑘 的每个操作都尊重它,并且它是此类关系中最粗的一个。也就是说,
- 1. ≈𝒜︀ is an equivalence, and every operation of 𝒜︀ respects it;
≈𝒜︀ 是一个等价关系,且 𝒜︀ 的每个操作都尊重它;
- 2. every equivalence that every operation of 𝒜︀ respects is contained in ≈𝒜︀.
每一个被 𝒜︀ 的每个操作所尊重的等价关系,都包含于 ≈𝒜︀ 之中。
Proof.
证明。
- 1. Agreement of tests, in definedness and in outcomes, is reflexive, symmetric, and transitive. Let 𝑣 ≈𝒜︀ 𝑣′ and let 𝑎 ∈ 𝒜︀ be applied to an argument. Prefixing a test by one letter is again a test, so the values the forward map reaches are indistinguishable, as are the values any one yielded inverse reaches from indistinguishable arguments; the one-letter test gives definedness at both or neither and equality of the outcome.
测试在定义性与结果上的一致是自反、对称且传递的。设 𝑣 ≈𝒜︀ 𝑣′,并对某个参数应用 𝑎 ∈ 𝒜︀。用一个字母给一个测试加前缀仍然是测试,因此正向映射所到达的值是不可区分的,任何一个被产出的逆操作从不可区分的参数所到达的值也同样不可区分;单字母的测试给出在二者处都有定义或都无定义,以及结果的相等性。
- 2. Let 𝑅 be such an equivalence and 𝑣𝑅𝑣′. Each letter of a test is a forward map or a yielded inverse of an operation, and respect carries 𝑅 along either, keeping the values reached related and the outcomes equal at every letter. Hence every test agrees at 𝑣 and 𝑣′. □ 23
设 𝑅 是这样一个等价关系且 𝑣𝑅𝑣′。一个测试的每个字母都是某个操作的正向映射或所产出的逆操作,而尊重性沿二者中的任意一个携带 𝑅,使所到达的值保持相关,且每个字母处的结果相等。因此每个测试在 𝑣 与 𝑣′ 处都一致。□
Clause (2) doubles as the proof principle for ≃𝑘: to relate two values, exhibit an equivalence the operations respect that contains the pair.
第 (2) 条同时充当 ≃𝑘 的证明原则:要使两个值相关,只需给出一个各操作都尊重且包含该对的等价关系。
Definition 33. Two coeffect contexts are related at a set 𝑆 ⊆ 𝐾 of keys when they bind the same keys of 𝑆 to related values, and two states of a context when their coeffect projections are:
定义 33. 两个余效应上下文在键集 𝑆 ⊆ 𝐾 上相关,是指它们把 𝑆 中相同的键绑定到相关的值;一个上下文的两个状态相关,是指它们的余效应投影相关:
𝜎 ≃𝑆 𝜎′ ≔ dom(𝜎) ∩ 𝑆 = dom(𝜎′) ∩ 𝑆 ∧ ∀𝑘 ∈ dom(𝜎) ∩ 𝑆. 𝜎(𝑘) ≃𝑘 𝜎′(𝑘) 𝛾 ≃𝑆 𝛾′ ≔ 𝜎𝛾 ≃𝑆 𝜎𝛾′ (34)
𝜎 ≃𝑆 𝜎′ ≔ dom(𝜎) ∩ 𝑆 = dom(𝜎′) ∩ 𝑆 ∧ ∀𝑘 ∈ dom(𝜎) ∩ 𝑆. 𝜎(𝑘) ≃𝑘 𝜎′(𝑘)
𝛾 ≃𝑆 𝛾′ ≔ 𝜎𝛾 ≃𝑆 𝜎𝛾′
(34)writing 𝜎𝛾 for the coeffect projection of 𝛾 (Definition 28). Each ≃𝑘 is an equivalence on 𝒱︀𝑘 by Lemma 32, so ≃𝑆 is an equivalence on coeffect contexts and on states, each of the three properties holding key by key. The subscript is dropped where 𝑆 = 𝐾, so that ≃ is the finest of these relations and ≃𝑆 forgets the keys outside 𝑆 as well.
其中 𝜎𝛾 表示 𝛾 的余效应投影(定义 28)。由引理 32,每个 ≃𝑘 都是 𝒱︀𝑘 上的等价关系,因此 ≃𝑆 是余效应上下文上以及状态上的等价关系,三条性质逐键成立。当 𝑆 = 𝐾 时省略下标,于是 ≃ 是这些关系中最细的一个,而 ≃𝑆 还遗忘 𝑆 之外的键。
The part of a state that no key binds is thereby forgotten, and forgetting it is what lets Theorem 7 be read up to ≃ at all: the heap layout and the generative name of the examples above lie outside the relation unless some key binds them. A restriction forgets more, comparing only what the keys of 𝑆 bind, and Section 4 reads each claim about one component at the restriction that component’s own declarations name (Definition 48). What Section 3.2.2 needs of ≃ follows rather than being assumed. Related states have the same domain, so they agree on the satisfaction predicate 𝜎 ⊧ 𝑑 and on the classification notify𝑑 of Definition 22, and reactivity is a property of Σ/ ≃.
一个状态中没有任何键绑定的那一部分就由此被遗忘,而正是这种遗忘,才使定理 7 能够被提升到 ≃ 之下解读:上述例子中的堆布局与生成式名字都落在这个关系之外,除非有某个键绑定了它们。一个限制遗忘得更多,只比较 𝑆 中键所绑定的东西,而第 4 节在组件自身声明所指名的那个限制上,解读关于该组件的每一项断言(定义 48)。第 3.2.2 节对 ≃ 的需要是推导出来的,而不是预先假设的。相关的状态具有相同的定义域,因此它们在满足谓词 𝜎 ⊧ 𝑑 上一致,在定义 22 的分类 notify𝑑 上也一致,而响应式是 Σ/ ≃ 的一个性质。
Substituting ≃ for = throughout is not by itself enough, because an effect function returns an inverse as well as a state, and two states that ≃ identifies have to yield inverses ≃ identifies as well.
处处用 ≃ 替换 = 本身还不够,因为一个效应函数除了状态之外还返回一个逆操作,而被 ≃ 视为同一的两个状态,也必须产出被 ≃ 视为同一的逆操作。
Definition 34. The relation of Definition 33 on states and each ≃𝑘 on the values of its key are the base cases, and on any other base type ≃𝑆 is equality. The relation extends along the type formers:
定义 34. 定义 33 中状态上的关系以及每个键的值上的 ≃𝑘 是基础情形,而在任何其他基础类型上 ≃𝑆 就是相等。该关系沿类型构造子扩展:
for 𝑓, 𝑔 : 𝑋 → 𝑌 , 𝑓 ≃𝑆 𝑔 ≔ (𝛾 : 𝑋) → (𝛾′ : 𝑋) → (𝛾 ≃𝑆 𝛾′ → 𝑓(𝛾) ≃𝑆 𝑔(𝛾′)) for 𝑎, 𝑏 : 𝑋1 × ⋯ × 𝑋𝑛, 𝑎 ≃𝑆 𝑏 ≔ (𝑎1 ≃𝑆 𝑏1) ∧ ⋯ ∧ (𝑎𝑛 ≃𝑆 𝑏𝑛) for 𝑥, 𝑦 : 𝖬𝖺𝗒𝖻𝖾(𝑋), 𝑥 ≃𝑆 𝑦 ≔ { 𝑧 ≃𝑆 𝑧′ if 𝑥 = 𝖩𝗎𝗌𝗍(𝑧) and 𝑦 = 𝖩𝗎𝗌𝗍(𝑧′) ⊤ if 𝑥 = 𝑦 = 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 ⊥ otherwise (35)
for 𝑓, 𝑔 : 𝑋 → 𝑌 , 𝑓 ≃𝑆 𝑔 ≔ (𝛾 : 𝑋) → (𝛾′ : 𝑋) → (𝛾 ≃𝑆 𝛾′ → 𝑓(𝛾) ≃𝑆 𝑔(𝛾′))
for 𝑎, 𝑏 : 𝑋1 × ⋯ × 𝑋𝑛, 𝑎 ≃𝑆 𝑏 ≔ (𝑎1 ≃𝑆 𝑏1) ∧ ⋯ ∧ (𝑎𝑛 ≃𝑆 𝑏𝑛)
for 𝑥, 𝑦 : 𝖬𝖺𝗒𝖻𝖾(𝑋), 𝑥 ≃𝑆 𝑦 ≔
𝑧 ≃𝑆 𝑧′ if 𝑥 = 𝖩𝗎𝗌𝗍(𝑧) and 𝑦 = 𝖩𝗎𝗌𝗍(𝑧′)
⊤ if 𝑥 = 𝑦 = 𝖭𝗈𝗍𝗁𝗂𝗇𝗀
⊥ otherwise
(35)On a recursive type the clauses are read coinductively, ≃𝑆 being the greatest relation satisfying its unfolding, as on ℑΓ. A map or an iterator respects ≃𝑆 when it is related to itself, written 𝑓 ≃𝑆 𝑓.
在递归类型上,这些子句按共归纳的方式解读,≃𝑆 是满足其展开式的最大关系,正如在 ℑΓ 上那样。一个映射或一个迭代器尊重 ≃𝑆,是指它与自身相关,记作 𝑓 ≃𝑆 𝑓。
Lemma 35. On maps and on iterators ≃𝑆 is a partial equivalence: it is symmetric and transitive, so two related members each respect it.
引理 35. 在映射上与在迭代器上,≃𝑆 都是一个部分等价关系:它是对称且传递的,因此两个相关的成员各自都尊重它。
Proof. On the base types ≃𝑆 is an equivalence, by Definition 33 and Lemma 32. For functions, symmetry is symmetry of the base relations applied at input and output, and transitivity reads the middle map at 𝛾′ ≃𝑆 𝛾′: from 𝑓 ≃𝑆 𝑔, 𝑔 ≃𝑆 ℎ, and 𝛾 ≃𝑆 𝛾′ follow 𝑓(𝛾) ≃𝑆 𝑔(𝛾′) and 𝑔(𝛾′) ≃𝑆 ℎ(𝛾′), whence 𝑓(𝛾) ≃𝑆 ℎ(𝛾′); products inherit both componentwise and 𝖬𝖺𝗒𝖻𝖾 by cases. For iterators, the inverse 𝑅−1 and the composite 𝑅 ∘ 𝑅 of the greatest relation 𝑅 satisfy the unfolding again, by the two arguments above read coinductively, so both are contained in 𝑅. Then 𝑓 ≃𝑆 𝑔 gives 𝑓 ≃𝑆 𝑓 by symmetry and transitivity, which is respect. □ 24
证明。在基础类型上,由定义 33 与引理 32,≃𝑆 是等价关系。对于函数,对称性是在输入端与输出端应用基础关系的对称性;传递性在 𝛾′ ≃𝑆 𝛾′ 处读取中间那个映射:由 𝑓 ≃𝑆 𝑔、𝑔 ≃𝑆 ℎ 以及 𝛾 ≃𝑆 𝛾′ 推出 𝑓(𝛾) ≃𝑆 𝑔(𝛾′) 与 𝑔(𝛾′) ≃𝑆 ℎ(𝛾′),从而 𝑓(𝛾) ≃𝑆 ℎ(𝛾′);积按分量继承这两条性质,𝖬𝖺𝗒𝖻𝖾 按情形继承。对于迭代器,最大关系 𝑅 的逆 𝑅−1 与复合 𝑅 ∘ 𝑅 按上述两个论证的共归纳读法再次满足展开式,因此二者都包含于 𝑅。于是由 𝑓 ≃𝑆 𝑔 通过对称性与传递性得到 𝑓 ≃𝑆 𝑓,这就是尊重性。□
A map respecting ≃𝑆 is one that descends to Γ/ ≃𝑆, and two maps related by ≃𝑆 are two that descend to the same map there, each respecting it by Lemma 35. Reflexivity is the one property the function former does not preserve: 𝑓 ≃𝑆 𝑓 demands related outputs at every pair of related inputs and not at the equal ones alone, so it holds of a map exactly where the map descends, which is why respect is a condition rather than a given. Respect at two key sets is two conditions of which neither implies the other, since ≃⊆≃𝑆 weakens the hypothesis and the conclusion together. A map that branches on a key outside 𝑆 is the case that separates them, respecting ≃ and failing to respect ≃𝑆.
一个尊重 ≃𝑆 的映射,就是一个下降到 Γ/ ≃𝑆 上的映射;而由 ≃𝑆 相关联的两个映射,就是下降到那里同一个映射的两个映射,由引理 35 可知二者都尊重它。自反性是函数构造子唯一不保持的性质:𝑓 ≃𝑆 𝑓 要求在每一对相关联的输入上都给出相关联的输出,而不只是在相等的输入上,因此它恰好对那些可以下降的映射成立——这正是尊重是一个条件而非既成事实的原因。在两个键集上尊重是两个互不蕴含的条件,因为 ≃⊆≃𝑆 把前提与结论一同削弱。一个在 𝑆 之外的键上做分支的映射,正是把二者区分开的情形:它尊重 ≃,却不尊重 ≃𝑆。
Definition 36. Define the effect function witnessed up to ≃𝑆 as:
定义 36. 定义见证到 ≃𝑆 的效应函数为:
𝔈𝑆 Γ ≔ (𝑒 : Γ → Γ × (Γ → Γ)) × (𝑒 ≃𝑆 𝑒) × ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → ((𝛿, 𝑔) = 𝑒(𝛾) → 𝑔(𝛿) ≃𝑆 𝛾)) (36)
𝔈𝑆
Γ ≔ (𝑒 : Γ → Γ × (Γ → Γ)) × (𝑒 ≃𝑆 𝑒)
× ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → ((𝛿, 𝑔) = 𝑒(𝛾) → 𝑔(𝛿) ≃𝑆 𝛾)) (36)The clause 𝑒 ≃𝑆 𝑒 carries every constituent’s respect, its instance at 𝛾 ≃𝑆 𝛾 relating each yielded inverse to itself. We write 𝔈∗ Γ for 𝔈𝐾 Γ , and taking ≃ to be equality on Γ recovers Definition 8, every map being equal to itself. The key set is where a component’s declarations enter: what Section 4 holds an effect function to is 𝔈𝑆 Γ at the keys that component names (Definition 48). The iterator of Section 3.1.3 is witnessed the same way, its continuation compared by Defin- ition 34 and witnessed again by the recursion:
子句 𝑒 ≃𝑆 𝑒 承载着每个组成部分的尊重性:它在 𝛾 ≃𝑆 𝛾 处的实例把每个产出的逆操作与它自身相关联。我们把 𝔈𝐾Γ 记作 𝔈∗Γ,而取 ≃ 为 Γ 上的相等关系则回到定义 8,因为每个映射都等于它自身。键集正是组件的声明进入之处:第 4 章要求一个效应函数所满足的,是该组件所指名的那些键上的 𝔈𝑆Γ(定义 48)。第 3.1.3 节的迭代器以同样的方式被见证,其延续按定义 34 加以比较,并由递归再次见证:
Definition 37. Define the effect iterator witnessed up to ≃𝑆 as:
定义 37. 定义见证到 ≃𝑆 的效应迭代器为:
ℑ𝑆 Γ ≔ 𝜇ℑ. (𝑒 : Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)) × (𝑒 ≃𝑆 𝑒) × ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → (𝑜 : 𝖬𝖺𝗒𝖻𝖾(ℑ)) → ((𝛿, 𝑔, 𝑜) = 𝑒(𝛾) → 𝑔(𝛿) ≃𝑆 𝛾)) (37)
ℑ𝑆
Γ ≔ 𝜇ℑ. (𝑒 : Γ → Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)) × (𝑒 ≃𝑆 𝑒)
× ((𝛾 : Γ) → (𝛿 : Γ) → (𝑔 : Γ → Γ) → (𝑜 : 𝖬𝖺𝗒𝖻𝖾(ℑ)) → ((𝛿, 𝑔, 𝑜) = 𝑒(𝛾) → 𝑔(𝛿) ≃𝑆 𝛾)) (37)The embedding of Section 3.1.3 carries 𝔈𝑆 Γ into ℑ𝑆 Γ, and taking ≃ to be equality on Γ and 𝑆 = 𝐾 recovers the witnessed ℑ∗ Γ of Definition 17.
第 3.1.3 节的嵌入把 𝔈𝑆Γ 送入 ℑ𝑆Γ,而取 ≃ 为 Γ 上的相等关系且 𝑆 = 𝐾,则回到定义 17 中带见证的 ℑ∗Γ。
Lemma 38. With 𝔈∗ Γ read as in Definition 36, every equality of states asserted in Section 3.1 holds with = replaced by ≃, and the accumulator of every state reachable from (𝛾0, idΓ) respects ≃. The same holds of any equivalence substituted for ≃, the proof using no property of the relation beyond transitivity and respect.
引理 38. 在按定义 36 理解 𝔈∗Γ 的前提下,第 3.1 节所断言的每个状态等式在把 = 替换为 ≃ 之后依然成立,并且从 (𝛾0, idΓ) 可达的每个状态的累加器都尊重 ≃。对代入 ≃ 的任意等价关系同样成立,其证明除传递性与尊重性之外没有用到该关系的任何性质。
Proof. An accumulator is a composition of inverses, each respecting ≃, the clause 𝑒 ≃ 𝑒 of Definition 36 read at 𝛾 ≃ 𝛾, and a composition of maps respecting ≃ respects ≃, the base case being idΓ. The proofs of Section 3.1 then go through unchanged, respect being what carries a relation through an inverse: from 𝑔2(𝛿2) ≃ 𝛿1 and 𝑔1(𝛿1) ≃ 𝛾 respect gives (𝑔1 ∘ 𝑔2)(𝛿2) ≃ 𝛾, which is the step each composition of inverses takes, and the soundness invariant of Theorem 7 reads 𝜑(𝛾) ≃ 𝛾0 by that step. □
证明. 累加器是逆操作的复合,其中每个逆操作都尊重 ≃——这是把定义 36 的子句 𝑒 ≃ 𝑒 读在 𝛾 ≃ 𝛾 处所得;而尊重 ≃ 的映射的复合仍尊重 ≃,其基线情形是 idΓ。于是第 3.1 节的证明原封不动地通过:尊重性正是把一个关系穿过逆操作携带过去的东西——由 𝑔2(𝛿2) ≃ 𝛿1 与 𝑔1(𝛿1) ≃ 𝛾,尊重性给出 (𝑔1 ∘ 𝑔2)(𝛿2) ≃ 𝛾,这正是逆操作的每一次复合所走的推理步,而定理 7 的可靠性不变量借这一步读作 𝜑(𝛾) ≃ 𝛾0。□
The stage shape of Definition 30 settles which readings of ≃ a member admits, and with them its membership in the witnessed iterators, which is what lets a claim about one component be read at the keys that component names.
定义 30 的阶段形态决定了一个成员承认 ≃ 的哪些读法,从而也决定了它在这些带见证迭代器中的成员资格;正是这一点使得关于某个组件的一个论断,能够在该组件所指名的那些键上被读出。
Lemma 39. Let 𝑖 ∈ ℑ𝒜︀ Σ(𝑆′, 𝑃) and let 𝑆 ⊆ 𝐾 contain every key at which a stage of 𝑖 occurs. Then 𝑖 lies in ℑ𝑆 Σ (Definition 37); in particular 𝑖 and every inverse it yields respect ≃𝑆, and the witnesses hold at equality. 25
引理 39. 设 𝑖 ∈ ℑ𝒜︀Σ(𝑆′, 𝑃),且 𝑆 ⊆ 𝐾 包含 𝑖 的各阶段出现于其上的每个键。那么 𝑖 落在 ℑ𝑆Σ 中(定义 37);特别地,𝑖 以及它产出的每个逆操作都尊重 ≃𝑆,而各见证在相等关系下成立。
Proof. By induction on the construction of Definition 30. The unit yields its argument, idΣ, and 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 everywhere. At an operation stage performing 𝑎 ∈ 𝒜︀𝑘, let 𝜎 ≃𝑆 𝜎′; then 𝑘 ∈ 𝑆, so 𝜎(𝑘) ≃𝑘 𝜎′(𝑘). 𝑎 respects ≃𝑘 (Lemma 32), so it is defined at both or at neither and yields ≃𝑘- related values, equal outcomes, and inverses carrying ≃𝑘-related values to ≃𝑘-related values, and its lift reads and writes 𝑘 alone; the states reached are therefore ≃𝑆-related, the inverses are ≃𝑆-related and respect ≃𝑆, and the equal outcomes select one continuation, to which the induction hypothesis applies. At a provision stage at 𝑘 ∈ 𝑃 ⊆ 𝑆, the precondition 𝑘 ∉ dom(𝜎) holds at both or at neither, the states reached bind 𝑘 to the one value the stage carries, and the restriction it yields respects ≃𝑆, two related states binding 𝑘 alike. The witness of each stage holds at equality, the lift of an operation’s inverse restoring the value Definition 29 witnesses it to and the restriction reverting the extension (Definition 20), and equality gives ≃𝑆. □
证明. 对定义 30 的构造作归纳。单位元处处产出它的参量、idΣ 与 𝖭𝗈𝗍𝗁𝗂𝗇𝗀。在执行 𝑎 ∈ 𝒜︀𝑘 的操作阶段处,设 𝜎 ≃𝑆 𝜎′;那么 𝑘 ∈ 𝑆,故 𝜎(𝑘) ≃𝑘 𝜎′(𝑘)。由引理 32,𝑎 尊重 ≃𝑘,于是它在两个状态上或者都有定义,或者都无定义,并且产出 ≃𝑘-相关的值、相等的结果,以及把 ≃𝑘-相关的值映到 ≃𝑘-相关的值的逆操作;而且它的提升只读写 𝑘。因此所到达的状态是 ≃𝑆-相关的,逆操作是 ≃𝑆-相关的并且尊重 ≃𝑆,而相等的结果选定同一个延续,归纳假设适用于该延续。在 𝑘 ∈ 𝑃 ⊆ 𝑆 处的提供阶段,前提 𝑘 ∉ dom(𝜎) 在两状态上同真或同假,所到达的状态把 𝑘 绑定到该阶段所携带的那唯一一个值,而它产出的限制(restriction)尊重 ≃𝑆——因为两个相关的状态以同样的方式绑定 𝑘。每个阶段的见证都在相等关系下成立:操作之逆操作的提升恢复了定义 29 所见证的那个值,而限制逆转了那次扩张(定义 20);又由相等即得 ≃𝑆。□
A stage reads the key it performs at and nothing else, so the hypothesis is met by taking 𝑆 to be the keys the member names.
一个阶段只读取它所执行于其上的那个键,此外别无他物,因此把 𝑆 取为该成员所指名的那些键即可满足假设。
3.4. 达成独立性
3.4. Attaining Independence
On the strength of Section 3.3, this section supplies the condition that extends the local guarantees to a system of interleaved components. Section 3.4.1 defines the condition, namely independence of two effect functions: every transformation of one commutes with every transformation of the other, forward maps and yielded inverses alike. Section 3.4.2 then reduces independence of context-mediated effect functions to commutativity at single coeffects and refines the coeffect with a witness of that commutativity, parallel to the witness of an effect function.
凭借第 3.3 节的结论,本节给出把局部保证推广到由相互交错的组件构成的系统的那个条件。第 3.4.1 节定义该条件,即两个效应函数的独立性:其中一个的每一次变换都与另一个的每一次变换可交换,前向映射与所产出的逆操作皆然。第 3.4.2 节接着把上下文中介的效应函数的独立性归约为单个余效应上的可交换性,并以该可交换性的见证来细化余效应,这与效应函数的见证是平行的。
3.4.1. 效应独立性
3.4.1. Effect Independence
Reverting an effect at the state its own application produced is what Theorem 16 covers; reverting one at any other state is what this subsection covers. Two situations call for the latter. An inverse may be run while later effects are still in place, which is what removing one component from a running system amounts to; and one sequence may interleave the effects of several components, each holding the inverses of its own, so that the inverses of one component are separated by the applications of another. In both an inverse meets a state that foreign effects have moved, and whether it still reverts what it was built to revert is a question of commutation: what has to commute is every transformation one effect can perform with every transformation the other can perform, forward map and yielded inverse alike. A single accumulator settles neither situation, 𝜑 being a composite that runs every inverse it holds in one order and all at once.
在某个效应自身的应用所产生的状态上撤销它,是定理 16 所覆盖的情形;在任一其他状态上撤销它,则是本小节所覆盖的情形。有两种情形需要后者。其一,一个逆操作可能在后来的效应仍然在位时被执行——这正是从一个正在运行的系统中移除某个组件所意味的事情;其二,一个序列可能把若干组件的效应交错起来,每个组件各自持有它自己的逆操作,于是某个组件的诸逆操作被另一个组件的诸次应用所分隔。在这两种情形中,一个逆操作遇到的状态都已被外来的效应移动过;它是否仍能撤销它被构造出来要撤销的东西,就成为一个交换性问题:必须可交换的,是一个效应所能执行的每一次变换与另一个效应所能执行的每一次变换,前向映射与所产出的逆操作皆然。单个累加器对这两种情形都无能为力,因为 𝜑 是一个复合物,它按某一种顺序一次性地运行它所持有的每个逆操作。
An iterator gives rise to several maps: the forward map of each iteration it can reach, and the inverse each yields at each context state. Commutation of two iterators relates the two collections rather than two single maps, and those collections are what the definitions below quantify over.
一个迭代器引出若干个映射:它能够到达的每一次迭代的前向映射,以及它在每个上下文状态所产出的逆操作。两个迭代器的可交换性关联的是这两个集合,而不是两个单独的映射;下面的定义所量化遍历的正是这些集合。
Definition 40. For an iterator 𝑖 ∈ ℑΓ, let reach(𝑖) be the least set of iterators containing 𝑖 and closed under continuation. The transformation monoid 𝔐(𝑖) is the submonoid of Γ → Γ generated by the forward maps and the yielded inverses of every iterator in reach(𝑖), and the generators of 𝔐(𝑖) are the elements of that generating set: 26
定义 40. 对迭代器 𝑖 ∈ ℑΓ,令 reach(𝑖) 为包含 𝑖 且对延续封闭的最小迭代器集合。变换幺半群 𝔐(𝑖) 是由 reach(𝑖) 中每个迭代器的前向映射与所产出的逆操作所生成的 Γ → Γ 的子幺半群,而 𝔐(𝑖) 的生成元就是该生成集的各个元素:
reach(𝑖) ≔ ⋂{𝑆 | 𝑖 ∈ 𝑆 ∧ ∀𝑖′ ∈ 𝑆, 𝛾 ∈ Γ. 𝑖′(𝛾) = (−, −, 𝖩𝗎𝗌𝗍(𝑖″)) ⇒ 𝑖″ ∈ 𝑆} 𝔐(𝑖) ≔ ⟨{pr1 ∘ 𝑖′ | 𝑖′ ∈ reach(𝑖)} ∪ {pr2(𝑖′(𝛾)) | 𝑖′ ∈ reach(𝑖), 𝛾 ∈ Γ}⟩ (38)
reach(𝑖) ≔ ⋂{𝑆 | 𝑖 ∈ 𝑆 ∧ ∀𝑖′ ∈ 𝑆, 𝛾 ∈ Γ. 𝑖′(𝛾) = (−, −, 𝖩𝗎𝗌𝗍(𝑖″)) ⇒ 𝑖″ ∈ 𝑆}
𝔐(𝑖) ≔ ⟨{pr1 ∘ 𝑖′ | 𝑖′ ∈ reach(𝑖)} ∪ {pr2(𝑖′(𝛾)) | 𝑖′ ∈ reach(𝑖), 𝛾 ∈ Γ}⟩
(38)Write len(𝑖) for the supremum of |𝐶| over the chains 𝐶 ⊆ reach(𝑖) that continuation orders. Through the embedding of Section 3.1.3, 𝔐(𝑒) at an effect function 𝑒 is generated by the forward map of 𝑒 together with every inverse 𝑒 yields; an effect induced by a pair (𝑓, 𝑔) ∈ 𝔗Γ has 𝑓 and 𝑔 for its generators, the inverse it yields being 𝑔 at every state.
记 len(𝑖) 为被延续所排序的那些链 𝐶 ⊆ reach(𝑖) 上 |𝐶| 的上确界。通过第 3.1.3 节的嵌入,效应函数 𝑒 处的 𝔐(𝑒) 由 𝑒 的前向映射连同 𝑒 所产出的每个逆操作生成;由二元组 (𝑓, 𝑔) ∈ 𝔗Γ 诱导的效应以 𝑓 与 𝑔 为其生成元,它在每个状态产出的逆操作都是 𝑔。
Lemma 41. Commutation is settled on the generators, and ⋄ enlarges no transformation monoid. That is,
引理 41. 可交换性可在生成元上判定,且 ⋄ 不扩大任何变换幺半群。亦即,
- 1. if every generator of 𝔐(𝑒1) commutes with every generator of 𝔐(𝑒2), then every element of 𝔐(𝑒1) commutes with every element of 𝔐(𝑒2);
若 𝔐(𝑒1) 的每个生成元都与 𝔐(𝑒2) 的每个生成元可交换,则 𝔐(𝑒1) 的每个元素都与 𝔐(𝑒2) 的每个元素可交换;
- 2. 𝔐(𝑒1 ⋄ 𝑒2) ⊆ ⟨𝔐(𝑒1) ∪ 𝔐(𝑒2)⟩.
𝔐(𝑒1 ⋄ 𝑒2) ⊆ ⟨𝔐(𝑒1) ∪ 𝔐(𝑒2)⟩。
Proof.
证明.
- 1. The maps commuting with every generator of 𝔐(𝑒2) form a submonoid of Γ → Γ, since idΓ lies in it and 𝑓 ∘ 𝑓′ does where 𝑓 and 𝑓′ do. That submonoid contains the generators of 𝔐(𝑒1) by hypothesis and hence contains 𝔐(𝑒1). Fixing 𝑓 ∈ 𝔐(𝑒1), the maps commuting with 𝑓 likewise form a submonoid containing the generators of 𝔐(𝑒2) and hence 𝔐(𝑒2).
与 𝔐(𝑒2) 的每个生成元都可交换的那些映射构成 Γ → Γ 的一个子幺半群:idΓ 在其中,并且当 𝑓 与 𝑓′ 在其中时 𝑓 ∘ 𝑓′ 也在其中。按假设,这个子幺半群包含 𝔐(𝑒1) 的生成元,因而包含 𝔐(𝑒1)。再固定 𝑓 ∈ 𝔐(𝑒1),与 𝑓 可交换的那些映射同样构成一个子幺半群,它包含 𝔐(𝑒2) 的生成元,因而包含 𝔐(𝑒2)。
- 2. By Definition 9 the forward map of 𝑒1 ⋄ 𝑒2 is (pr1 ∘ 𝑒1) ∘ (pr1 ∘ 𝑒2) and the inverse it yields at any state is 𝑠 ∘ 𝑡 for an 𝑠 yielded by 𝑒2 and a 𝑡 yielded by 𝑒1. Every generator of 𝔐(𝑒1 ⋄ 𝑒2) is therefore a composite of generators of the two. □
由定义 9,𝑒1 ⋄ 𝑒2 的前向映射是 (pr1 ∘ 𝑒1) ∘ (pr1 ∘ 𝑒2),而它在任一状态所产出的逆操作是 𝑠 ∘ 𝑡,其中 𝑠 由 𝑒2 产出、𝑡 由 𝑒1 产出。因此 𝔐(𝑒1 ⋄ 𝑒2) 的每个生成元都是二者生成元的一个复合。□
Definition 42. Iterators 𝑖, 𝑗 ∈ ℑΓ are independent when
定义 42. 称迭代器 𝑖, 𝑗 ∈ ℑΓ 是独立的,当
- 1. every transformation of one commutes with every transformation of the other,
一方的每次变换都与另一方的每次变换可交换,
∀𝑓 ∈ 𝔐(𝑖), 𝑔 ∈ 𝔐(𝑗). 𝑓 ∘ 𝑔 = 𝑔 ∘ 𝑓 (39)
∀𝑓 ∈ 𝔐(𝑖), 𝑔 ∈ 𝔐(𝑗). 𝑓 ∘ 𝑔 = 𝑔 ∘ 𝑓 (39)- 2. neither one’s transformations disturb what the other yields, inverse and continuation alike,
任一方的变换都不干扰另一方所产出的东西,逆操作与延续皆然,
∀𝑖′ ∈ reach(𝑖), 𝑔 ∈ 𝔐(𝑗), 𝛾 ∈ Γ. pr2,3(𝑖′(𝑔(𝛾))) = pr2,3(𝑖′(𝛾)) (40)
∀𝑖′ ∈ reach(𝑖), 𝑔 ∈ 𝔐(𝑗), 𝛾 ∈ Γ. pr2,3(𝑖′(𝑔(𝛾))) = pr2,3(𝑖′(𝛾)) (40)and the same with 𝑖 and 𝑗 exchanged. A family (𝑖𝑙)𝑙∈𝐿 is pairwise independent when 𝑖𝑙 and 𝑖𝑙′ are independent for every 𝑙 ≠ 𝑙′. A family may repeat an iterator, and holding one independent of itself is holding 𝔐(𝑖) commutative.
并且把 𝑖 与 𝑗 互换之后同样成立。称族 (𝑖𝑙)𝑙∈𝐿 是两两独立的,当对任意 𝑙 ≠ 𝑙′,𝑖𝑙 与 𝑖𝑙′ 都独立。一个族可以重复出现同一个迭代器,而要求一个迭代器与它自身独立,就是要求 𝔐(𝑖) 可交换。
Read at effect functions through the embedding of Section 3.1.3, clause (2) compares the inverse alone, the continuation being 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 at every state, and the result below is given at effect functions; Section 4 applies the definition at the iterators themselves. For effects induced by pairs (𝑓1, 𝑔1) and (𝑓2, 𝑔2), clause (1) is by Lemma 41(1) the commutation of the four pairs 𝑓1, 𝑓2; 𝑔1, 𝑔2; 𝑓1, 𝑔2; and 𝑔1, 𝑓2, and clause (2) holds outright, an induced effect yielding one inverse at every state. Commutation under ⋄ is a different property. What 𝑒1 ⋄ 𝑒2 = 𝑒2 ⋄ 𝑒1 equates is the composite forward map of the two orders with each other and the composite inverse of the two orders with each other, each inverse entering the composite at the state its own application produced; independence instead relates each transformation of one effect to each transformation of the other, a forward map paired with a foreign inverse included.
通过第 3.1.3 节的嵌入在效应函数处读出时,子句 (2) 只比较逆操作,因为延续在每个状态都是 𝖭𝗈𝗍𝗁𝗂𝗇𝗀;下面的结果是在效应函数上给出的,而第 4 章则把该定义用在迭代器自身上。对于由 (𝑓1, 𝑔1) 与 (𝑓2, 𝑔2) 诱导的效应,由引理 41(1),子句 (1) 就是四对映射 𝑓1, 𝑓2;𝑔1, 𝑔2;𝑓1, 𝑔2;以及 𝑔1, 𝑓2 的可交换性,而子句 (2) 无条件成立,因为一个诱导效应在每个状态都产出同一个逆操作。⋄ 之下的交换性是另一种性质。𝑒1 ⋄ 𝑒2 = 𝑒2 ⋄ 𝑒1 所等同的,是两种顺序的复合前向映射彼此相等、以及两种顺序的复合逆操作彼此相等,其中每个逆操作都在它自己的应用所产生的那个状态处进入复合;而独立性则是把一个效应的每次变换与另一个效应的每次变换关联起来,其中也包括一个前向映射与一个外来逆操作相配对。
Under independence an inverse may be run at a state later effects have moved, and it with- draws there its own contribution and nothing else, whatever order the inverses are applied in: 27
在独立性之下,一个逆操作可以在后来的效应已经移动过的状态上运行,并且无论诸逆操作以何种顺序被应用,它在那里撤回的都是它自己的贡献,此外无他:
Theorem 43. Let 𝑒1, ⋯, 𝑒𝑛 ∈ 𝔈∗ Γ be pairwise independent and applied in order from 𝛾0, and let each 𝑔𝑖 be the inverse 𝑒𝑖 yields where it is applied. Applying the 𝑛 inverses at the state the sequence reaches, in the order of any permutation of {1, ⋯, 𝑛}, reaches 𝛾0.
定理 43. 设 𝑒1, ⋯, 𝑒𝑛 ∈ 𝔈∗Γ 两两独立,且从 𝛾0 起依次被应用,并设每个 𝑔𝑖 是 𝑒𝑖 在其被应用处所产出的逆操作。在该序列所到达的状态上应用这 𝑛 个逆操作,顺序取 {1, ⋯, 𝑛} 的任一排列,都到达 𝛾0。
Proof. Write 𝑓𝑖 ≔ pr1 ∘ 𝑒𝑖, let 𝛿𝑖 ≔ 𝑓𝑖(𝛿𝑖−1) with 𝛿0 ≔ 𝛾0, so that 𝑔𝑖 = pr2(𝑒𝑖(𝛿𝑖−1)). Fix 𝑗 and write 𝛿′ 𝑖 ≔ (𝑓𝑖 ∘ ⋯ ∘ 𝑓𝑗+1)(𝛿𝑗−1) for the states of the sequence with 𝑒𝑗 omitted, so that 𝛿′ 𝑗 = 𝛿𝑗−1. Two claims hold for every 𝑢 with 𝑗 ≤ 𝑢 ≤ 𝑛.
证明. 记 𝑓𝑖 ≔ pr1 ∘ 𝑒𝑖,令 𝛿𝑖 ≔ 𝑓𝑖(𝛿𝑖−1) 且 𝛿0 ≔ 𝛾0,于是 𝑔𝑖 = pr2(𝑒𝑖(𝛿𝑖−1))。固定 𝑗,并记 𝛿′𝑖 ≔ (𝑓𝑖 ∘ ⋯ ∘ 𝑓𝑗+1)(𝛿𝑗−1) 为略去 𝑒𝑗 之后该序列的各状态,于是 𝛿′𝑗 = 𝛿𝑗−1。对每个满足 𝑗 ≤ 𝑢 ≤ 𝑛 的 𝑢,以下两个断言成立。
(1) 𝛿𝑢 = 𝑓𝑗(𝛿′ 𝑢) and 𝑔𝑗(𝛿𝑢) = 𝛿′ 𝑢. The first equation is an induction on 𝑢: at 𝑢 = 𝑗 it reads 𝛿𝑗 = 𝑓𝑗(𝛿𝑗−1), which is the definition of 𝛿𝑗, and for the inductive step, 𝛿𝑢+1 = 𝑓𝑢+1(𝛿𝑢) = 𝑓𝑢+1(𝑓𝑗(𝛿′ 𝑢)) = 𝑓𝑗(𝑓𝑢+1(𝛿′ 𝑢)) = 𝑓𝑗(𝛿′ 𝑢+1), the middle equality being clause (1) of Definition 42 for 𝑒𝑢+1 and 𝑒𝑗, which are distinct effects of the family since 𝑢 + 1 > 𝑗. For the second equation, clause (1) carries 𝑔𝑗 out through the forward maps applied after 𝑒𝑗, leaving the witness of 𝑒𝑗 to be used at the one state it holds at:
(1) 𝛿𝑢 = 𝑓𝑗(𝛿′𝑢) 且 𝑔𝑗(𝛿𝑢) = 𝛿′𝑢。第一个等式是对 𝑢 的归纳:在 𝑢 = 𝑗 处它读作 𝛿𝑗 = 𝑓𝑗(𝛿𝑗−1),这正是 𝛿𝑗 的定义;而归纳步为 𝛿𝑢+1 = 𝑓𝑢+1(𝛿𝑢) = 𝑓𝑢+1(𝑓𝑗(𝛿′𝑢)) = 𝑓𝑗(𝑓𝑢+1(𝛿′𝑢)) = 𝑓𝑗(𝛿′𝑢+1),其中间的那个等号是定义 42 的子句 (1) 用于 𝑒𝑢+1 与 𝑒𝑗,由于 𝑢 + 1 > 𝑗,它们是该族中两个不同的效应。至于第二个等式,子句 (1) 把 𝑔𝑗 带过 𝑒𝑗 之后所应用的那些前向映射,只留下 𝑒𝑗 的见证在它所持有的那唯一一个状态处被使用:
𝑔𝑗(𝛿𝑢) = (𝑔𝑗 ∘ 𝑓𝑢 ∘ ⋯ ∘ 𝑓𝑗+1)(𝛿𝑗) = (𝑓𝑢 ∘ ⋯ ∘ 𝑓𝑗+1)(𝑔𝑗(𝑓𝑗(𝛿𝑗−1))) = 𝛿′ 𝑢
𝑔𝑗(𝛿𝑢) = (𝑔𝑗 ∘ 𝑓𝑢 ∘ ⋯ ∘ 𝑓𝑗+1)(𝛿𝑗) = (𝑓𝑢 ∘ ⋯ ∘ 𝑓𝑗+1)(𝑔𝑗(𝑓𝑗(𝛿𝑗−1))) = 𝛿′𝑢the last equality resting on 𝑔𝑗(𝑓𝑗(𝛿𝑗−1)) = 𝛿𝑗−1, which is the witness Definition 8 requires of 𝑒𝑗 at 𝛿𝑗−1.
最后一个等式依赖 𝑔𝑗(𝑓𝑗(𝛿𝑗−1)) = 𝛿𝑗−1,这是定义 8 对 𝑒𝑗 在 𝛿𝑗−1 处所要求的见证。
(2) Each 𝑒𝑖 with 𝑖 > 𝑗 yields at 𝛿′ 𝑖−1 the same inverse 𝑔𝑖 it yields at 𝛿𝑖−1: by (1) the state 𝛿𝑖−1 is 𝑓𝑗(𝛿′ 𝑖−1), and 𝑓𝑗 ∈ 𝔐(𝑒𝑗), so clause (2) of Definition 42 for 𝑒𝑖 and 𝑒𝑗 gives pr2(𝑒𝑖(𝑓𝑗(𝛿′ 𝑖−1))) = pr2(𝑒𝑖(𝛿′ 𝑖−1)).
(2) 每个 𝑖 > 𝑗 的 𝑒𝑖 在 𝛿′𝑖−1 处产出的,与它在 𝛿𝑖−1 处所产出的同为逆操作 𝑔𝑖:由 (1),状态 𝛿𝑖−1 就是 𝑓𝑗(𝛿′𝑖−1),而 𝑓𝑗 ∈ 𝔐(𝑒𝑗),因此对 𝑒𝑖 与 𝑒𝑗 应用定义 42 的子句 (2) 给出 pr2(𝑒𝑖(𝑓𝑗(𝛿′𝑖−1))) = pr2(𝑒𝑖(𝛿′𝑖−1))。
The theorem follows by downward induction on 𝑛. Let the permutation begin with 𝑗. By (1) applying 𝑔𝑗 at 𝛿𝑛 reaches 𝛿′ 𝑛, the state the sequence with 𝑒𝑗 omitted reaches, and by (2) the inverses the remaining effects yielded there are the 𝑔𝑖 in hand. That sequence is pairwise independent, being a subfamily, so the induction hypothesis applies to it and to the rest of the permutation; the empty sequence reaches 𝛾0. □
定理通过对 𝑛 的向下归纳得出。设该排列以 𝑗 起首。由 (1),在 𝛿𝑛 处应用 𝑔𝑗 到达 𝛿′𝑛,即略去 𝑒𝑗 之后该序列所到达的状态;由 (2),其余效应在那里所产出的逆操作正是手上已有的那些 𝑔𝑖。该序列作为一个子族仍是两两独立的,因此归纳假设适用于它以及该排列的剩余部分;空序列到达 𝛾0。□
Section 4.3.2 carries this conclusion to a trace of a whole system, where the steps of other fibers intervene between an effect and its revert.
第 4.3.2 节把这一结论搬到整个系统的一条迹上,在那里,某个效应与它的撤销之间插入了其他纤程的步骤。(译注:本文的 fiber 指组件在注册表中的实例化实体/条目,与操作系统中"纤程"的轻量级线程含义不同;为保持术语一致,仍译作"纤程"。)
3.4.2. 余效应可交换性
3.4.2. Coeffect Commutativity
The commutation Definition 42 requires is read up to ≃ by Lemma 38, a yielded continuation compared as Definition 34 compares two iterators, and reading it that way is what makes it attainable at all: two operations may leave values that ≃𝑘 identifies and still count as commut- ing. Of two operations it requires one thing more than of the effect functions their lifts induce, an operation yielding an outcome as well.
由引理 38,定义 42 所要求的可交换性是在 ≃ 的意义下读出的,其中产出的延续按定义 34 比较两个迭代器的方式来比较。正是这样读法使它终究可达:两个操作可以留下被 ≃𝑘 视为同一的值,却仍然算作可交换。对于两个操作,它比其提升所诱导的效应函数多要求一样东西,因为一个操作还产出结果。
Definition 44. Operations 𝑎 and 𝑎′ are independent when their lifts are independent as effect functions (Definition 42) at every pair of arguments, and neither one’s transformations disturb the outcome the other yields:
定义 44. 称操作 𝑎 与 𝑎′ 是独立的,当它们的提升在每一对参量上都作为效应函数独立(定义 42),且任一方的变换都不干扰另一方所产出的结果:
∀𝑥 : 𝑋𝑎, 𝑔 ∈ 𝔐(𝑎′Σ), 𝜎 ∈ Σ. pr3(𝑎Σ(𝑥)(𝑔(𝜎))) = pr3(𝑎Σ(𝑥)(𝜎)) (41)
∀𝑥 : 𝑋𝑎, 𝑔 ∈ 𝔐(𝑎′Σ), 𝜎 ∈ Σ. pr3(𝑎Σ(𝑥)(𝑔(𝜎))) = pr3(𝑎Σ(𝑥)(𝜎)) (41)and the same with 𝑎 and 𝑎′ exchanged, writing 𝔐(𝑎Σ) for the submonoid generated by the forward maps and yielded inverses of the lifts 𝑎Σ(𝑥) over every argument, as Definition 40 generates 𝔐. A key 𝑘 is commutative when any two operations of 𝒜︀𝑘 are independent, an operation being held independent of itself as well. 28
并且把 𝑎 与 𝑎′ 互换后同样成立;其中 𝔐(𝑎Σ) 记由提升 𝑎Σ(𝑥) 在所有参量上的前向映射与所产出的逆操作所生成的子幺半群,正如定义 40 生成 𝔐 那样。称一个键 𝑘 是可交换的,当 𝒜︀𝑘 的任意两个操作都独立,这里也把一个操作与它自身独立的要求包括在内。
Across distinct keys the condition holds outright.
跨越不同的键,该条件无条件成立。
Theorem 45. Operations at distinct keys are independent.
定理 45. 位于不同键上的操作是独立的。
Proof. Let 𝑎 lie in 𝒜︀𝑘 and 𝑎′ in 𝒜︀𝑘′ with 𝑘 ≠ 𝑘′. By Definition 29 every generator of 𝔐(𝑎Σ) is of the form 𝜎 ↦ 𝜎[𝑘 ↦ 𝑢(𝜎(𝑘))] for a map 𝑢 on 𝒱︀𝑘, being either the lift of a forward map or the lift of a yielded inverse, and likewise for 𝑎′ at 𝑘′. Two such maps commute, each reading and writing one key alone and the two keys differing, and Lemma 41(1) extends the commutation from the generators to the two monoids. For the second condition, what 𝑎Σ yields at 𝜎, inverse and outcome alike, is determined by 𝜎(𝑘), which every generator of 𝔐(𝑎′Σ) leaves as it stands.□
证明. 设 𝑎 落在 𝒜︀𝑘 中,𝑎′ 落在 𝒜︀𝑘′ 中,且 𝑘 ≠ 𝑘′。由定义 29,𝔐(𝑎Σ) 的每个生成元都具有 𝜎 ↦ 𝜎[𝑘 ↦ 𝑢(𝜎(𝑘))] 的形式,其中 𝑢 是 𝒱︀𝑘 上的一个映射,它或者是某个前向映射的提升,或者是某个所产出逆操作的提升;𝑎′ 在 𝑘′ 处同理。这样的两个映射可交换:各自只读写一个键,而这两个键不同;再由引理 41(1) 把可交换性从生成元扩张到这两个幺半群。至于第二个条件,𝑎Σ 在 𝜎 处所产出的东西,逆操作与结果皆然,都由 𝜎(𝑘) 所决定,而 𝔐(𝑎′Σ) 的每个生成元都让 𝜎(𝑘) 保持原样。□
The condition therefore turns on the pairs at one key, and the proof of their independence is made a constituent of the coeffect itself, as the proof that an inverse reverts is a constituent of the effect function (Definition 8):
因此,该条件归根到底取决于同一个键上的各个配对;而它们独立性的证明被做成余效应自身的一个组成部分,正如逆操作能够撤销的证明是效应函数的一个组成部分那样(定义 8):
Definition 46. A coeffect at 𝑘 (Definition 29) is witnessed when it carries, as a third constituent beside 𝒱︀𝑘 and 𝒜︀𝑘, a proof that 𝑘 is commutative (Definition 44).
定义 46. 称 𝑘 处的一个余效应(定义 29)是被见证的,当它除 𝒱︀𝑘 与 𝒜︀𝑘 之外还携带第三个组成部分,即一个 𝑘 可交换的证明(定义 44)。
The two witnesses are parallel: each certifies the condition its consumers would otherwise have to assume, the returned inverse reverting there and the operations commuting here, and each is supplied where the definition is written rather than checked where it is used. By Theorem 45 the proof concerns the operations of 𝑘 alone, so the obligation falls on the compo- nent providing the key and on no component consuming it; the examples below are how it is discharged. From here on every coeffect is witnessed, and Section 4 reads every key of 𝐾 so.
这两个见证是平行的:它们各自为其使用者本来不得不假设的那个条件提供凭据——那里是返回的逆操作能够撤销,这里是诸操作彼此可交换;并且它们都是在定义被书写之处提供的,而不是在被使用之处被检验的。由定理 45,该证明只关乎 𝑘 自身的操作,因此这项义务落在提供该键的组件身上,而不落在任何消费它的组件身上;下面的例子就是它被履行的方式。由此往后,每个余效应都是被见证的,第 4 章对 𝐾 的每个键都如此理解。
A key whose value is a table of entries is commutative when each registration takes an entry of its own, registration of a route or of an event listener being the representative case. The operation draws an identifier for the entry it adds and the inverse it yields removes that entry, so two registrations name two entries whatever they register: either order leaves a table that answers every test alike, and either registration can be withdrawn while the other stands. Replicated data types are designed to this condition and attach a unique tag to each addition for this very reason, a set whose additions and removals name a bare element having no such property [43]. A key whose value is an ordered chain is not commutative, since a middleware inserted before another sees a different request, and neither order can be withdrawn without disturbing the other.
一个以条目表为值的键,当每次注册各取一个属于自己的条目时是可交换的,注册一条路由或一个事件监听器是最具代表性的情形。该操作为它添加的条目抽取一个标识符,而它产出的逆操作移除那个条目,因此无论它们注册的是什么,两次注册指名的都是两个条目:任一顺序留下的表对每个测试的应答都一样,并且任一注册都可以在另一个仍然在位时被撤回。复制数据类型正是照着这一条件被设计出来的,并为此给每次添加附上一个唯一的标签;相反,一个其添加与移除只指名一个裸元素的集合,并不具备这样的性质 [43]。一个以有序链为值的键则不可交换,因为插在另一个之前的中间件看到的是不同的请求,并且任一顺序在撤回时都会扰动另一个。
The allocator of the opening example divides by what its interface publishes. Where the handles it hands out are compared by no operation of the key, no test observes them, so ≃𝑘 relates two heaps up to a renaming of handles and allocation is commutative; a renaming is an equivalence the operations respect, contained in ≃𝑘 by Lemma 32(2), and it is how CompCert relates the memory states of a program and of its translation [44]. Where the addresses are outcomes compared by equality, the outcome of a further allocation separates the two orders of allocation, and the key is not commutative. POSIX draws the same line across its own allocators: mmap may return any unused address and creat may assign any unused inode, whereas open is required to return the lowest available descriptor, and that requirement alone is what stops two descriptor allocations from commuting [45].
开篇例子中的那个分配器如何划分,取决于它的接口公布了什么。在该键的任何操作都不比较它所分发的句柄的地方,没有测试观察到它们,于是 ≃𝑘 便在两个堆之间模句柄的重命名而相关联,分配因而可交换;一个重命名是诸操作所尊重的一个等价关系,由引理 32(2) 它被包含在 ≃𝑘 之中,这也正是 CompCert 把一个程序的内存状态与其编译产物的内存状态关联起来的方式 [44]。在地址是按相等来比较的结果的地方,再一次分配所得的结果就把两种分配顺序区分开来,该键不可交换。POSIX 在它自己的各个分配器之间划出了同一条界线:mmap 可以返回任意未使用的地址,creat 可以指派任意未使用的 inode,而 open 则被要求返回最小的可用描述符,仅仅这一项要求就阻止了两次描述符分配相互可交换 [45]。
Definition 31 turns each of these divisions into a design choice. ≃𝑘 is indistinguishability under the tests the operations of 𝑘 generate, so an interface publishing fewer outcomes admits fewer tests and coarsens the relation, and withholding an outcome its callers do not need can carry a key from one side of a division to the other. The scalable commutativity rule applies 29 the same move across the POSIX interface, and reads commutativity as indistinguishability through an interface rather than equality of internal states [45].
定义 31 把上述每一处划分都变成一个设计选择。≃𝑘 就是在 𝑘 的操作所生成的那些测试之下的不可分辨性,因此一个公布更少结果的接口所允许的测试也就更少,并把该关系变得更粗;而扣下一个它的调用者并不需要的结果,就可能把一个键从某条分界的一侧带到另一侧。可伸缩可交换性规则把同样的手法施加在 POSIX 接口上,把可交换性读作经由一个接口的不可分辨性,而不是内部状态的相等 [45]。
Independence of two context-mediated iterators (Definition 30) turns on their keys alone:
上下文中介的两个迭代器(定义 30)的独立性只取决于它们的键:
Theorem 47. Let 𝑖1 ∈ ℑ𝒜︀ Σ(𝑆1, 𝑃1) and 𝑖2 ∈ ℑ𝒜︀ Σ(𝑆2, 𝑃2) with 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀, and let every key at which operations of both occur be commutative ( Definition 44). Then 𝑖1 and 𝑖2 are independent (Definition 42).
定理 47. 设 𝑖1 ∈ ℑ𝒜︀Σ(𝑆1, 𝑃1) 与 𝑖2 ∈ ℑ𝒜︀Σ(𝑆2, 𝑃2),且 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀,又设二者的操作共同出现于其上的每个键都是可交换的(定义 44)。那么 𝑖1 与 𝑖2 独立(定义 42)。
Proof. Every iterator reach(𝑖𝑙) contains is the unit or a stage, whose forward map and yielded inverses are the constituents of the operation or the set its head performs, so the generators of 𝔐(𝑖𝑙) are among those of the stages occurring in 𝑖𝑙, together with idΣ.
证明. reach(𝑖𝑙) 所含的每个迭代器或者是单位元,或者是一个阶段,其前向映射与所产出的逆操作,正是其首部所执行的操作或集合的那些组成部分,因此 𝔐(𝑖𝑙) 的生成元都在 𝑖𝑙 中出现的各阶段的生成元之中,此外还有 idΣ。
For clause (1) of Definition 42 it is enough, by Lemma 41(1), that those generators commute pairwise. Each is key-local: defined by the binding at one key alone, presence included, and writing that binding alone, being the lift of an operation’s forward map or of an inverse it yields (Definition 29), the extension a provision stage takes, or the restriction it yields (Definition 20). Two key-local maps at distinct keys commute, each leaving what the other reads and writes as it stands. This settles every pair involving a provision-stage generator, whose key lies in one 𝑃𝑙 and hence outside the other member’s every key by hypothesis, and every pair of operation generators at distinct keys, which is Theorem 45; a pair of operation generators at one key is covered by that key’s commutativity.
对于定义 42 的子句 (1),由引理 41(1),只需这些生成元两两可交换即可。每个生成元都是键局部的:它仅由某一个键上的绑定所定义(该键是否存在也包括在内),并且只写下那个绑定——它或者是操作的前向映射、或者是操作所产出的逆操作的提升(定义 29),或者是提供阶段所施加的扩张,或者是它所产出的限制(定义 20)。两个位于不同键上的键局部映射可交换,因为各自都让对方所读写的那个绑定保持原样。这就解决了所有涉及提供阶段生成元的配对——其键落在某一个 𝑃𝑙 中,因而由假设落在另一个成员的所有键之外;也解决了位于不同键上的操作生成元的配对,此即定理 45;而位于同一个键上的一对操作生成元,则由该键的可交换性覆盖。
For clause (2), take 𝑖′ ∈ reach(𝑖1), 𝑔 ∈ 𝔐(𝑖2), and 𝜎 ∈ Σ. The unit yields (idΣ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀) every- where. A provision stage at 𝑘 ∈ 𝑃1 yields the restriction at 𝑘 and its one continuation whatever the state, and is defined at 𝜎 and 𝑔(𝜎) alike, its precondition reading the presence of 𝑘, which every generator of 𝔐(𝑖2) leaves as it stands. An operation stage at 𝑘 ∈ 𝑆1 yields what 𝑎Σ(𝑥) yields at 𝜎(𝑘), inverse and outcome; where no operation of 𝑖2 occurs at 𝑘 the generators of 𝔐(𝑖2) leave 𝜎(𝑘) as it stands, and where one does the key is commutative by hypothesis, and independence of its operations, applied to one generator of 𝑔 at a time, yields the same inverse and the same outcome at 𝑔(𝜎). Equal outcomes select one continuation, so the yields agree. □
对于子句 (2),取 𝑖′ ∈ reach(𝑖1)、𝑔 ∈ 𝔐(𝑖2) 与 𝜎 ∈ Σ。单位元处处产出 (idΣ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀)。位于 𝑘 ∈ 𝑃1 的提供阶段无论状态如何都产出 𝑘 处的限制及其唯一的延续,并且在 𝜎 与 𝑔(𝜎) 处同样有定义,因为其前提只读取 𝑘 的存在性,而 𝔐(𝑖2) 的每个生成元都让它保持原样。位于 𝑘 ∈ 𝑆1 的操作阶段产出 𝑎Σ(𝑥) 在 𝜎(𝑘) 处所产出的东西,逆操作与结果皆然;在 𝑖2 的操作不出现于 𝑘 处的地方,𝔐(𝑖2) 的各生成元让 𝜎(𝑘) 保持原样,而在有操作出现于那里时,该键由假设是可交换的,于是其操作的独立性一次用于 𝑔 的一个生成元,便在 𝑔(𝜎) 处给出相同的逆操作与相同的结果。相等的结果选定同一个延续,故两者的产出一致。□
With every coeffect witnessed, the commutativity hypothesis of Theorem 47 is supplied at every key ( Definition 46), and only the disjointness 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀ remains to be checked of a pair; Section 4 reads that disjointness off the two components’ declarations.
在每个余效应都被见证的情况下,定理 47 的可交换性假设在每个键上都已备好(定义 46),于是对一对成员而言,只剩下 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀ 这一不相交性有待检查;第 4 章从两个组件的声明中直接读出这一不相交性。
A component’s effect function is the lift of a context-mediated iterator along the coeffect projection (Definition 56), and independence transfers to that lift, whose transformations move the projection alone. The assumption Section 3.4.1 leaves open is met that way, the witness of each coeffect supplying the commutativity Theorem 47 consumes, and with it the temporal composability of a whole system of components.
一个组件的效应函数,是上下文中介的迭代器沿余效应投影的提升(定义 56),而独立性也随之转移到该提升上——它的变换只移动那个投影。第 3.4.1 节所留待解决的那个假设正是这样被满足的:每个余效应的见证提供了定理 47 所消耗的可交换性,随之而来的便是由组件构成的整个系统的时间可组合性。
What the decomposition divides is a computation’s commuting part from its order-sensitive part. The commuting part is carried by the effects: a component performs them in whatever order its task calls for, and Theorem 43 reverts them in whatever order the system finds conve- nient, no two components constraining each other. The order-sensitive part is carried by the coeffects, since a key whose operations do not commute is one whose order has to be imposed from outside the effects, and two places are available for imposing it. Within one component the accumulator imposes it, reverting in LIFO order whatever the effects (Theorem 16). Across components a declared coeffect imposes it, one component providing what another declares and the provision preceding the declaration’s satisfaction ( Section 3.2.2). Composability is 30
这一分解所划分的,是同一个计算中可交换的部分与对顺序敏感的部分。可交换的部分由效应承载:一个组件按它的任务所要求的任何顺序执行它们,而定理 43 按系统觉得方便的任何顺序把它们撤销,没有哪两个组件彼此约束。对顺序敏感的部分则由余效应承载,因为其诸操作不可交换的键,其顺序必须从效应之外被强加,而有两个位置可以用来强加它。在一个组件内部,累加器强加顺序,无论效应如何都按 LIFO 顺序撤销(定理 16)。在组件之间,被声明的余效应强加顺序:一个组件提供另一个组件所声明的东西,且这一提供先于该声明的被满足(第 3.2.2 节)。可组合性是
thereby had at the grain of components rather than of single effects, which is the scale Section 4 works at.
……因而必须处在组件的粒度上,而不是单个效应的粒度上,而这正是第 4 节所采用的尺度。
One limit of the theorem is worth naming, and it is the hypothesis this section opened on: binding every shared location at a key is the paradigm’s discipline and not a property of the construction, so a location the system cannot reify as a coeffect lies outside the boundary of Section 6.1 and outside the theorem with it.
该定理有一处局限值得一提,而它恰恰就是本节开篇所依据的那个假设:把每一个共享位置都绑定在某个键上,这是范式所要求的纪律,而非该构造本身的性质;因此,系统无法将其具体化为余效应的那些位置,既落在第 6.1 节的边界之外,也随之落在该定理的适用范围之外。
4. 动态组合演算
4. A Calculus of Dynamic Composition
This section gives the theory of Section 3 an operational semantics. It decomposes a running system into components, each a triple of a coeffect specification, a provision, and a witnessed effect function. The instantiations of components are fibers, and the calculus supplies the rules that move them: orchestration rules, by which the orchestrator inserts and retires fibers, and lifecycle rules, by which the system activates and deactivates them unprompted. The metathe- ory then establishes temporal and spatial composability in their global form, the guarantees Section 3 reads of one component holding of every fiber of an arbitrary interleaving.
本节为第 3 节的理论给出一套操作语义。它把一个运行中的系统分解为若干组件,每个组件都是一个三元组:一份余效应规约、一份提供项,以及一个带见证的效应函数。组件的实例化即纤程(译注:本文的 fiber 指组件在注册表中的实例化实体/条目,与操作系统中"纤程"的轻量级线程含义不同;为保持术语一致,仍译作"纤程"。),而演算提供推动这些纤程的规则:编排规则,编排器据此插入和退役纤程;以及生命周期规则,系统据此自发地激活和停用它们。随后的元理论在时间可组合性与空间可组合性的全局形式上确立这些保证——第 3 节从单个组件身上读出的保证,对任意交错中的每一个纤程都成立。
4.1. 组件与纤程
4.1. Components and Fibers
This section fixes the objects the rules act on: the component; the fiber, an instantiation of a component carrying a lifecycle state of its own; and the registry, which holds the fibers a state carries and from which the coeffect context is read off.
本节确定规则所作用的对象:组件;纤程,即组件的一个实例化,自身携带一份生命周期状态;以及注册表,它保存一个状态所携带的纤程,余效应上下文也正是从这一安排中读出的。
Components. A component is given as a triple, its coeffect side split into what it reads from the environment and what it provides to it.
组件。 一个组件被给作一个三元组,它的余效应一侧被拆分为从环境读取的部分和提供给环境的部分。
Definition 48. A component over a context Γ carrying both effects and coeffects (Definition 28) is defined as:
定义 48. 一个位于同时携带效应与余效应的上下文 Γ 之上的组件(定义 28)定义为:
ℭΓ ≔ (𝑑 : 𝔇Γ) × (𝑝 : 𝔓Γ) × ℑ𝑑∪𝑝 Γ (42)
ℭΓ ≔ (𝑑 : 𝔇Γ) × (𝑝 : 𝔓Γ) × ℑ𝑑∪𝑝 Γ (42)representing a triple (𝑑, 𝑝, 𝑒), where:
表示一个三元组 (𝑑, 𝑝, 𝑒),其中:
- • 𝑑 : 𝔇Γ is the coeffect specification of Definition 21, declaring the dependencies required from the environment;
𝑑 : 𝔇Γ 是定义 21 的余效应规约,声明需要从环境获得的依赖;
- • 𝑝 : 𝔓Γ ≔ 𝖲𝖾𝗍(𝐾) is the coeffect provision, declaring the coeffect keys the component may provide, and no key outside 𝑝 is one its effect function installs a binding at;
𝑝 : 𝔓Γ ≔ 𝖲𝖾𝗍(𝐾) 是余效应提供项,声明该组件可以提供的余效应键,并且 𝑝 之外的任何键,其效应函数都不会在其上安装绑定;
- • 𝑒 : ℑ𝑑∪𝑝 Γ is the witnessed effect function, an effect iterator ( Definition 17) witnessed up to ≃𝑑∪𝑝 (Definition 37), defining the effects contributed when the component is active together with the inverses that withdraw them; a plain effect function enters through the embedding of Section 3.1.3.
𝑒 : ℑ𝑑∪𝑝 Γ 是带见证的效应函数,即一个被见证到 ≃𝑑∪𝑝 为止(定义 37)的效应迭代器(定义 17),它定义了该组件处于激活状态时所贡献的效应,以及撤销这些效应的逆操作;一个普通的效应函数经由第 3.1.3 节的嵌入进入其中。
Subscripts are taken on Γ throughout, the coeffect context being one of its projections ( Defin- ition 28), so the 𝔇Σ of Definition 21 is written 𝔇Γ here.
下标一律取自 Γ,因为余效应上下文是它的一个投影(定义 28),所以定义 21 中的 𝔇Σ 在这里写作 𝔇Γ。
Fibers. One component may be instantiated many times over, and each instantiation is activated and deactivated over time, carrying a lifecycle state of its own. We name such an instantiation a fiber. A fiber records the component that produced it, the fiber it was instantiated under, the coeffects it provides, and where in its lifecycle it stands. 31
纤程。 一个组件可以被反复实例化许多次,而每个实例化都会随时间被激活与停用,各自携带一份生命周期状态。我们把这样一个实例化命名为纤程。一个纤程记录产生它的组件、它是在其下被实例化的那个纤程、它所提供的余效应,以及它处在生命周期的哪个位置。
Definition 49. Fix a set 𝔑 of fiber names. A fiber instantiating the component (𝑑, 𝑝, 𝑒) ∈ ℭΓ is a tuple ⟨𝑑, 𝑝, 𝑒, 𝜋, 𝜎, 𝜏, 𝜃⟩, where
定义 49. 固定一个纤程名的集合 𝔑。一个实例化组件 (𝑑, 𝑝, 𝑒) ∈ ℭΓ 的纤程是一个元组 ⟨𝑑, 𝑝, 𝑒, 𝜋, 𝜎, 𝜏, 𝜃⟩,其中
- • 𝑑 : 𝔇Γ, 𝑝 : 𝔓Γ, and 𝑒 : ℑ𝑑∪𝑝 Γ are the coeffect specification, provision, and effect function of Definition 48;
𝑑 : 𝔇Γ、𝑝 : 𝔓Γ 与 𝑒 : ℑ𝑑∪𝑝 Γ 分别是定义 48 中的余效应规约、提供项与效应函数;
- • 𝜋 : 𝔑 ∪ {𝗋𝗈𝗈𝗍} is the parent, the fiber this one was instantiated under, or the root marker 𝗋𝗈𝗈𝗍;
𝜋 : 𝔑 ∪ {𝗋𝗈𝗈𝗍} 是父节点,即这个纤程是在其下被实例化的那个纤程,或者根标记 𝗋𝗈𝗈𝗍;
- • 𝜎 : Σ is the fiber’s own coeffect table (Definition 19), empty until it activates and written by its effects as they run;
𝜎 : Σ 是该纤程自己的余效应表(定义 19),在激活之前为空,并在其效应运行时由这些效应写入;
- • 𝜏 : {⊥, ⊤} is the retirement flag, ⊥ in a fresh fiber and ⊤ once the orchestrator has retired the fiber;
𝜏 : {⊥, ⊤} 是退役标志,在一个新建纤程中为 ⊥,一旦编排器将该纤程退役即为 ⊤;
- • 𝜃 : ΘΓ is the lifecycle state:
𝜃 : ΘΓ 是生命周期状态:
ΘΓ ≔ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) | 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔, 𝜔) | 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) (43)
ΘΓ ≔ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) | 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔, 𝜔) | 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) (43)where 𝑖 : ℑ𝑑∪𝑝 Γ is the remaining effect iterator, 𝑔 : Γ → Γ the accumulator built so far, and 𝜔 : 𝑑 → 𝔑 the committed view.
其中 𝑖 : ℑ𝑑∪𝑝 Γ 是剩余的效应迭代器,𝑔 : Γ → Γ 是到目前为止累积起来的累加器,𝜔 : 𝑑 → 𝔑 是已提交的视图。
A fiber is installed when its lifecycle state carries an accumulator and a committed view:
当一个纤程的生命周期状态带有累加器和已提交视图时,称它已安装:
installed𝑛(𝛾) ≔ 𝜃𝑛 ≠ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 (44)
installed𝑛(𝛾) ≔ 𝜃𝑛 ≠ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 (44)and an installed fiber resolves 𝑘 to 𝑚 when 𝜔𝑛(𝑘) = 𝑚.
并且当 𝜔𝑛(𝑘) = 𝑚 时,一个已安装的纤程把 𝑘 解析为 𝑚。
A transition is what moves a fiber from one lifecycle state to another, and between transitions the fiber rests at one of the two settled states, 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 or 𝖠𝖼𝗍𝗂𝗏𝖾, contributing nothing at the first and its effects at the second. A transition runs in one of two directions: an activation executes 𝑒, accumulating side effects on the context, and a deactivation applies the accumulator to recover the context. A transition in a real runtime is spread over an interval rather than taken in one step, so each direction has a state of its own that the fiber occupies while the transition runs, 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 for an activation and 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 for a deactivation. The committed view 𝜔 sends each key the fiber declares to the name of the fiber that provided it when the transition committed. Section 4.2 draws the four states as a state machine ( Figure 1) and supplies the rules on its edges.
一次迁移就是把一个纤程从一种生命周期状态带到另一种生命周期状态的东西;在两次迁移之间,纤程停驻于两个稳定状态之一,即 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 或 𝖠𝖼𝗍𝗂𝗏𝖾,在前一个状态上不贡献任何东西,在后一个状态上贡献它的效应。迁移沿两个方向之一进行:一次激活执行 𝑒,在上下文上累积副作用;一次停用则应用累加器以恢复上下文。在真实的运行时中,迁移是分布在一个区间上的,而不是一步完成的,因此每个方向都有它自己的一个状态,供纤程在迁移进行期间占据:激活对应 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,停用对应 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀。已提交视图 𝜔 把该纤程声明的每个键送到迁移提交时提供该键的那个纤程的名字。第 4.2 节把这四个状态画成一个状态机(图 1),并给出其各条边上的规则。
Registry. A state holds its fibers under their names, and both the identity of a fiber and the coeffect context of Section 3.2 are read off that arrangement.
注册表。 一个状态以其名字保存它所拥有的纤程,而一个纤程的身份以及第 3.2 节的余效应上下文,都是从这一安排中读出的。
Definition 50. Write 𝔉Γ for the set of fibers over Γ. A state 𝛾 ∈ Γ carries a registry
定义 50. 记 𝔉Γ 为 Γ 上纤程的集合。一个状态 𝛾 ∈ Γ 携带一个注册表
𝐹𝛾 : 𝔑 ⇀ 𝔉Γ (45)
𝐹𝛾 : 𝔑 ⇀ 𝔉Γ (45)a finite partial function whose parent pointers form a tree rooted at 𝗋𝗈𝗈𝗍, together with whatever else in Γ no fiber’s 𝜎 names. We write 𝛾(𝑛) for 𝐹𝛾(𝑛), and abbreviate a field of 𝛾(𝑛) by subscripting it with 𝑛 where the state is clear, so that 𝑑𝑛, 𝑝𝑛, 𝑒𝑛, 𝜋𝑛, 𝜎𝑛, 𝜏𝑛, 𝜃𝑛 are the fields of Definition 49 and 𝑔𝑛, 𝜔𝑛 the accumulator and committed view that 𝜃𝑛 carries; 𝛾[𝜃𝑛 ↦ 𝜃′], 𝛾[𝑛 ↦ ⟨⋯⟩], and 𝛾 ∖ 𝑛 are the states differing from 𝛾 in one field, one fiber, and the presence of one fiber respectively.
这是一个有限偏函数,其父指针构成一棵以 𝗋𝗈𝗈𝗍 为根的树,此外还带有 Γ 中任何纤程的 𝜎 都没有指名的一切其他内容。我们把 𝐹𝛾(𝑛) 写作 𝛾(𝑛),并在状态明确时用下标 𝑛 简记 𝛾(𝑛) 的某个字段,于是 𝑑𝑛、𝑝𝑛、𝑒𝑛、𝜋𝑛、𝜎𝑛、𝜏𝑛、𝜃𝑛 就是定义 49 中的各字段,而 𝑔𝑛、𝜔𝑛 是 𝜃𝑛 所携带的累加器与已提交视图;𝛾[𝜃𝑛 ↦ 𝜃′]、𝛾[𝑛 ↦ ⟨⋯⟩] 与 𝛾 ∖ 𝑛 分别表示与 𝛾 在一个字段上、在一个纤程上、以及在某一个纤程的有无上有所不同的状态。
A fiber’s name is what gives it an identity that survives its own mutation: every rule below rewrites the lifecycle state of one fiber and leaves the others alone, so the rule has to say which one, and two fields refer to fibers rather than describe them, the parent 𝜋 and the committed view 𝜔. Names are atoms: no rule computes one, inspects its structure, or relates two of them by anything but equality, and introducing a fiber simply draws one not already in use. This is the discipline of dynamically created local names [40], used here for fiber identity. 32
一个纤程的名字,正是赋予它一种能够经受自身变动的身份的东西:下面的每一条规则都只重写一个纤程的生命周期状态而不触动其他纤程,所以规则必须指明是哪一个,而有两个字段是指称纤程而非描述纤程的,即父节点 𝜋 与已提交视图 𝜔。名字是原子:没有任何规则去计算一个名字、检视它的结构,或者用相等之外的任何关系去关联两个名字,而引入一个纤程只不过是抽取一个尚未使用的名字。这正是动态创建的局部名字 [40] 的纪律,这里被用于纤程的身份。
Each fiber owning a table means the coeffect context is derived rather than stored: it is what the active fibers jointly provide.
每个纤程各自持有一张表,这意味着余效应上下文是推导出来的而非存储的:它就是所有激活纤程共同提供的东西。
𝜎𝛾 ≔ ⋃{𝜎𝑚 | 𝑚 ∈ dom(𝐹𝛾), 𝜃𝑚 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −)} (46)
𝜎𝛾 ≔ ⋃{𝜎𝑚 | 𝑚 ∈ dom(𝐹𝛾), 𝜃𝑚 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −)} (46)The union is well defined because a fiber’s own table holds only the keys of its provision, dom(𝜎𝑛) ⊆ 𝑝𝑛 (Definition 48), and the provisions of distinct fibers are disjoint, O-Insert admit- ting no fiber whose provision meets an existing one (Section 4.2.1), so each 𝑘 ∈ dom(𝜎𝛾) lies in the table of exactly one 𝖠𝖼𝗍𝗂𝗏𝖾 fiber, whose name we write provider𝑘(𝛾) ∈ 𝔑 and call the provider of 𝑘. Each key therefore has one possible provider, fixed by the provisions and not by the state. No rule writes a table directly: the bindings a fiber provides are the provision stages its own effect function performs, which land in 𝜎𝑛 and so are already part of the state 𝑒𝑛 returns, and they leave again with the accumulator, and the operations it performs at a declared key act on the value its provider’s table holds (Definition 56). An effect function is built from such stages and from nothing else, so a location no key binds is one no fiber touches.
这个并集是良定义的,因为一个纤程自己的表只持有其提供项中所含的键,dom(𝜎𝑛) ⊆ 𝑝𝑛(定义 48),而不同纤程的提供项两两不交——O-Insert 不允许任何提供项与已有提供项相交的纤程进入(第 4.2.1 节),所以每个 𝑘 ∈ dom(𝜎𝛾) 都恰好落在一个 𝖠𝖼𝗍𝗂𝗏𝖾 纤程的表中,我们把该纤程的名字记作 provider𝑘(𝛾) ∈ 𝔑,并称之为 𝑘 的提供者。因此每个键都只有一个可能的提供者,这由提供项决定,而非由状态决定。没有任何规则直接写一张表:一个纤程所提供的绑定,就是它自己的效应函数所执行的那些提供阶段,它们落到 𝜎𝑛 中,从而已经是 𝑒𝑛 所返回的状态的一部分,并且它们又会随累加器一同离去,而它在某个已声明键上所执行的操作,作用于其提供者的表所保存的那个值(定义 56)。一个效应函数就是由这样一些阶段、并且仅由这样一些阶段构造出来的,因此一个没有被任何键绑定的位置,就是没有任何纤程会去触碰的位置。
The disjointness the union rests on is where this chapter parts company with Section 3.2.3, and it simplifies the formalization rather than the systems it models. The isolation of Defin- ition 24 lets one key resolve through a realm table, so that two fibers may provide the same key in different realms; a calculus carrying realms would relax disjointness to disjointness within a realm, resolving a declared key against the realm of the fiber declaring it (Section 4.4 supplies that reading). We read every key at one shared realm instead, and a system that wants several providers of one key keeps two routes: realms, and the broker of Section 6.2, one fiber providing the key and dispatching among implementations registered with it. Within the calculus, the disjointness restricts how often a component may be instantiated: one with a non- empty provision has one fiber at a time, so the many instantiations below are of components providing nothing, which is the common case of a component that only consumes, or that instantiates others.
作为该并集之依据的不交性,正是本章与第 3.2.3 节分道扬镳之处;它简化的是形式化本身,而不是它所建模的那些系统。定义 24 的隔离允许一个键经由某个领域表解析,从而两个纤程可以在不同领域中提供同一个键;一个携带领域的演算会把不交性放松为"在一个领域内的不交性",即针对声明该键的纤程所属的领域来解析这个已声明的键(第 4.4 节给出了这种读法)。我们则改为在唯一一个共享领域上读取每个键;而一个希望让某个键有多个提供者的系统,仍有两条路可走:领域,以及第 6.2 节的代理——由一个纤程提供该键,并在注册于它之上的多个实现之间分派。在演算内部,这种不交性限制了一个组件可以被实例化的频度:一个提供项非空的组件在同一时刻只能有一个纤程,因此下文中的那些多重实例化,都是提供项为空的组件的实例化,而这正是一个只做消费、或者只是去实例化其他组件的组件的常见情形。
With 𝜎𝛾 in hand, the satisfaction relation of Section 3.2.2 applies unchanged, 𝛾 ⊧ 𝑑 abbrevi- ating 𝜎𝛾 ⊧ 𝑑. A key lies in dom(𝜎𝛾) exactly when some 𝖠𝖼𝗍𝗂𝗏𝖾 fiber has installed it, its provision being the keys it may install rather than the ones it has, so 𝛾 ⊧ 𝑑 already requires that every declared key have an 𝖠𝖼𝗍𝗂𝗏𝖾 provider. Taking the union over 𝖠𝖼𝗍𝗂𝗏𝖾 fibers alone is what lets a fiber cease to provide before it has withdrawn anything, which Section 4.2.2 turns into the ordering discipline, and it fixes how a transition in progress reads: a 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 or 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 fiber reads its coeffects through the 𝜔 it holds and provides none of its own, so a key its transition has already written is not yet one a dependent may activate against.
有了 𝜎𝛾,第 3.2.2 节的满足关系即可原样适用,𝛾 ⊧ 𝑑 是 𝜎𝛾 ⊧ 𝑑 的简记。一个键属于 dom(𝜎𝛾),恰好当某个 𝖠𝖼𝗍𝗂𝗏𝖾 纤程已经安装了它;而一个纤程的提供项是它可以安装的那些键,而不是它已经安装的那些键,所以 𝛾 ⊧ 𝑑 已经要求每个已声明的键都有一个 𝖠𝖼𝗍𝗂𝗏𝖾 的提供者。只对 𝖠𝖼𝗍𝗂𝗏𝖾 纤程取并集,正是让一个纤程能够在尚未撤回任何东西之前就停止提供的原因,第 4.2.2 节将把它转化为一种序上的纪律;它也规定了一个进行中的迁移应如何读取:一个 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 或 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的纤程通过它所持有的 𝜔 读取自己的余效应,并且不提供任何它自己的余效应,因此一个已被它的迁移写入的键,还不是一个依赖方可以据以激活的键。
The two declarations of Definition 48 are the two directions of one interface, 𝑑 what a component reads from the environment and 𝑝 what it writes to it, and the superscript on the effect function’s type holds the witness to that interface. A fiber holds its own bindings whether or not they are published, so the projection the witness is read at is a second reading of the same tables.
定义 48 中的两份声明,是同一个接口的两个方向:𝑑 是一个组件从环境中读取的东西,𝑝 是它写入环境的东西,而效应函数类型上的上标则持有该接口的见证。一个纤程无论其绑定是否已被发布,都持有它自己的绑定,因此读取见证时所取的投影,是对同一些表的第二次读取。
Definition 51. Write
定义 51. 记
𝜎𝑆 𝛾 ≔ ⋃{𝜎𝑚|𝑆 | 𝑚 ∈ dom(𝐹𝛾)} (47)
𝜎𝑆 𝛾 ≔ ⋃{𝜎𝑚|𝑆 | 𝑚 ∈ dom(𝐹𝛾)} (47)for the bindings the registry records at a set 𝑆 ⊆ 𝐾 of keys, over every fiber and not the 𝖠𝖼𝗍𝗂𝗏𝖾 ones alone; disjointness of provisions makes it a function, and 𝜎𝛾 is the restriction of 𝜎𝐾 𝛾 to the 𝖠𝖼𝗍𝗂𝗏𝖾 fibers. This is the coeffect projection Definition 33 is read at throughout this section, so that 𝛾 ≃𝑆 𝛿 is 𝜎𝑆 𝛾 ≃ 𝜎𝑆 𝛿 and ℑ𝑆 Γ (Definition 37) is the effect functions witnessed at those keys. 33
为注册表在一组键 𝑆 ⊆ 𝐾 上所记录的绑定,它取遍所有纤程而不只是 𝖠𝖼𝗍𝗂𝗏𝖾 的那些;提供项的不交性使它成为一个函数,而 𝜎𝛾 则是 𝜎𝐾 𝛾 在 𝖠𝖼𝗍𝗂𝗏𝖾 纤程上的限制。这就是本节通篇读取定义 33 时所取的余效应投影,于是 𝛾 ≃𝑆 𝛿 即 𝜎𝑆 𝛾 ≃ 𝜎𝑆 𝛿,而 ℑ𝑆 Γ(定义 37)就是在这些键上被见证的效应函数。
The keys of both declarations are read off the tables rather than off 𝜎𝛾, and the witness condition is why: a binding a transition has written stays in the fiber’s table before the fiber is 𝖠𝖼𝗍𝗂𝗏𝖾, and that binding is what the inverse is held to remove, so the projection the witness is read at has to hold it wherever the fiber’s lifecycle stands. The same reading keeps the relation where the control fields cannot move it: a write to a lifecycle state can move a table into or out of 𝜎𝛾 with every binding left as it stands, whereas 𝜎𝑆 𝛾 moves only where some binding does. What the witness is thereby held to restore is the two declarations and nothing else.
两份声明中的键都是从这些表上读出的,而不是从 𝜎𝛾 上读出的,其原因就在于见证条件:一个迁移已经写入的绑定,在纤程变为 𝖠𝖼𝗍𝗂𝗏𝖾 之前一直留在该纤程的表中,而这个绑定正是逆操作被要求移除的东西,因此读取见证时所取的投影,无论该纤程的生命周期处在何处都必须持有它。同样的读法把该关系保持在控制字段无法移动它的地方:一次对生命周期状态的写入,可以在每个绑定都保持原样的情况下,把一张表移入或移出 𝜎𝛾;而 𝜎𝑆 𝛾 只在某个绑定发生变动时才变动。由此,见证被要求恢复的东西,就是这两份声明,此外无他。
4.2. 演算
4.2. The Calculus
This section gives the calculus: nine rules generating two relations. An orchestration rule, prefixed O- and written 𝛾 ⇒ 𝛿, is an action the orchestrator may perform; its premises say when the action is legal, not when it occurs. A lifecycle rule, prefixed L- and written 𝛾 ⟶ 𝛿, is a step the system takes unprompted whenever its premises hold. A sequence of steps interleaves the two. Eight of the nine lie on the edges of Figure 1; O-Retire writes the retirement flag alone and applies at every lifecycle state, so it would be a self-loop at each of the four nodes, and the figure omits it.
本节给出这个演算:九条规则生成两个关系。一条编排规则,前缀为 O-、记作 𝛾 ⇒ 𝛿,是编排器可以执行的一个动作;它的前提说明的是该动作何时合法,而不是它何时发生。一条生命周期规则,前缀为 L-、记作 𝛾 ⟶ 𝛿,是系统在其前提成立时自发采取的、无需外部触发的一步。一个步骤序列就是两者的交错。九条规则中有八条位于图 1 的各条边上;O-Retire 只写退役标志,并且在每一种生命周期状态下都适用,因此它在四个节点上都会是一个自环,图中便将其略去。
O-Insert O-Remove L-Begin L-Finish L-LeaveL-Unload L-Iter L-Divert𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝖠𝖼𝗍𝗂𝗏𝖾 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀
O-Insert
O-Remove
L-Begin L-Finish
L-Leave L-Unload
L-Iter
L-Divert 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝖠𝖼𝗍𝗂𝗏𝖾
𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀
𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀Figure 1 | The component lifecycle, with the empty node marking a fiber absent from the registry
图 1 | 组件生命周期,其中空节点表示一个不在注册表中的纤程
4.2.1. 编排
4.2.1. Orchestration
Insertion and retirement are the only external inputs: the orchestrator requests that a fiber exist or stop existing, and never sets its lifecycle state directly.
插入与退役是仅有的外部输入:编排器请求一个纤程存在或停止存在,而从不直接设置它的生命周期状态。
𝑛 ∉ dom(𝐹𝛾) 𝜋 ∈ dom(𝐹𝛾) ∪ {𝗋𝗈𝗈𝗍} (𝑑, 𝑝, 𝑒) ∈ ℭΓ ∀𝑚 ∈ dom(𝐹𝛾). 𝑝 ∩ 𝑝𝑚 = ⌀ 𝛾 ⇒ 𝛾[𝑛 ↦ ⟨𝑑, 𝑝, 𝑒, 𝜋, ⌀, ⊥, 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾⟩] O-Insert 𝑛 ∈ dom(𝐹𝛾) 𝛾 ⇒ 𝛾[𝜏𝑛 ↦ ⊤] O-Retire 𝜏𝑛 = ⊤ 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝜎𝑛 = ⌀ ∀𝑚. 𝜋𝑚 ≠ 𝑛 𝛾 ⇒ 𝛾 ∖ 𝑛 O-Remove
𝑛 ∉ dom(𝐹𝛾) 𝜋 ∈ dom(𝐹𝛾) ∪ {𝗋𝗈𝗈𝗍} (𝑑, 𝑝, 𝑒) ∈ ℭΓ ∀𝑚 ∈ dom(𝐹𝛾). 𝑝 ∩ 𝑝𝑚 = ⌀
────────────────────────────────────────────────────────────────────────
𝛾 ⇒ 𝛾[𝑛 ↦ ⟨𝑑, 𝑝, 𝑒, 𝜋, ⌀, ⊥, 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾⟩] O-Insert
𝑛 ∈ dom(𝐹𝛾)
────────────
𝛾 ⇒ 𝛾[𝜏𝑛 ↦ ⊤] O-Retire
𝜏𝑛 = ⊤ 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝜎𝑛 = ⌀ ∀𝑚. 𝜋𝑚 ≠ 𝑛
──────────────────────────────────────────
𝛾 ⇒ 𝛾 ∖ 𝑛 O-RemoveO-Retire is unconditional on the fiber’s state because retiring is a request, and the lifecycle rules are what carry it out. Retirement is separated from removal for the same reason: a retired fiber that is still 𝖠𝖼𝗍𝗂𝗏𝖾 must first be deactivated, and removing it earlier would discard the accumulator and leak. The premise ∀𝑚. 𝜋𝑚 ≠ 𝑛 keeps the tree well-formed by removing children before their parent, and 𝜎𝑛 = ⌀ admits only an entry holding no bindings, so that a removal discards none: a deactivation leaves the entry so ( Corollary 69), and it is what lets Theorem 68 count a removal as no change to the tables. The last premise of O-Insert is where the 34 single-source discipline is imposed: a key has one possible provider because the orchestrator may not admit a second component declaring it.
O-Retire 之所以对纤程的状态不作条件限制,是因为退役只是一个请求,而执行它的是那些生命周期规则。退役之所以与移除分离,原因相同:一个已退役但仍然处于 𝖠𝖼𝗍𝗂𝗏𝖾 的纤程必须先被停用,而提前移除它会丢弃累加器并造成泄漏。前提 ∀𝑚. 𝜋𝑚 ≠ 𝑛 通过先移除子节点再移除其父节点来保持这棵树的良构性,而 𝜎𝑛 = ⌀ 只允许一个不持有任何绑定的条目被移除,从而一次移除不会丢弃任何绑定:一次停用会留下该条目(推论 69),而这正是定理 68 能够把一次移除算作对诸表无任何改动的原因。O-Insert 的最后一个前提,正是单源纪律被施加的地方:一个键之所以只有一个可能的提供者,是因为编排器不得接纳第二个声明了该键的组件。
实例化
Instantiation. A component may instantiate another while installing its effects, which is what a plugin host does when a plugin loads plugins of its own. The rules so far leave the registry to the orchestration rules alone, so such an instantiation has nowhere to happen. One primitive gives it somewhere.
一个组件可以在安装其效应的同时实例化另一个组件,这正是插件宿主在某个插件加载它自己的插件时所做的事情。到目前为止的规则把注册表完全交给了编排规则,因此这样一个实例化无处发生。有一个原语给了它发生的场所。
Definition 52. An iteration of 𝑒𝑛 may instantiate a component (𝑑, 𝑝, 𝑒) ∈ ℭΓ. In place of a state map it takes the O-Insert of that component with 𝜋 = 𝑛, and it yields as its inverse the O-Retire of the fiber so instantiated. The rule draws the name, subject to the freshness premise of O- Insert, and hands it to the effect function.
定义 52. 𝑒𝑛 的一次迭代可以实例化一个组件 (𝑑, 𝑝, 𝑒) ∈ ℭΓ。它不取一个状态映射,而是取该组件在 𝜋 = 𝑛 下的那次 O-Insert,并交出对被如此实例化的那个纤程的 O-Retire 作为其逆操作。该规则在服从 O-Insert 的新鲜性前提之下抽取名字,并把它交给效应函数。
The inverse retires rather than removes, and the reason is that an inverse has to apply wherever it is reached. O-Remove carries premises, so an inverse built from it can fail to: a parent whose child is still 𝖠𝖼𝗍𝗂𝗏𝖾 could not run its accumulator, and no rule would move the child, since Definition 53 does not read the fiber tree. O-Retire has 𝑛 ∈ dom(𝐹𝛾) as its only premise. The entry it leaves behind at the state the instantiation was taken is retired, 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾, and holds an empty table, which is the vestigial entry of Lemma 62: it differs from the absence of the fiber in control fields alone, and no rule tells the two apart.
逆操作选择的是退役而非移除,原因在于一个逆操作必须在它被抵达的任何地方都可用。O-Remove 带有前提,因此由它构造出来的逆操作有可能无法应用:一个其子节点仍处于 𝖠𝖼𝗍𝗂𝗏𝖾 的父节点无法运行它的累加器,而没有任何规则会去移动那个子节点,因为定义 53 并不读取纤程树。O-Retire 的唯一前提则是 𝑛 ∈ dom(𝐹𝛾)。它在实例化被采取的那一状态上留下的条目是已退役的、𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 的,并且持有一张空表,这正是引理 62 所说的那个残留条目:它与该纤程的缺席仅在控制字段上有所不同,而没有规则能把这两者区分开来。
Retiring a child sets 𝜏 and so takes its target view to ⊥, after which the ordinary rules carry it back to 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾. The parent is not made to wait, O-Retire being unconditional, so L-Unload applies to the parent whether or not the child has left. A grandchild is reached one level at a time, the child’s own accumulator retiring what the child instantiated. Theorem 73 covers this cascade and the one the guard of Section 4.2.2 imposes along coeffects together.
退役一个子节点会设置 𝜏,从而把它的目标视图置为 ⊥,此后由普通的那些规则把它带回 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾。父节点不必等待,因为 O-Retire 是无条件的,所以无论子节点是否已经离开,L-Unload 都适用于该父节点。一个孙节点则是一层一层被抵达的,由子节点自己的累加器去退役该子节点所实例化的东西。定理 73 覆盖了这一级联,以及第 4.2.2 节的守卫沿余效应方向一并施加的那一次级联。
4.2.2. 生命周期
4.2.2. Lifecycle
The six lifecycle rules divide by the direction they move a fiber in: an activation carries a fiber toward a target view it does not yet hold, and a deactivation carries one away from a committed view that is no longer its target.
六条生命周期规则按它们把纤程移动的方向划分:一次激活把一个纤程带向一个它尚未持有的目标视图,而一次停用把一个纤程带离一个已不再是它的目标的已提交视图。
目标视图
Target views. The rules compare each fiber against a target, namely whether it ought to be running and against which resolution of its dependencies. The target is not a property of the fiber alone, since the keys a fiber declares are resolved against the whole state, so it is a predicate on that state.
这些规则把每个纤程与一个目标相比较,即它是否应当运行、以及应当针对其依赖的哪一种解析来运行。这个目标不是纤程自身的性质,因为一个纤程所声明的键是相对于整个状态来解析的,所以它是关于那个状态的一个谓词。
Definition 53. The target view of 𝑛 at 𝛾 maps each declared key to its provider, so it is a total map 𝑑𝑛 → 𝔑, and is ⊥ when 𝑛 ought not to be running at all:
定义 53. 𝑛 在 𝛾 处的目标视图把每个已声明的键映射到它的提供者,因此它是一个全映射 𝑑𝑛 → 𝔑,并且当 𝑛 根本不应当运行时为 ⊥:
target𝑛(𝛾) ≔ {⊥ if 𝜏𝑛 ∨ ¬(𝛾 ⊧ 𝑑𝑛) (𝑘 ∈ 𝑑𝑛) ↦ provider𝑘(𝛾) otherwise (48)
target𝑛(𝛾) ≔ { ⊥ 若 𝜏𝑛 ∨ ¬(𝛾 ⊧ 𝑑𝑛)
{ (𝑘 ∈ 𝑑𝑛) ↦ provider𝑘(𝛾) 否则 (48)A state is quiescent when every fiber has settled at its target view, no transition left in progress:
当一个状态中的每个纤程都已停驻在其目标视图处、没有任何迁移仍在进行时,称该状态是静止的:
quiet(𝛾) ≔ ∀𝑛 ∈ dom(𝐹𝛾). { target𝑛(𝛾) = ⊥ if 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 target𝑛(𝛾) = 𝜔𝑛 if 𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔𝑛) ⊥ otherwise (49) 35
quiet(𝛾) ≔ ∀𝑛 ∈ dom(𝐹𝛾).
{ target𝑛(𝛾) = ⊥ 若 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾
{ target𝑛(𝛾) = 𝜔𝑛 若 𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔𝑛) (49)
{ ⊥ 否则The target answers to two things and to nothing else: retirement, through 𝜏𝑛, and coeffect resolution, through 𝛾 ⊧ 𝑑𝑛 and provider𝑘, each declared key being read off 𝜎𝛾 at the one shared realm of Section 4.1.
这个目标只回应两样东西,此外无他:退役,通过 𝜏𝑛;以及余效应解析,通过 𝛾 ⊧ 𝑑𝑛 与 provider𝑘,其中每个已声明的键都是在第 4.1 节所说的那个唯一共享领域上从 𝜎𝛾 读出的。
The committed view of Definition 49 has the same type as the target view, and the lifecycle is driven by comparing them: 𝜔𝑛 is the resolution 𝑛 activated against, target𝑛(𝛾) the one it should be running against, and every rule below fires on their agreeing or differing. This is the reactive discipline of Section 3.2: a transition is initiated whenever the target view changes, regardless of which of the two moved it. Recording a provider rather than a value is what makes the comparison usable, since a different fiber providing an equal value would otherwise compare equal. The value a component reads is reached through the view, since the provider’s table holds that value, and the implementation holds the map in fiber.committed and a hash of it in fiber.target (Section 5.1.3).
定义 49 的已提交视图与目标视图具有相同的类型,而生命周期正是由对二者的比较所驱动的:𝜔𝑛 是 𝑛 激活时所针对的那份解析,target𝑛(𝛾) 则是它应当据以运行的那份解析,下面的每一条规则都在二者一致或不一致时触发。这正是第 3.2 节的响应式纪律:每当目标视图发生变化,就发起一次迁移,无论推动它变化的是两者中的哪一个。记录的是提供者而不是值,这正是使这一比较可用的原因,否则由一个不同的纤程提供一个相等的值时也会被比较为相等。一个组件所读到的值是经由该视图抵达的,因为提供者的表持有那个值;而实现把这个映射保存在 fiber.committed 中,并把它的一个散列保存在 fiber.target 中(第 5.1.3 节)。
激活
Activation. An activation may execute multiple effects in sequence, and the deactivation must revert them. The effect iterator 𝑒𝑛 models such an activation ( Section 3.1.3), each of its iterations yielding the modified context, an inverse, and a continuation, and a component’s whole activation is one run of 𝑒𝑛: L-Begin enters 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀, L-Iter takes one iteration, and L- Finish lands the last.
一次激活可以顺序执行多个效应,而停用必须把它们撤销。效应迭代器 𝑒𝑛 建模的正是这样一次激活(第 3.1.3 节),它的每一次迭代都交出被修改后的上下文、一个逆操作和一个延续,而一个组件的整次激活就是 𝑒𝑛 的一次运行:L-Begin 进入 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,L-Iter 走一步迭代,L-Finish 落下最后一步。
𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝜔 = target𝑛(𝛾) ≠ ⊥ 𝛾 ⟶ 𝛾[𝜃𝑛 ↦ 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑒𝑛, idΓ, 𝜔)] L-Begin 𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) = 𝜔 𝑖(𝛾) = (𝛿, ℎ, 𝖩𝗎𝗌𝗍(𝑖′)) 𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖′, 𝑔 ∘ ℎ, 𝜔)] L-Iter 𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) = 𝜔 𝑖(𝛾) = (𝛿, ℎ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀) 𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔 ∘ ℎ, 𝜔)] L-Finish
𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 𝜔 = target𝑛(𝛾) ≠ ⊥
──────────────────────────────────────────
𝛾 ⟶ 𝛾[𝜃𝑛 ↦ 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑒𝑛, idΓ, 𝜔)] L-Begin
𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) = 𝜔 𝑖(𝛾) = (𝛿, ℎ, 𝖩𝗎𝗌𝗍(𝑖′))
─────────────────────────────────────────────────────────────────
𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖′, 𝑔 ∘ ℎ, 𝜔)] L-Iter
𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) = 𝜔 𝑖(𝛾) = (𝛿, ℎ, 𝖭𝗈𝗍𝗁𝗂𝗇𝗀)
─────────────────────────────────────────────────────────────────
𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔 ∘ ℎ, 𝜔)] L-FinishEach iteration composes the newly yielded inverse onto the accumulator as 𝑔 ∘ ℎ, following Definition 18, so that the accumulator applies the inverses in last-in-first-out order.
每一次迭代都依定义 18 把新交出的逆操作以 𝑔 ∘ ℎ 的形式复合到累加器上,从而使累加器以后进先出的顺序应用这些逆操作。
停用
Deactivation. A deactivation enters from either stage of the lifecycle: L-Divert takes out a fiber whose transition is still in progress, and L-Leave one that is 𝖠𝖼𝗍𝗂𝗏𝖾. It cannot be taken in one step. A component being torn down because its provider is going away is running its own teardown code, which may need the very coeffect that is being withdrawn; closing a connection pool typically means handing the connections back to whatever provided them. The consumer must therefore still be able to read the key throughout its own deactivation, and the provider’s withdrawal must take effect only afterwards, which gives content to the ordering Section 3.2 requires of dependencies and dependents. A deactivation taken in one step would remove the provisions and run the inverse together, with no interval between them for a consumer’s teardown to occupy. The rules below therefore separate the decision from the act, and the act is guarded by the following condition.
一次停用可以从生命周期的任一个阶段进入:L-Divert 处理一个迁移仍在进行中的纤程,L-Leave 处理一个处于 𝖠𝖼𝗍𝗂𝗏𝖾 的纤程。它不可能一步完成。一个因为其提供者正在离去而被拆除的组件,正在运行它自己的拆除代码,而这段代码可能恰恰需要那个正被撤回的余效应;关闭一个连接池通常意味着把那些连接交还给提供它们的一方。因此,消费方在其自身的整个停用期间必须仍然能够读取该键,而提供者的撤回只能在之后再生效——这赋予了第 3.2 节对依赖与依赖方所要求的那种次序以实际内容。一步完成的停用会同时移除那些提供项并运行逆操作,二者之间没有任何区间可供消费方的拆除代码占据。因此下面的规则把决定与行动分离开来,而该行动由下述条件守卫。
Definition 54. The fiber 𝑛 is relied upon at 𝛾 when some other installed fiber resolves a key to it:
定义 54. 当某个其他已安装的纤程把某个键解析到 𝑛 时,称纤程 𝑛 在 𝛾 处被依赖:
relied𝑛(𝛾) ≔ ∃𝑚 ∈ dom(𝐹𝛾), 𝑘 ∈ 𝑑𝑚. 𝑚 ≠ 𝑛 ∧ installed𝑚(𝛾) ∧ 𝜔𝑚(𝑘) = 𝑛 (50) 36
relied𝑛(𝛾) ≔ ∃𝑚 ∈ dom(𝐹𝛾), 𝑘 ∈ 𝑑𝑚. 𝑚 ≠ 𝑛 ∧ installed𝑚(𝛾) ∧ 𝜔𝑚(𝑘) = 𝑛 (50)𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) ≠ 𝜔 (𝛿, ℎ) = (𝛾, idΓ) ∨ 𝑖(𝛾) = (𝛿, ℎ, −) 𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔 ∘ ℎ, 𝜔)] L-Divert 𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔, 𝜔) target𝑛(𝛾) ≠ 𝜔 𝛾 ⟶ 𝛾[𝜃𝑛 ↦ 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔)] L-Leave 𝜃𝑛 = 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) ¬ relied𝑛(𝛾) 𝑔(𝛾) = 𝛿 𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾] L-Unload
𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) target𝑛(𝛾) ≠ 𝜔 (𝛿, ℎ) = (𝛾, idΓ) ∨ 𝑖(𝛾) = (𝛿, ℎ, −)
𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔 ∘ ℎ, 𝜔)]
L-Divert
𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔, 𝜔) target𝑛(𝛾) ≠ 𝜔
𝛾 ⟶ 𝛾[𝜃𝑛 ↦ 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔)]
L-Leave
𝜃𝑛 = 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) ¬ relied𝑛(𝛾) 𝑔(𝛾) = 𝛿
𝛾 ⟶ 𝛿[𝜃𝑛 ↦ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾]
L-UnloadL-Divert may fall between any two consecutive iterations of a transition, routing the fiber into 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 with the inverses accumulated so far rather than applying them on the spot. Routing through 𝖠𝖼𝗍𝗂𝗏𝖾 instead would let the fiber provide its coeffects for the length of one step and oblige its dependents to activate against a component that is already leaving. The first of L-Divert’s two alternatives aborts the iteration the fiber is holding, which only an iteration boundary makes possible, so the granularity at which a divert may fall is that of the iterator; the second lets that iteration land, serving the host Section 4.4 admits, in which an iteration in flight cannot be declined.
L-Divert 可以落在一次变迁的任意两次连续迭代之间,把纤程连同到目前为止已累积的逆操作一起转入 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀,而不是就地应用这些逆操作。若改为经由 𝖠𝖼𝗍𝗂𝗏𝖾 转接,则会让该纤程在一个步骤的时间长度内继续提供其余效应,并迫使它的依赖方对着一个已经在退出的组件完成激活。L-Divert 两个分支中的第一个会中止该纤程正持有的那次迭代,这只有在迭代边界上才可能做到,因此一次 divert 所能落入的粒度就是迭代器的粒度;第二个分支则让那次迭代落地,服务于 4.4 节所承认的那类宿主——在其中,一次在途的迭代不能被拒绝。
L-Leave records the decision to deactivate without acting on it, which stops the fiber providing its coeffects while leaving its own committed view and everyone else’s intact. L- Unload applies the accumulator, discards the committed view, and leaves the fiber 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾; it is the only rule in the calculus that applies an accumulator, an L-Divert routing here rather than applying one of its own.
L-Leave 记下停用的决定却不付诸实施,这使该纤程停止提供其余效应,同时把它自己的已提交视图以及所有其他纤程的已提交视图原样保留下来。L-Unload 应用累加器、丢弃已提交视图,并使该纤程变为 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾;它是演算中唯一会应用累加器的规则,L-Divert 把纤程转接到这里,而不是自行应用某个累加器。
The two requirements are then carried by different parts of the form: the consumer’s reading by the committed view, which L-Unload discards as its last act, and the deferral of the withdrawal by the premise ¬ relied𝑛(𝛾), which we call the guard and which holds a provider’s withdrawal back until every consumer that resolves a key to 𝑛 has gone. For a fiber L-Divert takes out of its first transition the guard is vacuous, a fiber that has never been 𝖠𝖼𝗍𝗂𝗏𝖾 providing nothing and appearing in no committed view. Theorem 70 establishes both requirements.
于是那两项要求由这一形式的不同部分分别承担:消费者的读取由已提交视图承担,而 L-Unload 把丢弃它作为自己的最后一个动作;撤回的推迟则由前提 ¬ relied𝑛(𝛾) 承担,我们称之为守卫(guard),它把一个提供者的撤回一直扣住,直到每一个把某个键解析到 𝑛 的消费者都已经离去。对于一个被 L-Divert 从它的第一次变迁中带出来的纤程,守卫是空真成立的:一个从未进入 𝖠𝖼𝗍𝗂𝗏𝖾 的纤程不提供任何东西,也不出现在任何已提交视图中。定理 70 确立了这两项要求。
The guard is imposed per binding rather than per fiber: relied𝑛(𝛾) tests whether some committed view names 𝑛, so a fiber that declares none of 𝑛’s keys is no obstacle, and neither is one that resolved a key of 𝑛’s in another realm ( Section 3.2.3). Under the single-source discipline of O-Insert the per-binding reading coincides with the coarser test ∃𝑚 ≠ 𝑛, 𝑘 ∈ 𝑑𝑚. installed𝑚(𝛾) ∧ 𝑘 ∈ 𝑝𝑛, a key having one possible provider there.
守卫是按绑定而非按纤程施加的:relied𝑛(𝛾) 检验的是是否有某个已提交视图指名 𝑛,因此一个并不声明 𝑛 的任何键的纤程不构成障碍,一个在另一个 realm(3.2.3 节)中解析过 𝑛 的某个键的纤程同样不构成障碍。在 O-Insert 的单源纪律之下,这种按绑定的读法与那个更粗的检验 ∃𝑚 ≠ 𝑛, 𝑘 ∈ 𝑑𝑚. installed𝑚(𝛾) ∧ 𝑘 ∈ 𝑝𝑛 相吻合,因为在那里一个键只有一个可能的提供者。
A guard of this kind ordinarily deadlocks. What keeps it from doing so is 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 together with 𝜎𝛾 being the union over 𝖠𝖼𝗍𝗂𝗏𝖾 fibers alone: once L-Leave or L-Divert has marked 𝑛, its table leaves 𝜎𝛾, so no target view can name 𝑛 any longer, and every consumer that committed to 𝑛 is itself on its way out. Theorem 73 turns that into the claim that the guard always releases.
这类守卫通常会造成死锁。使之不至于如此的是 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀,连同 𝜎𝛾 只对 𝖠𝖼𝗍𝗂𝗏𝖾 纤程取并这一事实:一旦 L-Leave 或 L-Divert 标记了 𝑛,它的表就离开了 𝜎𝛾,于是再没有任何目标视图能够指名 𝑛,而每一个曾向 𝑛 作出承诺的消费者自身也正在退出的路上。定理 73 把这一点转化为"守卫总会释放"这一断言。
The guard orders deactivations along coeffects and not along the fiber tree: a parent may run its inverse while a child of it is still 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀, since relied speaks only of committed views. Parent and child are accordingly ordered more weakly than Theorem 70 orders a provider and its consumer, and a parent and a child whose effects meet at a shared key are governed by the pairwise independence of Lemma 66 instead.
守卫是沿着余效应而非沿着纤程树来为停用排序的:一个父纤程可以在它的某个子纤程仍处于 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 时运行自己的逆操作,因为 relied 只谈已提交视图。因此父与子所受的排序,弱于定理 70 为一个提供者与其消费者所确立的排序;而一个父与一个子,若其效应在某个共享的键上相遇,则转由引理 66 的两两独立性来约束。
The rules are nondeterministic: several fibers may hold a committed view differing from their target view, and the relation commits to no order among them. They are also reactive only, in that no rule mentions a scheduler; the steps are any sequence of rule applications, so a theorem proved over all such sequences holds for every scheduling policy a runtime might adopt. 37
这些规则是非确定性的:可能有若干纤程同时持有着与其目标视图不同的已提交视图,而这一关系并不承诺它们之间有任何顺序。它们也纯然是响应式的,因为没有哪条规则提及调度器;各步骤就是规则应用的任意序列,因此一个对所有这样的序列都成立的定理,对运行时可能采取的任何调度策略都成立。
4.2.3. 限定
4.2.3. Confinement
With instantiation the one exception in hand, the discipline an effect function is held to can be given. It bounds what an application of an iteration writes, so that the rule applying it accounts for every other change, and what it reads, so that a fiber sees the coeffects it declared and no more of the registry. Bounding the writes is what lets Section 4.3 read Table 1 as a complete inventory of them.
既然实例化这唯一的例外已经握在手里,就可以给出效应函数所须遵守的纪律了。它限定一次迭代的应用写什么——使得应用它的那条规则能够交代其他每一处改动——以及读什么——使得一个纤程只看见它所声明的余效应,而看不见注册表更多的部分。对写的限定,正是让 4.3 节得以把表 1 读作这些写操作的完整清单的东西。
Definition 55. A map 𝑓 : Γ → Γ is confined to 𝑛 when for every 𝛾 ∈ Γ with 𝑛 ∈ dom(𝐹𝛾), writing 𝛿 = 𝑓(𝛾),
定义 55. 一个映射 𝑓 : Γ → Γ 被限定于 𝑛,是指对每个满足 𝑛 ∈ dom(𝐹𝛾) 的 𝛾 ∈ Γ,记 𝛿 = 𝑓(𝛾),都有:
- 1. (Writes.) dom(𝐹𝛿) = dom(𝐹𝛾), 𝛿(𝑚) and 𝛾(𝑚) differ at most in 𝜎𝑚|𝑑𝑛 for every 𝑚 ∈ dom(𝐹𝛾) with 𝑚 ≠ 𝑛, and 𝛿(𝑛) and 𝛾(𝑛) differ in 𝜎 alone;
(写。)dom(𝐹𝛿) = dom(𝐹𝛾);对 dom(𝐹𝛾) 中每个 𝑚 ≠ 𝑛,𝛿(𝑚) 与 𝛾(𝑚) 至多在 𝜎𝑚|𝑑𝑛 上不同;且 𝛿(𝑛) 与 𝛾(𝑛) 仅在 𝜎 上不同;
- 2. (Reads.) two states agreeing on 𝜎𝑛 and on the restrictions 𝜎𝑚|𝑑𝑛 for every 𝑚 ∈ dom(𝐹𝛾) are carried by 𝑓 to states agreeing on the same two.
(读。)两个在 𝜎𝑛 上一致、并且对每个 𝑚 ∈ dom(𝐹𝛾) 在限制 𝜎𝑚|𝑑𝑛 上也一致的状态,被 𝑓 送到在这同样的两个部分上仍保持一致的状态。
An effect function 𝑒 is confined to 𝑛 when every iterator 𝑖 ∈ reach(𝑒) either instantiates a compo- nent (Definition 52) or has both its state map pr1 ∘ 𝑖 and every inverse it yields confined to 𝑛.
一个效应函数 𝑒 被限定于 𝑛,是指每个迭代器 𝑖 ∈ reach(𝑒) 要么实例化一个组件(定义 52),要么其状态映射 pr1 ∘ 𝑖 以及它所产生的每个逆操作都被限定于 𝑛。
An instantiation writes the entry O-Insert writes, at the one name it draws, and nothing else; the O-Retire it yields as its inverse writes the 𝜏 of that name and nothing else. An application of either kind therefore writes no control field of a fiber already present, save that one 𝜏, and reads none at all.
一次实例化写的是 O-Insert 所写的那个条目,写在它抽出的那一个名字上,此外不写别的;它作为逆操作所产生的那个 O-Retire 写的是该名字的 𝜏,此外不写别的。因此这两类中任何一类的应用都不写一个已存在纤程的控制字段,唯一的例外是那一个 𝜏,并且根本不读任何控制字段。
Clause (1) permits a write outside the fiber’s own table, and there is exactly one kind: the value at a declared key lives in the provider’s table, so a component operating on a coeffect it declared moves 𝜎𝑚|𝑑𝑛 for the 𝑚 providing it. Clause (2) is why a component may read the values it declared as well: an effect function that reads no table but 𝜎𝑛 would be unable to use its own coeffects. What it may neither read nor write is a table outside the two declarations, any control field, or anything no table holds, which is what keeps a component from branching on the lifecycle state of a fiber it did not declare.
第 (1) 款允许在纤程自身表之外的一处写入,而这样的写入恰好只有一种:一个被声明的键上的值存放在提供者的表中,所以一个操作自己所声明的余效应的组件,会改动为之提供该余效应的那个 𝑚 的 𝜎𝑚|𝑑𝑛。第 (2) 款正是一个组件还可以读取它所声明的那些值的原因:一个只读 𝜎𝑛 而不读其他表的效应函数,将无法使用自己的余效应。它既不能读也不能写的,是这两个声明之外的任何表、任何控制字段,以及任何不被任何表所持有的东西——这正是使一个组件无法依据它未曾声明的纤程的生命周期状态来分支的原因。
The context paradigm fixes the form of an effect function — a sequence of stages, each a coeffect operation, a provision, or an instantiation — and confinement is a consequence of that form.
上下文范式规定了一个效应函数的形式——一个阶段序列,每个阶段是一个余效应操作、一次提供或一次实例化——而限定正是那个形式的推论。
Definition 56. A stage of Definition 30 lifts along the coeffect projection : it acts on the one table that holds the binding at its key, over every fiber and not the 𝖠𝖼𝗍𝗂𝗏𝖾 ones alone as Definition 51 reads the tables, an extension landing in the table of the fiber acting, and it leaves the rest of the state as it stands. The context-mediated iterators for 𝑛 form the least set ℑ𝒜︀ Γ(𝑛) of iterators on Γ that contains the unit and, each continuation drawn from 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 and the members, contains three iteration forms: the lift of an operation stage at a key of 𝑑𝑛 ∪ 𝑝𝑛, the lift of a provision stage at a key of 𝑝𝑛, and an instantiation (Definition 52). Every fiber’s effect function is required to lie in ℑ𝒜︀ Γ(𝑛) at that fiber.
定义 56. 定义 30 的一个阶段沿余效应投影提升:它作用于在其键处持有该绑定的那唯一一张表(遍及每个纤程,而不像定义 51 读表那样只遍及 𝖠𝖼𝗍𝗂𝗏𝖾 的那些),一次扩展落在施行动作的纤程的表里,而它把状态的其余部分原样留下。𝑛 的上下文中介迭代器构成 Γ 上迭代器的最小集合 ℑ𝒜︀Γ(𝑛),它包含单位元,并且——每个延续取自 𝖭𝗈𝗍𝗁𝗂𝗇𝗀 及各个成员——包含三种迭代形式:在 𝑑𝑛 ∪ 𝑝𝑛 的某个键处的一个操作阶段的提升、在 𝑝𝑛 的某个键处的一个提供阶段的提升,以及一次实例化(定义 52)。每个纤程的效应函数都被要求在该纤程处落入 ℑ𝒜︀Γ(𝑛)。
Lemma 57. A member of ℑ𝒜︀ Γ(𝑛) is confined to 𝑛, and it lies in ℑ𝑑𝑛∪𝑝𝑛 Γ (Definition 37) at the projection Definition 51 fixes, which is the witness Definition 48 requires of it.
引理 57. ℑ𝒜︀Γ(𝑛) 的一个成员被限定于 𝑛,并且它在定义 51 所确定的投影处落入 ℑ𝑑𝑛∪𝑝𝑛Γ(定义 37),这正是定义 48 对它所要求的见证条件。
Proof. For confinement, by induction on the construction. An instantiation is the exception Definition 55 carves out. The lift of a stage at 𝑘 writes the binding at 𝑘 and nothing else: at 𝑘 ∈ 𝑝𝑛 that binding lies in 𝜎𝑛, by disjointness of provisions, and at 𝑘 ∈ 𝑑𝑛 it lies in some 𝜎𝑚|𝑑𝑛 , which is clause (1); the inverse it yields, the lift of the operation’s inverse or the restriction at 𝑘 ∈ 𝑝𝑛, writes the same binding or removes it from 𝜎𝑛. For clause (2), a stage reads the binding 38 at its key and its presence, both determined by 𝜎𝑛 together with the 𝜎𝑚|𝑑𝑛 , and writes what it read into the same two parts.
证明. 就限定而言,对其构造作归纳。一次实例化正是定义 55 所划出的那个例外。在 𝑘 处一个阶段的提升只写 𝑘 处的绑定而不写别的:当 𝑘 ∈ 𝑝𝑛 时,由提供的不相交性,该绑定位于 𝜎𝑛 中;当 𝑘 ∈ 𝑑𝑛 时,它位于某个 𝜎𝑚|𝑑𝑛 中——这就是第 (1) 款;它所产生的逆操作,即该操作之逆的提升或在 𝑘 ∈ 𝑝𝑛 处的限制,写的是同一个绑定,或者把它从 𝜎𝑛 中移除。至于第 (2) 款,一个阶段读取其键处的绑定以及该绑定是否存在,这两者都由 𝜎𝑛 连同 𝜎𝑚|𝑑𝑛 决定,并且它把所读到的内容写回这两个相同的部分。
For membership, the induction of Lemma 39 carries over stage by stage: at an operation or provision stage the argument there applies as it stands, the lift moving the tables that jointly carry 𝜎𝑑𝑛∪𝑝𝑛 𝛾 as the stage moves the projection and moving nothing else, so respect and witness at ≃𝑑𝑛∪𝑝𝑛 on Σ read as the same conditions on Γ at the projection of Definition 51; an instan- tiating iteration adds an entry holding an empty table and yields the O-Retire writing one 𝜏, both invisible to ≃𝑑𝑛∪𝑝𝑛 , so its clauses hold outright. □
就成员资格而言,引理 39 的归纳逐阶段照搬过来:在一个操作或提供阶段,那里的论证原样适用——提升所移动的正是共同承载 𝜎𝑑𝑛∪𝑝𝑛𝛾 的那些表,正如该阶段移动那个投影一样,此外不移动任何东西,于是 Σ 上 ≃𝑑𝑛∪𝑝𝑛 处的 respect 与 witness 在该投影处读作 Γ 上的同样条件;一次实例化式迭代添加一个持有空表的条目,并产生写一个 𝜏 的那个 O-Retire,两者对 ≃𝑑𝑛∪𝑝𝑛 都是不可见的,所以它的各款直接成立。□
4.3. 元理论
4.3. Metatheory
This section establishes the metatheory of the calculus: that every rule preserves the well- formedness of the registry ( Section 4.3.1); that temporal and spatial composability hold in their global form, one fiber’s guarantee surviving whatever the other fibers do in between (Section 4.3.2, Section 4.3.3); that the system quiesces (Section 4.3.4); and that it quiesces where a load of the same configuration from scratch would have left it (Section 4.3.5).
本节确立该演算的元理论:每条规则都保持注册表的良构性(4.3.1 节);时间可组合性与空间可组合性以其全局形式成立,一个纤程的保证在其他纤程在其间所做的一切之下依然存活(4.3.2 节、4.3.3 节);系统会静止(4.3.4 节);并且它静止在从头加载同一份配置本会到达的地方(4.3.5 节)。
Every property below is a property of a sequence of steps, so we index the steps and read the fields of a state off that index.
下面的每个性质都是一个步骤序列的性质,因此我们为各步骤编号,并从该编号读出状态的各个字段。
Definition 58. Index the steps by 𝑡, so that 𝛾𝑡 is the state the first 𝑡 of them reach, and write
定义 58. 用 𝑡 为各步骤编号,使 𝛾𝑡 是前 𝑡 个步骤所到达的状态,并记
step𝑡 ≔ 𝑟(𝑛) (51)
step𝑡 ≔ 𝑟(𝑛) (51)for the step taken at 𝛾𝑡: the rule 𝑟 it applies, one of the nine, and the name 𝑛 ∈ 𝔑 it applies that rule at. The sequence starts at a 𝛾0 with dom(𝐹 0) = ⌀, so every fiber comes into existence by an O-Insert, whether the orchestrator’s or one an iteration takes ( Definition 52). A field of 𝛾𝑡 carries the index as a superscript, so that 𝜃𝑡 𝑛, 𝜔𝑡 𝑛, 𝜎𝑡 𝑛, 𝑔𝑡 𝑛, and 𝑖𝑡 𝑛 are the lifecycle state, committed view, table, accumulator, and remaining iterator of 𝑛 at 𝛾𝑡, and 𝐹 𝑡 and 𝜎𝑡 the registry and coeffect context of 𝛾𝑡 itself, the 𝐹𝛾 and 𝜎𝛾 of Definition 50 read there. Predicates take the state as their argument and everything else as a subscript, so installed𝑡 𝑛, target𝑡 𝑛, relied𝑡 𝑛, and quiet𝑡 are the predicates of Definition 49, Definition 53, and Definition 54 at 𝛾𝑡. An episode of 𝑛 is a maximal interval [𝑏, 𝑢] of indices throughout which installed𝑡 𝑛 holds. It opens at 𝑏, where 𝑏 > 0 and ¬ installed𝑏−1 𝑛 , the empty 𝐹 0 leaving no fiber installed at the outset; it closes at 𝑢 when installed𝑢 𝑛 and not installed𝑢+1 𝑛 , which a final episode need not do.
表示在 𝛾𝑡 处采取的步骤:它所应用的规则 𝑟(九条之一),以及它把该规则作用于其上的名字 𝑛 ∈ 𝔑。该序列起始于一个满足 dom(𝐹 0) = ⌀ 的 𝛾0,因此每个纤程都由一次 O-Insert 而进入存在,无论那是编排器的 O-Insert 还是某次迭代所采取的 O-Insert(定义 52)。𝛾𝑡 的一个字段把下标作为上标携带,于是 𝜃𝑡𝑛、𝜔𝑡𝑛、𝜎𝑡𝑛、𝑔𝑡𝑛 与 𝑖𝑡𝑛 分别是 𝑛 在 𝛾𝑡 处的生命周期状态、已提交视图、表、累加器和剩余迭代器,而 𝐹 𝑡 与 𝜎𝑡 是 𝛾𝑡 本身的注册表与余效应上下文,即定义 50 的 𝐹𝛾 与 𝜎𝛾 在该处的读法。谓词以状态为其变元、其余一切为下标,于是 installed𝑡𝑛、target𝑡𝑛、relied𝑡𝑛 与 quiet𝑡 是定义 49、定义 53 与定义 54 的谓词在 𝛾𝑡 处的读法。𝑛 的一个 episode(时段)是指标的一个极大区间 [𝑏, 𝑢],在其间 installed𝑡𝑛 始终成立。它在 𝑏 处开启,其中 𝑏 > 0 且 ¬ installed𝑏−1𝑛,因为空的 𝐹 0 使得起初没有任何纤程被安装;它在 𝑢 处闭合,当 installed𝑢𝑛 成立而 installed𝑢+1𝑛 不成立时——最后一个时段未必闭合。
Every rule of Section 4.2 concludes in the shape 𝛾 ⟶ 𝛿[⋯], where the premises compute 𝛿 from 𝛾 and leave it as 𝛾 where they compute nothing, and the bracket edits named fields of the registry. The two halves are named separately, and both are maps on all of Γ. The state map of a step taken at 𝛾𝑡 by a rule acting on 𝑛 is
4.2 节的每条规则都以 𝛾 ⟶ 𝛿[⋯] 的形状作结,其中各前提由 𝛾 计算出 𝛿,并在它们什么都没算出时把它留作 𝛾,而方括号编辑注册表里被指名的字段。这两半分别命名,且都是 Γ 全体的映射。在 𝛾𝑡 处由一个作用于 𝑛 的规则所采取的步骤,其状态映射为
Ψ𝑡 ≔ { pr1 ∘ 𝑖 at L-Iter, L-Finish, and a landing L-Divert 𝑔 at L-Unload idΓ at every other rule (52)
Ψ𝑡 ≔ pr1 ∘ 𝑖 在 L-Iter、L-Finish 以及一次落地的 L-Divert 处
𝑔 在 L-Unload 处
idΓ 在其余每条规则处 (52)where 𝑖 and 𝑔 are the iterator and the accumulator that 𝜃𝑡 𝑛 carries, and the edit edit𝑡 : Γ → Γ is the bracket read as a function, assigning to the fields it names the values the premises computed at 𝛾𝑡. Both are therefore fixed by step𝑡 together with 𝛾𝑡 and defined at every state, which is what lets Theorem 68 and Lemma 78 evaluate them away from 𝛾𝑡. Each step factors as 39
其中 𝑖 与 𝑔 是 𝜃𝑡𝑛 所携带的迭代器与累加器,而编辑 edit𝑡 : Γ → Γ 是把方括号读作一个函数,给它所命名的各字段指派前提在 𝛾𝑡 处算出的值。两者因此都由 step𝑡 连同 𝛾𝑡 确定,并且在每个状态上都有定义,这正是使定理 68 与引理 78 得以在 𝛾𝑡 之外对它们求值的原因。每一步都可分解为
𝛾𝑡+1 = edit𝑡(Ψ𝑡(𝛾𝑡)) (53)
𝛾𝑡+1 = edit𝑡(Ψ𝑡(𝛾𝑡)) (53)At L-Unload, for instance, edit𝑡 is [𝜃𝑛 ↦ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾], and at O-Remove it is the removal ∖ 𝑛, which is why the second half is an edit rather than an assignment. The fields divide along the same seam: the tables 𝜎𝑚, which no edit𝑡 writes once the O-Insert creating 𝑚 has set it empty, and the control fields 𝜃𝑚, 𝜏𝑚, 𝜋𝑚, 𝑑𝑚, 𝑝𝑚, 𝑒𝑚 together with dom(𝐹𝛾), which no Ψ𝑡 writes save through the primitive of Definition 52.
例如在 L-Unload 处,edit𝑡 是 [𝜃𝑛 ↦ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾],而在 O-Remove 处它是移除 ∖ 𝑛,这正是后一半被称为编辑而非赋值的原因。各字段沿同一道接缝划分:表 𝜎𝑚——一旦创建 𝑚 的那个 O-Insert 把它置空,就再没有 edit𝑡 写它——以及控制字段 𝜃𝑚、𝜏𝑚、𝜋𝑚、𝑑𝑚、𝑝𝑚、𝑒𝑚 连同 dom(𝐹𝛾)——除了经由定义 52 的原语之外,没有 Ψ𝑡 写它们。
A rule reads the control fields to decide whether it applies, so the relation two whole states are compared at has to keep them. It is Definition 33 over the registry conjoined with agreement on the registry’s domain and on every control field of every fiber:
一条规则要读控制字段来决定自己是否适用,因此对两个完整状态作比较时所依据的那个关系必须保留它们。它就是定义 33 在注册表上的版本,再合取上在注册表论域上、以及在每个纤程的每个控制字段上的一致:
𝛾 ≃ 𝛿 ≔ 𝜎𝐾 𝛾 ≃ 𝜎𝐾 𝛿 ∧ dom(𝐹𝛾) = dom(𝐹𝛿) ∧ ∀𝑛, 𝑐 ∈ {𝜃, 𝜏, 𝜋, 𝑑, 𝑝, 𝑒}. 𝑐(𝛾(𝑛)) ≃ 𝑐(𝛿(𝑛))(54)
𝛾 ≃ 𝛿 ≔ 𝜎𝐾𝛾 ≃ 𝜎𝐾𝛿 ∧ dom(𝐹𝛾) = dom(𝐹𝛿) ∧ ∀𝑛, 𝑐 ∈ {𝜃, 𝜏, 𝜋, 𝑑, 𝑝, 𝑒}. 𝑐(𝛾(𝑛)) ≃ 𝑐(𝛿(𝑛)) (54)A field of function type, as 𝑒𝑛 and the 𝑔 inside 𝜃𝑛 are, is compared as Definition 34 compares maps and iterators, and a field of any other type by equality. The results below compare states at the coarser readings Definition 51 gives, ≃𝐾 where every table is in question and ≃𝑑𝑛∪𝑝𝑛 where one fiber’s is, and the three are nested rather than crosswise, ≃ implying ≃𝐾 and ≃𝐾 implying ≃𝑆 at every 𝑆. Lemma 60 establishes the first once for all nine rules.
一个函数类型的字段,如 𝑒𝑛 以及 𝜃𝑛 内部的 𝑔,按定义 34 比较映射与迭代器的方式比较;任何其他类型的字段则按相等比较。下面的结果在定义 51 所给出的那些更粗的读法上比较状态:≃𝐾 用于每一张表都在被考察时,≃𝑑𝑛∪𝑝𝑛 用于只有一个纤程的表在被考察时;这三者是嵌套的而非交叉的,≃ 蕴涵 ≃𝐾,而 ≃𝐾 在每个 𝑆 上蕴涵 ≃𝑆。引理 60 为九条规则一次性地确立第一条蕴涵。
Table 1 is the nine rules of Section 4.2 read as such writes. The accumulator, the committed view, and the remaining iterator are constituents of 𝜃𝑛, so the third column records the writes to them as well, and ℎ there names the inverse the iteration of the fourth column yields, idΓ where L-Divert aborts that iteration. Where a Ψ𝑡 built from an iterator instantiates a fiber (Definition 52), that instantiation carries the writes of the O-Insert row at the name it draws, and an L-Unload whose accumulator retires one carries those of the O-Retire row. Every case analysis below is a lookup in the table, and five lookups recur often enough to name.
表 1 是把 4.2 节的九条规则读作这样的写操作。累加器、已提交视图与剩余迭代器都是 𝜃𝑛 的组成部分,因此第三列也记录了对它们的写入;那里的 ℎ 指第四列那次迭代所产生的逆操作,在 L-Divert 中止那次迭代时取 idΓ。凡是由迭代器构成的 Ψ𝑡 实例化了一个纤程(定义 52)时,该实例化在它所抽出的名字上带有 O-Insert 一行的那些写入;而一个累加器退役了某个纤程的 L-Unload,则带有 O-Retire 一行的那些写入。下面的每个情形分析都是对这张表的一次查表,其中有五次查表反复出现,值得命名。
rule 𝜃𝑡 𝑛 𝜃𝑡+1 𝑛 Ψ𝑡 control fields edited
| rule | 𝜃𝑡𝑛 | 𝜃𝑡+1𝑛 | Ψ𝑡 | control fields edited |
|---|---|---|---|---|
| O-Insert | undefined | 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | idΓ | dom(𝐹𝛾) |
| O-Retire | unconstrained | unchanged | idΓ | 𝜏𝑛 |
| O-Remove | 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | undefined | idΓ | dom(𝐹𝛾) |
| L-Begin | 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑒𝑛, idΓ, 𝜔) | idΓ | 𝜃𝑛 |
| L-Iter | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖′, 𝑔 ∘ ℎ, 𝜔) | pr1 ∘ 𝑖 | 𝜃𝑛 |
| L-Finish | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) | 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔 ∘ ℎ, 𝜔) | pr1 ∘ 𝑖 | 𝜃𝑛 |
| L-Divert | 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑖, 𝑔, 𝜔) | 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔 ∘ ℎ, 𝜔) | idΓ or pr1 ∘ 𝑖 | 𝜃𝑛 |
| L-Leave | 𝖠𝖼𝗍𝗂𝗏𝖾(𝑔, 𝜔) | 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) | idΓ | 𝜃𝑛 |
| L-Unload | 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(𝑔, 𝜔) | 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 | 𝑔 | 𝜃𝑛 |
Table 1 | The rules as writes on the fiber 𝑛 they act on, where step𝑡 is that rule applied at 𝑛.
表 1 | 把各规则读作它们所作用的纤程 𝑛 上的写操作,其中 step𝑡 即该规则作用于 𝑛。
Lemma 59. Reading Table 1 together with Definition 55, for every step 𝑡 and all fibers 𝑚, 𝑛 present at 𝛾𝑡:
引理 59. 结合定义 55 读表 1,对每个步骤 𝑡 以及在 𝛾𝑡 处存在的每个纤程 𝑚、𝑛:
- 1. a table moves only inside a Ψ𝑡: 𝜎𝑡+1 𝑚 ≠ 𝜎𝑡 𝑚 only where step 𝑡 acts on 𝑚, or acts on an 𝑛 ≠ 𝑚 with dom(𝜎𝑡 𝑚) ∩ 𝑑𝑛 ≠ ⌀, in which case the two tables differ in values at keys of 𝑑𝑛 alone and dom(𝜎𝑚) is unchanged;
一张表只在某个 Ψ𝑡 内部变动:𝜎𝑡+1𝑚 ≠ 𝜎𝑡𝑚 仅当步骤 𝑡 作用于 𝑚,或作用于某个 𝑛 ≠ 𝑚 且 dom(𝜎𝑡𝑚) ∩ 𝑑𝑛 ≠ ⌀——在后一种情形下,两张表仅在 𝑑𝑛 中各键上的值不同,而 dom(𝜎𝑚) 不变;
- 2. 𝜔𝑛 comes into existence only where step𝑡 = L-Begin(𝑛) and ceases only where step𝑡 = L-Unload(𝑛), so 𝜔𝑡 𝑛 is constant for 𝑡 in an episode of 𝑛;
𝜔𝑛 仅在 step𝑡 = L-Begin(𝑛) 处进入存在,仅在 step𝑡 = L-Unload(𝑛) 处终止,因此 𝜔𝑡𝑛 对 𝑛 的一个时段内的 𝑡 是常量;
- 3. Ψ𝑡 = 𝑔𝑡 𝑛 only where step𝑡 = L-Unload(𝑛), and no other step applies 𝑔𝑛 to the state; 40
Ψ𝑡 = 𝑔𝑡𝑛 仅当 step𝑡 = L-Unload(𝑛),且没有其他步骤把 𝑔𝑛 应用于状态;
- 4. ¬ installed𝑡 𝑛 ∧ installed𝑡+1 𝑛 ⇒ step𝑡 = L-Begin(𝑛), and installed𝑡 𝑛 ∧ ¬ installed𝑡+1 𝑛 ⇒ step𝑡 = L-Unload(𝑛);
¬ installed𝑡𝑛 ∧ installed𝑡+1𝑛 ⇒ step𝑡 = L-Begin(𝑛),且 installed𝑡𝑛 ∧ ¬ installed𝑡+1𝑛 ⇒ step𝑡 = L-Unload(𝑛);
- 5. 𝜋𝑛, 𝑑𝑛, 𝑝𝑛, and 𝑒𝑛 come into existence with the entry of 𝑛 and are never written again, and 𝜏𝑛 is monotone, written only at ⊤ and only by an O-Retire.
𝜋𝑛、𝑑𝑛、𝑝𝑛 与 𝑒𝑛 随 𝑛 的条目进入存在,此后再不被写入;而 𝜏𝑛 是单调的,只在 ⊤ 处被写,且只由 O-Retire 写。
Proof. Let step 𝑡 apply 𝑟 at 𝑛. By Definition 58 it factors as edit𝑡 ∘ Ψ𝑡, where edit𝑡 writes the fields the fifth column of Table 1 names and nothing else, and Ψ𝑡 is idΓ, an application of one of 𝑛’s iterations, or the accumulator 𝑔𝑡 𝑛, which is a composite of the inverses those iterations yielded. Each of the three is confined to 𝑛 by Lemma 57, so Ψ𝑡 writes no field of a fiber present at 𝛾𝑡 but 𝜎𝑛 and the values other tables hold at keys of 𝑑𝑛, their domains untouched, together with the entry an instantiation adds and the 𝜏 its inverse writes. The two halves therefore partition the writes, and each clause is that partition read at one field. One reading of the second and third columns is used twice: 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 is the one lifecycle state carrying no committed view, L-Begin the one rule leading out of it, and L-Unload the one rule leading into it, while every other row carries the 𝜔 of its premise into its conclusion unchanged.
证明. 设步骤 𝑡 在 𝑛 处应用 𝑟。由定义 58,它分解为 edit𝑡 ∘ Ψ𝑡,其中 edit𝑡 写表 1 第五列所命名的那些字段且只写这些,而 Ψ𝑡 是 idΓ、𝑛 的某次迭代的一个应用,或累加器 𝑔𝑡𝑛——后者是那些迭代所产生的逆操作的复合。三者中的每一个都由引理 57 被限定于 𝑛,因此 Ψ𝑡 不写在 𝛾𝑡 处存在的任何纤程的字段,只写 𝜎𝑛 以及其他表在 𝑑𝑛 各键处所持的值(其论域不受触动),外加一次实例化所添加的条目及其逆操作所写的 𝜏。于是这两半把写操作划分开来,而每一款都是在某一个字段上读出的那个划分。第二、三列的一种读法被用到两次:𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 是唯一不携带已提交视图的生命周期状态,L-Begin 是唯一从它出发的规则,L-Unload 是唯一进入它的规则,而其余每一行都把它前提中的 𝜔 原封不动地带入结论。
(1) An edit𝑡 writes no table, the fifth column naming none, and what a Ψ𝑡 writes outside 𝜎𝑛 is values at keys of 𝑑𝑛 in the tables holding them, the domains unchanged. So 𝜎𝑚 can move only inside a Ψ𝑡, at the acting fiber or at the keys of 𝑑𝑛 its table holds.
(1) edit𝑡 不写任何表——第五列没有命名任何表;而 Ψ𝑡 在 𝜎𝑛 之外所写的,是持有这些键的各表中 𝑑𝑛 各键处的值,论域不变。因此 𝜎𝑚 只能在某个 Ψ𝑡 内部、在起作用的纤程处或在它所持有的 𝑑𝑛 各键处发生变动。
(2) 𝜔𝑛 is a constituent of 𝜃𝑛, which only an edit𝑡 writes and only at the fiber the step acts on, so by the reading above 𝜔𝑛 comes into existence at an L-Begin of 𝑛 and ceases at an L-Unload of 𝑛. An episode of 𝑛 is an interval on which installed𝑛 holds, hence one throughout which 𝜔𝑛 is defined, so neither rule falls in its interior.
(2) 𝜔𝑛 是 𝜃𝑛 的组成部分,而 𝜃𝑛 只有 edit𝑡 才写,且只写在步骤所作用的那个纤程上,于是由上述读法,𝜔𝑛 在 𝑛 的一次 L-Begin 处进入存在,在 𝑛 的一次 L-Unload 处终止。𝑛 的一个时段是 installed𝑛 成立的一个区间,因而也是 𝜔𝑛 有定义的一个区间,所以两条规则都不落在它的内部。
(3) The fourth column, where an accumulator appears at L-Unload alone: the other rules take a forward map pr1 ∘ 𝑖 or idΓ, and no edit𝑡 applies a map to the state at all.
(3) 第四列:累加器只在 L-Unload 处出现;其他规则取的是前向映射 pr1 ∘ 𝑖 或 idΓ,而且没有任何 edit𝑡 会向状态应用一个映射。
(4) installed𝑛 is 𝜃𝑛 ≠ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾, and by the reading above L-Begin and L-Unload are the only rules whose premise and conclusion differ in whether 𝜃𝑛 is 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾. A step acting on some 𝑚 ≠ 𝑛 writes no 𝜃𝑛, and the entry an instantiation adds is at a name not present at 𝛾𝑡.
(4) installed𝑛 即 𝜃𝑛 ≠ 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾,而由上述读法,L-Begin 与 L-Unload 是仅有的两条其前提与结论在 𝜃𝑛 是否为 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 上不同的规则。一个作用于某个 𝑚 ≠ 𝑛 的步骤不写 𝜃𝑛,而一次实例化所添加的条目位于一个在 𝛾𝑡 处并不存在的名字上。
(5) No row of the fifth column names a 𝜋, 𝑑, 𝑝, or 𝑒; those come into existence with the entry O-Insert adds, which its conclusion writes, as does the O-Insert an instantiation takes. Only O- Retire writes a 𝜏, at ⊤, whether taken by the orchestrator or as the inverse of an instantiation (Definition 52); O-Insert sets 𝜏 = ⊥ at a name not already present, so no step returns a 𝜏 to ⊥.□
(5) 第五列没有任何一行命名 𝜋、𝑑、𝑝 或 𝑒;它们随 O-Insert 所添加的条目进入存在,该条目正是它的结论所写的,一次实例化所采取的 O-Insert 也是如此。只有 O-Retire 写一个 𝜏,写在 ⊤ 处,无论是由编排器采取的还是作为一次实例化的逆操作采取的(定义 52);O-Insert 在一个尚未存在的名字上置 𝜏 = ⊥,所以没有步骤会把 𝜏 变回 ⊥。□
Three further lookups say what the rules cannot see. The first is that they read the state only through the observations above, so that the whole calculus descends to Γ/ ≃.
另有三次查表说的是这些规则看不见什么。其一是它们只通过上面的那些观察来读状态,因而整个演算下降到 Γ/ ≃。
Lemma 60. (≃-invariance.) Let 𝛾 ≃ 𝛾′ as read above. Then a rule of Section 4.2 applies at 𝛾 acting on 𝑛 if and only if it applies at 𝛾′ acting on 𝑛, and the states the two applications reach are again related by ≃.
引理 60.(≃-不变性。)设 𝛾 ≃ 𝛾′(按上述读法)。那么 4.2 节的一条规则在 𝛾 处作用于 𝑛 适用,当且仅当它在 𝛾′ 处作用于 𝑛 适用,并且两次应用所到达的状态仍由 ≃ 相关联。
Proof. Every premise of Section 4.2 is of one of four kinds, and each reads a constituent the relation keeps. A premise matching 𝜃𝑛 or 𝜏𝑛 against a pattern, and the premise ∀𝑚. 𝜋𝑚 ≠ 𝑛 of O-Remove, read control fields. The premises (𝑑, 𝑝, 𝑒) ∈ ℭΓ and ∀𝑚. 𝑝 ∩ 𝑝𝑚 = ⌀ of O-Insert read 𝑑, 𝑝, and 𝑒. A premise mentioning target𝑛 or relied𝑛 reads 𝜏𝑛, the committed views inside the 𝜃𝑚, and dom(𝜎𝛾), which Definition 50 computes from the 𝜃𝑚 and the dom(𝜎𝑚), and Definition 33 relates two coeffect contexts only where their domains agree. The remaining premises read dom(𝐹𝛾). Two ≃-related states have ≃-related 𝜎𝛾, the relation comparing every table and the control fields deciding which of them 𝜎𝛾 unions, and no premise reads a value 𝜎𝛾(𝑘) otherwise than up to ≃𝑘, so no premise separates two ≃-related states. 41
证明. 4.2 节的每个前提都属于四类之一,而每一类读的都是该关系所保留的某个组成部分。把 𝜃𝑛 或 𝜏𝑛 与某个模式相匹配的前提,以及 O-Remove 的前提 ∀𝑚. 𝜋𝑚 ≠ 𝑛,读的是控制字段。O-Insert 的前提 (𝑑, 𝑝, 𝑒) ∈ ℭΓ 与 ∀𝑚. 𝑝 ∩ 𝑝𝑚 = ⌀ 读的是 𝑑、𝑝 与 𝑒。提到 target𝑛 或 relied𝑛 的前提读的是 𝜏𝑛、各 𝜃𝑚 内部的已提交视图以及 dom(𝜎𝛾)——后者由定义 50 从各 𝜃𝑚 与各 dom(𝜎𝑚) 算出,而定义 33 只在两个余效应上下文的论域一致时才关联它们。其余前提读的是 dom(𝐹𝛾)。两个 ≃-相关的状态有 ≃-相关的 𝜎𝛾——该关系比较每一张表以及决定 𝜎𝛾 对哪些表取并的控制字段——并且没有哪个前提会以 ≃𝑘 之外的方式读一个值 𝜎𝛾(𝑘),所以没有前提能区分两个 ≃-相关的状态。
For the conclusion, 𝛾𝑡+1 = edit𝑡(Ψ𝑡(𝛾𝑡)) by Definition 58. The values an edit𝑡 assigns are the constituents of the premises it matched, related at the two states by the paragraph above and by the clause 𝑒 ≃ 𝑒 of Definition 37, which relates the triples an iterator yields at related states. And Ψ𝑡 carries ≃-related states to ≃-related states: it is idΓ, an iteration of 𝑒𝑛, or the accumulator inside 𝜃𝑛, and the latter two respect ≃𝑑𝑛∪𝑝𝑛 by Lemma 57, which ≃ implies, while confinement leaves every binding outside the two declarations and every control field as they stand. □
至于结论,由定义 58 有 𝛾𝑡+1 = edit𝑡(Ψ𝑡(𝛾𝑡))。edit𝑡 所指派的那些值,正是它所匹配的各前提的组成部分,它们在这两个状态上由上一段以及定义 37 的 𝑒 ≃ 𝑒 一款而相关联——该款把迭代器在相关状态上所产生的三元组关联起来。并且 Ψ𝑡 把 ≃-相关的状态送到 ≃-相关的状态:它是 idΓ、𝑒𝑛 的一次迭代,或 𝜃𝑛 内部的累加器,而后两者由引理 57 尊重 ≃𝑑𝑛∪𝑝𝑛(≃ 蕴涵它),同时限定把这两个声明之外的每个绑定以及每个控制字段原样留下。□
The names a state carries are read by two of those observations, dom(𝐹𝛾) and the indexing of the control fields, and the rule that draws a name draws any name not already in use (Definition 52). Reading the results below up to ≃ therefore also calls for reading them up to a renaming, which is the discipline of Section 4.1 cashed out.
一个状态所携带的名字由其中两个观察读出,即 dom(𝐹𝛾) 与控制字段的索引,而抽取名字的那条规则抽取的是任何尚未被使用的名字(定义 52)。因此,把下面的结果读到 ≃ 为止,也就要求把它们读到一个重命名为止——这正是 4.1 节那条纪律的兑现。
Lemma 61. (Equivariance.) Let 𝜒 : 𝔑 → 𝔑 be a bijection and let 𝜒 ⋅ 𝛾 be the state carrying the registry 𝐹𝛾 ∘ 𝜒−1, with every name occurring in a 𝜋𝑚 or an 𝜔𝑚 replaced by its image. Then 𝜒 ⋅ 𝛾 is a state, well formed where 𝛾 is, and step𝑡 = 𝑟(𝑛) carries 𝛾𝑡 to 𝛾𝑡+1 if and only if 𝑟(𝜒(𝑛)) carries 𝜒 ⋅ 𝛾𝑡 to 𝜒 ⋅ 𝛾𝑡+1.
引理 61.(等变性。)设 𝜒 : 𝔑 → 𝔑 是一个双射,并设 𝜒 ⋅ 𝛾 是这样一个状态:它携带注册表 𝐹𝛾 ∘ 𝜒−1,且每个出现在某个 𝜋𝑚 或 𝜔𝑚 中的名字都被替换为它的像。那么 𝜒 ⋅ 𝛾 是一个状态,在 𝛾 良构之处它也良构,并且 step𝑡 = 𝑟(𝑛) 把 𝛾𝑡 送到 𝛾𝑡+1,当且仅当 𝑟(𝜒(𝑛)) 把 𝜒 ⋅ 𝛾𝑡 送到 𝜒 ⋅ 𝛾𝑡+1。
Proof. A premise reads a name only by comparing it with another, whether directly, as in the freshness 𝑛 ∉ dom(𝐹𝛾) of O-Insert and the ∀𝑚. 𝜋𝑚 ≠ 𝑛 of O-Remove, or through a table of names, as target𝑛 and relied𝑛 read the 𝜋𝑚 and the 𝜔𝑚. A bijection preserves each such compar- ison. The only names a rule writes are the 𝜋 that O-Insert sets and the 𝜔 that L-Begin sets, both taken from what its premises read, so the writes commute with 𝜒; an effect function writes no name at all, drawing one only through the primitive of Definition 52, which Definition 55 confines to the entry that primitive adds. Well-formedness ( Definition 63) is four conditions comparing names with names. □
证明. 一个前提只通过把一个名字与另一个名字相比较来读名字,无论是直接比较,如 O-Insert 的新鲜性 𝑛 ∉ dom(𝐹𝛾) 与 O-Remove 的 ∀𝑚. 𝜋𝑚 ≠ 𝑛,还是经由一张名字的表来比较,如 target𝑛 与 relied𝑛 读各 𝜋𝑚 与各 𝜔𝑚。双射保持这每一种比较。一条规则所写的名字只有 O-Insert 所置的 𝜋 与 L-Begin 所置的 𝜔,两者都取自其前提所读到的东西,因此这些写入与 𝜒 交换;一个效应函数根本不写任何名字,它只能通过定义 52 的原语抽取一个名字,而定义 55 把该原语限定在它所添加的那个条目上。良构性(定义 63)是四个把名字与名字相比较的条件。□
A sequence and its renaming therefore take the same rules in the same order and reach states differing by 𝜒 alone. Two sequences agreeing save in the names their instantiations draw are accordingly identified, and the results below are read up to the renaming that identifies them.
因此,一个序列与它的重命名采取同样顺序的同样规则,并到达只差一个 𝜒 的状态。于是,两个除了各自实例化所抽取的名字之外都一致的序列被等同看待,而下面的结果是在把二者等同起来的那个重命名之下读出的。
The second lookup is that an entry stripped of everything but its name is invisible to the rules, which is what lets Definition 52 retire a fiber where the state it recovers has none, and Lemma 79 remove the fibers a deleted episode instantiated.
第二次查表说的是,一个被剥去了除名字之外一切的条目对规则是不可见的,这正是使定义 52 得以在它所恢复的状态没有纤程之处退役一个纤程、使引理 79 得以移除一个被删除的时段所实例化的那些纤程的东西。
Lemma 62. (Vestigial entries.) Call 𝑛 vestigial at 𝛾 when 𝜏𝑛 = ⊤, 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾, 𝜎𝑛 = ⌀, and no 𝑚 has 𝜋𝑚 = 𝑛; a vestigial entry satisfies 𝛾 ≃𝐾 𝛾 ∖ 𝑛. If 𝑛 is vestigial at 𝛾 then for every rule and every 𝑚 ≠ 𝑛:
引理 62.(退化条目。)称 𝑛 在 𝛾 处是退化的(vestigial),当 𝜏𝑛 = ⊤、𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾、𝜎𝑛 = ⌀ 且没有 𝑚 使 𝜋𝑚 = 𝑛;一个退化条目满足 𝛾 ≃𝐾 𝛾 ∖ 𝑛。若 𝑛 在 𝛾 处退化,则对每条规则和每个 𝑚 ≠ 𝑛:
- 1. a rule applying at 𝛾 acting on 𝑚 applies at 𝛾 ∖ 𝑛 acting on 𝑚, and the states the two reach differ in the entry at 𝑛 alone, which stays vestigial;
一条在 𝛾 处作用于 𝑚 而适用的规则,在 𝛾 ∖ 𝑛 处作用于 𝑚 也适用,且两者所到达的状态只在 𝑛 处的条目上不同,而该条目保持退化;
- 2. conversely a rule applying at 𝛾 ∖ 𝑛 acting on 𝑚 applies at 𝛾, unless it is an O-Insert drawing the name 𝑛 or claiming a key of 𝑝𝑛.
反之,一条在 𝛾 ∖ 𝑛 处作用于 𝑚 而适用的规则,在 𝛾 处也适用,除非它是一次抽取名字 𝑛 或主张 𝑝𝑛 的某个键的 O-Insert。
Proof. A vestigial 𝑛 contributes to no observation a premise of a rule acting on 𝑚 ≠ 𝑛 reads. It is not 𝖠𝖼𝗍𝗂𝗏𝖾, so 𝜎𝑛 enters no 𝜎𝛾 and 𝑛 is the provider of no key, leaving 𝛾 ⊧ 𝑑𝑚 and target𝑚 unmoved; installed𝑛 fails, so 𝑛 contributes no disjunct to relied𝑚; no 𝜋𝑚′ names 𝑛, so the premise ∀𝑚′. 𝜋𝑚′ ≠ 𝑚 of an O-Remove of 𝑚 is unmoved; and 𝜃𝑛, 𝜏𝑛, and 𝜋𝑛 are read by rules acting on 𝑛 alone. The two premises clause (2) excepts are the two the removal relaxes, an absent name being fresh and an absent provision meeting every other. By Lemma 59 no rule acting on 𝑚 ≠ 𝑛 writes a field of 𝑛 save values at keys of 𝑑𝑚 that 𝜎𝑛 holds, of which the empty 𝜎𝑛 holds none, so the entry survives vestigial. □ 42
证明. 一个退化的 𝑛 对任何作用于 𝑚 ≠ 𝑛 的规则的前提所读的任何观察都没有贡献。它不是 𝖠𝖼𝗍𝗂𝗏𝖾,所以 𝜎𝑛 不进入 𝜎𝛾,且 𝑛 不是任何键的提供者,于是 𝛾 ⊧ 𝑑𝑚 与 target𝑚 都不受触动;installed𝑛 不成立,所以 𝑛 不给 relied𝑚 贡献任何析取支;没有 𝜋𝑚′ 指名 𝑛,所以一次 𝑚 的 O-Remove 的前提 ∀𝑚′. 𝜋𝑚′ ≠ 𝑚 不受触动;而 𝜃𝑛、𝜏𝑛 与 𝜋𝑛 只被作用于 𝑛 的规则读取。第 (2) 款所排除的两个前提,正是移除会放宽的那两个:一个不存在的名字是新鲜的,一个不存在的提供与任何其他提供都不相交。由引理 59,没有作用于 𝑚 ≠ 𝑛 的规则会写 𝑛 的字段,除了 𝜎𝑛 所持有的 𝑑𝑚 各键处的值,而空的 𝜎𝑛 一个也不持有,因此该条目以退化状态存活下来。□
4.3.1. 保持性
4.3.1. Preservation
Definition 50 fixes the shape of a registry, and the rules have to be checked against it before the results below can add to it. This subsection identifies the invariant the rules preserve, of which the first clause is that shape and the rest what those results assume.
定义 50 固定了注册表的形状,在下述结论能够在此基础上有所增添之前,各规则必须先对照它加以检验。本小节确定各规则所保持的不变式,其中第一条即为该形状,其余各条则是那些结论所假设的内容。
Definition 63. A registry 𝐹𝛾 is well formed when, for all 𝑚, 𝑛 ∈ dom(𝐹𝛾) and all 𝑘 ∈ 𝐾,
定义 63. 称注册表 𝐹𝛾 是良构的,当对所有 𝑚, 𝑛 ∈ dom(𝐹𝛾) 与所有 𝑘 ∈ 𝐾:
- 1. 𝜋𝑛 ∈ dom(𝐹𝛾) ∪ {𝗋𝗈𝗈𝗍};
𝜋𝑛 ∈ dom(𝐹𝛾) ∪ {𝗋𝗈𝗈𝗍};
- 2. 𝑚 ≠ 𝑛 ⇒ 𝑝𝑚 ∩ 𝑝𝑛 = ⌀;
𝑚 ≠ 𝑛 ⇒ 𝑝𝑚 ∩ 𝑝𝑛 = ⌀;
- 3. installed𝑛(𝛾) ⇒ 𝜔𝑛 is total on 𝑑𝑛 and valued in dom(𝐹𝛾);
installed𝑛(𝛾) ⇒ 𝜔𝑛 在 𝑑𝑛 上是全函数且取值于 dom(𝐹𝛾);
- 4. installed𝑛(𝛾) ∧ 𝑘 ∈ 𝑑𝑛 ∧ 𝜔𝑛(𝑘) = 𝑚 ⇒ installed𝑚(𝛾).
installed𝑛(𝛾) ∧ 𝑘 ∈ 𝑑𝑛 ∧ 𝜔𝑛(𝑘) = 𝑚 ⇒ installed𝑚(𝛾)。
Clause (1) is the tree of Definition 50 read one edge at a time, keeping a parent pointer landing in the registry. The acyclicity that definition also requires needs no clause, since the fiber a pointer names is introduced before the fiber naming it.
第 (1) 条就是把定义 50 的那棵树一次读一条边,并保持父指针落在注册表之内。该定义同时要求的无环性无需单列条款,因为指针所指名的纤程必在指名它的纤程之前被引入。
Theorem 64. (Preservation.) If 𝐹 𝑡 is well formed then so is 𝐹 𝑡+1, whichever rule step 𝑡 applies. Each clause is established at 𝛾𝑡+1 from all four at 𝛾𝑡.
定理 64.(保持性。)若 𝐹 𝑡 良构,则 𝐹 𝑡+1 亦良构,无论第 𝑡 步应用哪一条规则。每一条条款都由 𝛾𝑡 处的全部四条在 𝛾𝑡+1 处得到确立。
Proof. Let step 𝑡 act on 𝑛.
证明. 设第 𝑡 步作用于 𝑛。
(1) By Table 1 only O-Insert and O-Remove write a 𝜋 or dom(𝐹𝛾). O-Insert has 𝜋𝑛 ∈ dom(𝐹 𝑡) ∪ {𝗋𝗈𝗈𝗍} as a premise, which is the clause for the fiber it adds, and it leaves every other 𝜋 alone while enlarging dom(𝐹𝛾). O-Remove has ∀𝑚. 𝜋𝑚 ≠ 𝑛, so no surviving 𝜋𝑚 names the fiber it takes away.
(1) 由表 1,只有 O-Insert 与 O-Remove 写 𝜋 或 dom(𝐹𝛾)。O-Insert 以 𝜋𝑛 ∈ dom(𝐹 𝑡) ∪ {𝗋𝗈𝗈𝗍} 为前提,这正是它所添加纤程的那条条款,并且它在扩大 dom(𝐹𝛾) 的同时不动任何其他 𝜋。O-Remove 以 ∀𝑚. 𝜋𝑚 ≠ 𝑛 为前提,故存活下来的 𝜋𝑚 都不指名它所取走的那个纤程。
(2) The last premise of O-Insert is ∀𝑚. 𝑝𝑛 ∩ 𝑝𝑚 = ⌀, which is the clause for the fiber it adds, and by Table 1 no other rule writes a 𝑝 or enlarges dom(𝐹𝛾). Two consequences are used below: dom(𝜎𝑚) ⊆ 𝑝𝑚 by Definition 48, so distinct tables are disjoint and 𝜎𝛾 is a function; and 𝑘 ∈ 𝑝𝑚 ∩ 𝑝𝑚′ forces 𝑚 = 𝑚′, so 𝑘 has at most one possible provider.
(2) O-Insert 的最后一条前提是 ∀𝑚. 𝑝𝑛 ∩ 𝑝𝑚 = ⌀,即它所添加纤程的那条条款;而由表 1,没有其他规则写 𝑝,也没有其他规则扩大 dom(𝐹𝛾)。下面用到两个推论:由定义 48 有 dom(𝜎𝑚) ⊆ 𝑝𝑚,故不同的表两两不交,𝜎𝛾 是一个函数;而 𝑘 ∈ 𝑝𝑚 ∩ 𝑝𝑚′ 必迫使 𝑚 = 𝑚′,故 𝑘 至多有一个可能的提供者。
(3) By Lemma 59(2) the only rule that writes an 𝜔𝑛 is L-Begin, whose premise 𝜔 = target𝑡 𝑛 ≠ ⊥ makes it total on 𝑑𝑛 and valued in dom(𝐹 𝑡), target naming providers. By Table 1 the only rule that shrinks dom(𝐹𝛾) is O-Remove, whose premise 𝜃𝑡 𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 gives ¬ installed𝑡 𝑛, whence by clause (4) at 𝛾𝑡 no 𝑚 has 𝜔𝑡 𝑚(𝑘) = 𝑛 for a 𝑘 ∈ 𝑑𝑚 while installed𝑡 𝑚; and 𝑛 itself carries no 𝜔.
(3) 由引理 59(2),唯一写 𝜔𝑛 的规则是 L-Begin,其前提 𝜔 = target𝑡 𝑛 ≠ ⊥ 使 𝜔𝑛 在 𝑑𝑛 上是全函数且取值于 dom(𝐹 𝑡),而 target 所指名者即提供者。由表 1,唯一收缩 dom(𝐹𝛾) 的规则是 O-Remove,其前提 𝜃𝑡 𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 给出 ¬ installed𝑡 𝑛,于是由 𝛾𝑡 处的第 (4) 条,没有 𝑚 能在 installed𝑡 𝑚 成立时对某个 𝑘 ∈ 𝑑𝑚 满足 𝜔𝑡 𝑚(𝑘) = 𝑛;且 𝑛 自身不带 𝜔。
(4) By Lemma 59(2) and (4) the clause can fail at 𝛾𝑡+1 only where some installed has fallen, some 𝜔 has been written, or a fiber some 𝜔 names has left dom(𝐹𝛾). The last is an O-Remove, whose removed fiber is not installed and hence, by clause (4) at 𝛾𝑡, is named by no 𝜔𝑡 𝑚 of an installed 𝑚. The first is an L-Unload of 𝑛, whose premise ¬ relied𝑡 𝑛 reads ∀𝑚 ≠ 𝑛, 𝑘 ∈ 𝑑𝑚. installed𝑡 𝑚 ⇒ 𝜔𝑡 𝑚(𝑘) ≠ 𝑛 and which writes no 𝜔𝑚 for 𝑚 ≠ 𝑛 and leaves ¬ installed𝑡+1 𝑛 , so the clause holds of 𝑛 as well. The second is an L-Begin of 𝑛, writing target𝑡 𝑛, whose values are the providers of the keys of 𝑑𝑛 and hence 𝖠𝖼𝗍𝗂𝗏𝖾 at 𝛾𝑡; the step alters no other fiber’s 𝜃, so they are installed at 𝛾𝑡+1 too. □
(4) 由引理 59(2) 与 (4),该条款在 𝛾𝑡+1 处只可能在三种情形下失效:某个 installed 已经倒下、某个 𝜔 已被写入、或某个 𝜔 所指名的纤程已离开 dom(𝐹𝛾)。最后一种是一次 O-Remove,其被删去的纤程不是 installed,因而由 𝛾𝑡 处的第 (4) 条,它不被任何 installed 的 𝑚 的 𝜔𝑡 𝑚 所指名。第一种是 𝑛 的一次 L-Unload,其前提 ¬ relied𝑡 𝑛 读作
∀𝑚 ≠ 𝑛, 𝑘 ∈ 𝑑𝑚. installed𝑡 𝑚 ⇒ 𝜔𝑡 𝑚(𝑘) ≠ 𝑛
而它对 𝑚 ≠ 𝑛 不写任何 𝜔𝑚,并留下 ¬ installed𝑡+1 𝑛,故该条款对 𝑛 本身也成立。第二种是 𝑛 的一次 L-Begin,写入 target𝑡 𝑛,其取值即 𝑑𝑛 各键的提供者,因而在 𝛾𝑡 处是 𝖠𝖼𝗍𝗂𝗏𝖾;这一步不改变任何其他纤程的 𝜃,故它们在 𝛾𝑡+1 处也是 installed。□
The guard on L-Unload is what carries clauses (3) and (4). The premise ∀𝑚. 𝜋𝑚 ≠ 𝑛 of O- Remove speaks only of parent pointers; what keeps a committed view from naming a removed fiber is the guard, imposed several steps earlier and for a different reason. Two things follow. A name freed by O-Remove may be reissued by O-Insert, since no stale committed view can name 43 it; and a fiber may be removed as soon as it is 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾, without a separate check that nobody depends on it.
L-Unload 上的守卫正是承载第 (3)、(4) 两条的东西。O-Remove 的前提 ∀𝑚. 𝜋𝑚 ≠ 𝑛 只涉及父指针;真正阻止一个已提交视图指名某个被删纤程的,是那个守卫——它在若干步之前就已施加,且出于另一重理由。由此得到两件事。被 O-Remove 释放的名字可以由 O-Insert 重新发放,因为没有任何过期的已提交视图会指名它;而一个纤程一旦成为 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 便可立即删除,无需另设检查去确认无人依赖它。
4.3.2. 时间可组合性
4.3.2. Temporal Composability
Local temporal composability reverts one sequence of effects with one accumulator ( Sec- tion 3.1.2). The registry holds one accumulator per fiber and the fibers interleave: between the moment 𝑛 composes an inverse onto 𝑔𝑛 and the moment 𝑔𝑛 runs, other fibers have moved the state. Whether 𝑔𝑛 still undoes what it was built to undo there is what the global form of the guarantee asserts, and the condition it turns on is that the intervening steps commute with 𝑔𝑛.
局部时间可组合性用一个累加器撤销一个效应序列(第 3.1.2 节)。注册表为每个纤程各持一个累加器,而各纤程相互交错:在 𝑛 把一个逆操作复合到 𝑔𝑛 上的那一刻与 𝑔𝑛 运行的那一刻之间,其他纤程已经挪动了状态。𝑔𝑛 在那里是否仍能撤销它被构造出来所要撤销的东西,正是该保证的全局形式所断言的内容,而它所倚靠的条件是:其间插入的那些步骤与 𝑔𝑛 可交换。
Definition 65. Two iterators 𝑖, 𝑗 over Γ are independent when they are so in the sense of Definition 42, reading ≃ on maps, triples, and continuations as Definition 34 does, and an instantiating iteration (Definition 52) as agreement of the component it names. Fibers 𝑚 and 𝑛 are entangled when one’s provision meets the other’s declarations, 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) ≠ ⌀ or 𝑝𝑛 ∩ (𝑑𝑚 ∪ 𝑝𝑚) ≠ ⌀. A sequence of steps is pairwise independent when for every two names 𝑚 ≠ 𝑛 it ever holds — one for each fiber the orchestrator inserts and each fiber an iteration instantiates — either 𝑒𝑚 and 𝑒𝑛 are independent, or 𝑚 and 𝑛 are entangled and every key at which operations of both occur is commutative (Definition 44).
定义 65. 称 Γ 上的两个迭代器 𝑖、𝑗 是独立的,当它们在定义 42 的意义上独立,其中映射、三元组与延续上的 ≃ 按定义 34 的读法理解,而一次实例化迭代(定义 52)理解为它所指名那个组件的一致。称纤程 𝑚 与 𝑛 相互纠缠,当一方的提供与另一方的声明相交,即 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) ≠ ⌀ 或 𝑝𝑛 ∩ (𝑑𝑚 ∪ 𝑝𝑚) ≠ ⌀。称一个步骤序列是两两独立的,当对它涉及的每两个名字 𝑚 ≠ 𝑛 —— 编排器所插入的每个纤程、以及每次迭代所实例化的每个纤程各对应一个 —— 或者 𝑒𝑚 与 𝑒𝑛 独立,或者 𝑚 与 𝑛 相互纠缠且两者操作共同出现的每个键都是可交换的(定义 44)。
Independence in this sense is what trace theory takes as primitive: commuting actions gen- erate an equivalence on sequences under which reordering two adjacent independent actions preserves the endpoint [46], and Lemma 78 is that reordering for these rules. Quantifying over names rather than iterators is what keeps two fibers of one component in scope: such a pair requires that component’s effect function to be independent of itself, which is to require that 𝔐(𝑖) be commutative. Clause (1) of Definition 42 is what Theorem 68 uses and clause (2) what Theorem 80 needs in addition: reordering the steps of two fibers evaluates an iterator at a state the other fiber moved, and commuting the maps does not by itself say that the iterator yields the same inverse and the same continuation there. Checking clause (1) calls for no more than the iterations themselves, since Lemma 41(1) carries commutation from the generators to the monoids they generate.
这种意义上的独立正是迹理论当作初始概念的东西:可交换的动作生成一个序列上的等价关系,在该关系下重排两个相邻的独立动作保持终点不变 [46],而引理 78 就是针对这些规则的那种重排。对名字而非对迭代器作量化,是把同一组件的两个纤程保持在视野之内的关键:这样一对纤程要求该组件的效应函数与自身独立,也就是要求 𝔐(𝑖) 可交换。定义 42 的第 (1) 条是定理 68 所用到的,第 (2) 条则是定理 80 另外需要的:重排两个纤程的步骤,会在被另一纤程挪动过的状态上求值一个迭代器,而映射的可交换性本身并不说明迭代器在那里会产生同一个逆操作与同一个延续。检验第 (1) 条所需的不过是各次迭代自身,因为引理 41(1) 把交换性从生成元带到它们所生成的幺半群上。
The paradigm supplies both disjuncts:
范式同时供给两个析取支:
Lemma 66. (Pairwise independence.) Every sequence of steps is pairwise independent.
引理 66.(两两独立性。)每个步骤序列都是两两独立的。
Proof. Every key is commutative, its coeffect carrying the proof as its witness ( Definition 46), which settles the second disjunct at an entangled pair. A pair that is not entangled has each member’s provision outside the other’s every key, which is the hypothesis 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀ of Theorem 47 read at the underlying members of Definition 30, so those members are independent, the commutativity of every shared operation key again supplied by the witness; independence transfers along the lift of Definition 56, whose transformations move the tables as the stages move the projection, an instantiating iteration adding an entry no table map reads. Two fibers of one component fall under the same two cases, Theorem 47 holding at 𝑖1 = 𝑖2 as well. □
证明. 每个键都是可交换的,它的余效应带着该证明作为其见证(定义 46),这就在相互纠缠的纤程对上确立了第二个析取支。不相互纠缠的一对,其每一方的提供都落在对方所有键之外,这正是定理 47 的前提 𝑃1 ∩ 𝑆2 = 𝑃2 ∩ 𝑆1 = ⌀ 在定义 30 的底层成员上读出的样子,故那些成员独立,其中每个共享操作键的可交换性同样由见证提供;独立性沿定义 56 的提升转移,该提升的变换像各阶段移动投影那样移动各张表,而一次实例化迭代只添加一个没有表映射会读的条目。同一组件的两个纤程同样落在上述两种情形之内,因为定理 47 在 𝑖1 = 𝑖2 时也成立。□
Entangled fibers are the pairs independence cannot cover, and could not be expected to: a consumer’s operation acts on the very value the provider’s extension installs, so the two orders differ at every state the binding is absent from, whichever equivalence the difference is read up to. What stands in for independence there is the rules themselves, which never interleave the 44 two maps in the order that separates them. Throughout the argument, a lift applied where its precondition fails produces no transition, per the convention of Section 3.2.1, so a map meeting a state its key has left is read as the identity.
相互纠缠的纤程正是独立性所不能覆盖、也不该指望它覆盖的那些对:消费方的操作所作用的,恰恰是提供方的扩展所安装的那个值本身,因此在绑定缺失的每个状态上两种顺序都会不同,无论这一差异是按哪种等价关系读出的。在那里代替独立性出场的是规则自身,它们从不以会把两个映射分开的那个顺序交错二者。在整个论证中,一个提升若被施加在其前提不成立之处则不产生转移(依第 3.2.1 节的约定),故一个映射遇到其键已经离开的状态时被读作恒等。
Lemma 67. (Entangled steps.) Let an episode of 𝑛 open at 𝑏, and let step 𝑡 ≥ 𝑏 in the episode act on an 𝑚 ≠ 𝑛 entangled with 𝑛. Then
引理 67.(纠缠步骤。)设 𝑛 的一个回合于 𝑏 开启,并设该回合中的第 𝑡 ≥ 𝑏 步作用于某个与 𝑛 相互纠缠的 𝑚 ≠ 𝑛。则
- 1. where 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) ≠ ⌀, Ψ𝑡 = idΓ;
在 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) ≠ ⌀ 之处,Ψ𝑡 = idΓ;
- 2. otherwise 𝑔𝑡 𝑛(Ψ𝑡(𝛾𝑡)) ≃𝐾 Ψ𝑡(𝑔𝑡 𝑛(𝛾𝑡));
否则 𝑔𝑡 𝑛(Ψ𝑡(𝛾𝑡)) ≃𝐾 Ψ𝑡(𝑔𝑡 𝑛(𝛾𝑡));
- 3. where moreover 𝜃𝑡 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −), Ψ𝑡 = idΓ in either case.
此外当 𝜃𝑡 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −) 时,两种情形下都有 Ψ𝑡 = idΓ。
Proof. (1) A key of 𝑝𝑚 ∩ 𝑝𝑛 would put two registered provisions in conflict with the premise of O-Insert, so some 𝑘 ∈ 𝑝𝑚 ∩ 𝑑𝑛, and 𝑚 is the one registered fiber whose provision carries 𝑘. The premise of 𝑛’s L-Begin at 𝑏 − 1 resolves 𝑘 to an 𝖠𝖼𝗍𝗂𝗏𝖾 provider, so 𝜔𝑏 𝑛(𝑘) = 𝑚 and 𝜃𝑏−1 𝑚 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −); 𝜔𝑛 holds 𝑚 for as long as the episode is open ( Lemma 59(2)) while installed𝑛 holds, which is relied𝑚(𝛾𝑡) at every such 𝑡. The guard therefore blocks every L-Unload of 𝑚 there, so 𝑚 never reaches 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾, and a fiber standing at 𝖠𝖼𝗍𝗂𝗏𝖾 or 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 is acted on only by L-Leave, O-Retire, and the blocked L-Unload, of which the first two have Ψ𝑡 = idΓ (Table 1).
证明. (1) 𝑝𝑚 ∩ 𝑝𝑛 中的键会使两个已注册的提供与 O-Insert 的前提相冲突,故存在 𝑘 ∈ 𝑝𝑚 ∩ 𝑑𝑛,且 𝑚 是唯一其提供带有 𝑘 的已注册纤程。𝑛 在 𝑏 − 1 处的 L-Begin,其前提把 𝑘 解析为一个 𝖠𝖼𝗍𝗂𝗏𝖾 提供者,故 𝜔𝑏 𝑛(𝑘) = 𝑚 且 𝜃𝑏−1 𝑚 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −);只要回合是开的(引理 59(2))且 installed𝑛 成立,𝜔𝑛 就一直持有 𝑚,这在每个这样的 𝑡 处就是 relied𝑚(𝛾𝑡)。因此守卫在那里阻断 𝑚 的每一次 L-Unload,故 𝑚 永远到不了 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾;而停在 𝖠𝖼𝗍𝗂𝗏𝖾 或 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的纤程只被 L-Leave、O-Retire 以及那个被阻断的 L-Unload 所作用,其中前两者的 Ψ𝑡 = idΓ(表 1)。
(2) Here 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) = ⌀ and some 𝑘 ∈ 𝑝𝑛 ∩ 𝑑𝑚. Ψ𝑡 is one of 𝑚’s iterations or the accumula- tor 𝑔𝑡 𝑚, in either case a composite of the maps Definition 56 admits for 𝑚: lifts of operation maps, forward or inverse, at keys of 𝑑𝑚 ∪ 𝑝𝑚, extensions and restrictions at keys of 𝑝𝑚, instantiations, and the O-Retires those yield. The constituents of 𝑔𝑡 𝑛 are the inverses Definition 56 admits for 𝑛: lifts of operation inverses at keys of 𝑑𝑛 ∪ 𝑝𝑛, restrictions at keys of 𝑝𝑛, and O-Retires. Commute the constituents of Ψ𝑡 past those of 𝑔𝑡 𝑛 one pair at a time. A pair at distinct keys is a pair of key- local maps and commutes; an instantiation or an O-Retire writes a fresh entry or a control field, which no table map reads, and commutes with every constituent in sight; a pair of operation maps at one shared key is covered by that key’s commutativity, which the witness of its coeffect supplies (Definition 46), read at the tables through the lift of Definition 56. What remains is an operation map of 𝑚 at a key 𝑘 ∈ 𝑝𝑛 ∩ 𝑑𝑚 against the extension or restriction of 𝑛 at 𝑘. Such a map exists in Ψ𝑡 only under a committed view of 𝑚 resolving 𝑘 (Lemma 59(2)), whose L- Begin required an 𝖠𝖼𝗍𝗂𝗏𝖾 provider of 𝑘; the commitment pins that provider for as long as 𝑚 is installed, by the argument of (1) read at 𝑚, and two registered provisions cannot share 𝑘, so the provider is 𝑛 and 𝑛 was 𝖠𝖼𝗍𝗂𝗏𝖾 within the open episode. Its transition had therefore finished by 𝑡, and 𝑔𝑡 𝑛 carries the restriction at 𝑘 the extension 𝑚 resolved yielded, composed to the left of 𝑛’s operation inverses at 𝑘 by the LIFO order of Definition 18. On one side the operation map of 𝑚 commutes leftward, past constituents at other keys and, by commutativity of 𝑘, past 𝑛’s operation inverses at 𝑘, until the restriction absorbs it, a write to the value at 𝑘 followed by the removal of 𝑘 being the removal alone; on the other side it meets a state 𝑔𝑡 𝑛 has removed 𝑘 from and produces no transition. Both composites therefore agree at 𝛾𝑡.
(2) 此处 𝑝𝑚 ∩ (𝑑𝑛 ∪ 𝑝𝑛) = ⌀,且存在 𝑘 ∈ 𝑝𝑛 ∩ 𝑑𝑚。Ψ𝑡 或是 𝑚 的某次迭代,或是累加器 𝑔𝑡 𝑚,两种情形都是定义 56 为 𝑚 所容许的那些映射的复合:在 𝑑𝑚 ∪ 𝑝𝑚 的键上操作映射的提升(正向或逆向)、在 𝑝𝑚 的键上的扩展与限制、实例化,以及由它们产生的那些 O-Retire。𝑔𝑡 𝑛 的各成分则是定义 56 为 𝑛 所容许的那些逆操作:在 𝑑𝑛 ∪ 𝑝𝑛 的键上操作逆映射的提升、在 𝑝𝑛 的键上的限制,以及 O-Retire。把 Ψ𝑡 的各成分逐一与 𝑔𝑡 𝑛 的各成分交换。位于不同键上的一对是键局部的映射对,可交换;一次实例化或一次 O-Retire 写一个新条目或一个控制字段,没有表映射会读它,故与视野内的每个成分都可交换;位于同一个共享键上的一对操作映射,由该键的可交换性覆盖,而这一可交换性由其余效应的见证提供(定义 46),并经定义 56 的提升在各张表上读出。剩下的是 𝑚 在某个键 𝑘 ∈ 𝑝𝑛 ∩ 𝑑𝑚 处的操作映射,与 𝑛 在 𝑘 处的扩展或限制相对。这样的映射只在 𝑚 的某个已提交视图解析了 𝑘 时才存在于 Ψ𝑡 之中(引理 59(2)),而该视图的 L-Begin 要求 𝑘 有一个 𝖠𝖼𝗍𝗂𝗏𝖾 提供者;按 (1) 在 𝑚 处读出的论证,只要 𝑚 是 installed,这个提交就把该提供者钉住,而两个已注册的提供不能共享 𝑘,故该提供者就是 𝑛,且 𝑛 在这个开着的回合内曾经是 𝖠𝖼𝗍𝗂𝗏𝖾。它的转移因此到 𝑡 时已经完成,而 𝑔𝑡 𝑛 带着 𝑚 所解析出的那个扩展所产生的 𝑘 处的限制,按定义 18 的 LIFO 序复合在 𝑛 在 𝑘 处的各操作逆映射的左侧。一侧上,𝑚 的操作映射向左交换,越过其他键上的各成分,并借 𝑘 的可交换性越过 𝑛 在 𝑘 处的各操作逆映射,直到被那个限制吸收——先写 𝑘 处的值再移除 𝑘,结果就是只移除 𝑘;另一侧上,它遇到的是 𝑔𝑡 𝑛 已把 𝑘 从中移除的状态,因而不产生转移。故两个复合在 𝛾𝑡 处一致。
(3) The provider case is (1). In the consumer case, a consumer of 𝑛 is not installed while 𝑛 is 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀: its commitment resolving a key to 𝑛 would have held relied𝑛 and blocked the L- Unload closing 𝑛’s previous episode, and a new commitment requires an 𝖠𝖼𝗍𝗂𝗏𝖾 provider. An uninstalled fiber is acted on only by L-Begin and the orchestration rules; the orchestration rules have Ψ𝑡 = idΓ, and L-Begin is inapplicable, the key 𝑛 provides lying outside dom(𝜎𝛾𝑡) and the target of a fiber declaring it therefore ⊥. □
(3) 提供者情形即 (1)。在消费方情形,当 𝑛 为 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 时,𝑛 的消费方不是 installed:它把某个键解析到 𝑛 的那个提交本会维持 relied𝑛 并阻断关闭 𝑛 上一回合的那次 L-Unload,而一个新的提交又要求一个 𝖠𝖼𝗍𝗂𝗏𝖾 提供者。未安装的纤程只被 L-Begin 与编排规则所作用;编排规则的 Ψ𝑡 = idΓ,而 L-Begin 又不适用,因为 𝑛 所提供的键落在 dom(𝜎𝛾𝑡) 之外,声明该键的纤程的 target 因而为 ⊥。□
With every pair so covered, the single-accumulator invariant of Theorem 7 survives the interleaving, in the form that gives temporal composability its content: running an inverse withdraws the fiber’s contribution and nothing else. 45
每一对纤程都如此被覆盖之后,定理 7 的单累加器不变式便在交错之下存活下来,其形式正是赋予时间可组合性以内容的那种形式:运行一个逆操作,撤销该纤程自身的贡献,此外不撤销任何别的东西。
Theorem 68. (Recovery exactness.) Let an episode of 𝑛 open at 𝑏, let 𝑢 ≥ 𝑏 lie in it, and let 𝑡1 < ⋯ < 𝑡𝑙 be the indices in [𝑏, 𝑢) at which the acting fiber is not 𝑛. Then
定理 68.(恢复精确性。)设 𝑛 的一个回合于 𝑏 开启,𝑢 ≥ 𝑏 落在该回合内,并设 𝑡1 < ⋯ < 𝑡𝑙 为 [𝑏, 𝑢) 中作用纤程不是 𝑛 的那些下标。则
𝑔𝑢 𝑛(𝛾𝑢) ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏) (55)
𝑔𝑢 𝑛(𝛾𝑢) ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏) (55)
That is, applying 𝑛’s accumulator at 𝛾𝑢 leaves every fiber’s table where those same steps would have left it from 𝛾𝑏, the control fields lying outside the comparison. Reading the right side as the state reached had 𝑛 never begun assumes that no fiber 𝑛 instantiates take a step in [𝑏, 𝑢), since a fiber 𝑛 instantiates is one that would not be there to take it.
也就是说,在 𝛾𝑢 处应用 𝑛 的累加器,把每个纤程的表留在与那些同样的步骤从 𝛾𝑏 出发所会留下的相同之处,控制字段不在比较之列。把右端读作"若 𝑛 从不曾开始"所到达的状态,需要假设 𝑛 所实例化的纤程不在 [𝑏, 𝑢) 中迈步,因为 𝑛 所实例化的纤程,正是若 𝑛 不开始便不会在那里、因而无从迈步的纤程。
Proof. By induction on 𝑢, over the indices 𝑢 with 𝑢 + 1 in the episode. At 𝑢 = 𝑏 the step at 𝑏 − 1 is an L-Begin, the episode opening by Definition 58, so 𝑔𝑏 𝑛 = idΓ by Table 1, the index set is empty, and the claim is 𝛾𝑏 ≃𝐾 𝛾𝑏. Two facts are used at each step. An edit𝑡 writes control fields alone, and the two rules that write dom(𝐹𝛾) leave the tables as they stand, an O-Insert adding an entry with an empty table and an O-Remove taking one away by its premise, so
证明. 对 𝑢 归纳,取那些 𝑢 + 1 仍在回合内的下标 𝑢。当 𝑢 = 𝑏 时,第 𝑏 − 1 步是一次 L-Begin(回合按定义 58 由此开启),故由表 1 有 𝑔𝑏 𝑛 = idΓ,下标集为空,而断言即 𝛾𝑏 ≃𝐾 𝛾𝑏。每一步用到两个事实。edit𝑡 只写控制字段,而写 dom(𝐹𝛾) 的那两条规则让各张表保持原样——O-Insert 添加一个带空表的条目,O-Remove 依其前提取走一个条目——故
𝛾𝑡+1 ≃𝐾 Ψ𝑡(𝛾𝑡)
𝛾𝑡+1 ≃𝐾 Ψ𝑡(𝛾𝑡)
and, for every fiber 𝑚, every map in 𝔐(𝑒𝑚) carries ≃𝐾-related states to ≃𝐾-related states, since ≃𝐾 implies ≃𝑑𝑚∪𝑝𝑚 , at which Lemma 57 makes such a map respect the relation, and since confinement leaves it moving no binding outside the two declarations, so that the keys outside the interface stay as related as it found them; an instantiation adds an empty entry by Definition 52.
且对每个纤程 𝑚,𝔐(𝑒𝑚) 中的每个映射都把 ≃𝐾 相关的状态映到 ≃𝐾 相关的状态,因为 ≃𝐾 蕴含 ≃𝑑𝑚∪𝑝𝑚,在其上引理 57 使这样的映射尊重该关系,又因为限定使它不挪动那两个声明之外的任何绑定,故接口之外的键保持它所发现的那般相关;一次实例化按定义 52 添加一个空条目。
Let step 𝑢 act on 𝑛. Since the episode is open at 𝑢 and 𝑢 + 1, Lemma 59(4) excludes an L-Begin and an L-Unload of 𝑛, and O-Insert and O-Remove read a 𝜃𝑛 that installed𝑢 𝑛 denies, leaving two cases. Where the rule is L-Iter, L-Finish, or a landing L-Divert, Table 1 gives Ψ𝑢 = pr1 ∘ 𝑖𝑢 𝑛 and 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛 ∘ ℎ for the inverse ℎ that iteration yields. The witness condition of Definition 37 reads ℎ(Ψ𝑢(𝛾𝑢)) ≃𝑑𝑛∪𝑝𝑛 𝛾𝑢, which is ≃𝐾 once Lemma 57 adds that neither map moves a binding outside the two declarations, and the instantiating iteration is the case where the two states differ in an entry with an empty table that ≃𝐾 does not compare. Since 𝑔𝑢 𝑛 carries ≃𝐾 by the paragraph above,
设第 𝑢 步作用于 𝑛。由于回合在 𝑢 与 𝑢 + 1 处都是开的,引理 59(4) 排除了 𝑛 的 L-Begin 与 L-Unload,而 O-Insert 与 O-Remove 读到的 𝜃𝑛 为 installed𝑢 𝑛 所否定,于是只剩两种情形。当规则是 L-Iter、L-Finish 或一次落地的 L-Divert 时,表 1 给出 Ψ𝑢 = pr1 ∘ 𝑖𝑢 𝑛 与 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛 ∘ ℎ,其中 ℎ 是该次迭代所产生的逆操作。定义 37 的见证条件读作 ℎ(Ψ𝑢(𝛾𝑢)) ≃𝑑𝑛∪𝑝𝑛 𝛾𝑢,一旦引理 57 补上"两个映射都不挪动那两个声明之外的绑定",这便是 ≃𝐾;而实例化迭代是那样一种情形:两个状态在一个带空表的条目上不同,而 ≃𝐾 并不比较它。由于由上段知 𝑔𝑢 𝑛 携带 ≃𝐾,
𝑔𝑢+1 𝑛 (𝛾𝑢+1) ≃𝐾 (𝑔𝑢 𝑛 ∘ ℎ)(Ψ𝑢(𝛾𝑢)) ≃𝐾 𝑔𝑢 𝑛(𝛾𝑢)
𝑔𝑢+1 𝑛 (𝛾𝑢+1) ≃𝐾 (𝑔𝑢 𝑛 ∘ ℎ)(Ψ𝑢(𝛾𝑢)) ≃𝐾 𝑔𝑢 𝑛(𝛾𝑢)
Where the rule is L-Leave, an aborting L-Divert, or an O-Retire of 𝑛, Table 1 gives Ψ𝑢 = idΓ and 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛, so the same equation holds with ℎ = idΓ. Either way the induction hypothesis carries over with the index set unchanged, which is the computation of Theorem 7 one step at a time.
当规则是 L-Leave、一次中止的 L-Divert 或 𝑛 的一次 O-Retire 时,表 1 给出 Ψ𝑢 = idΓ 与 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛,故同一等式在 ℎ = idΓ 时也成立。两种情形下归纳假设都在下标集不变的前提下传递下去,这就是定理 7 的那个计算逐步展开的样子。
Let step 𝑢 act on 𝑚 ≠ 𝑛. Then 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛 by Table 1, and Ψ𝑢 ∈ 𝔐(𝑒𝑚), or Ψ𝑢 = idΓ where the rule is an orchestration rule. Where 𝑚 and 𝑛 are not entangled, Lemma 66 makes 𝑒𝑚 and 𝑒𝑛 independent, and clause (1) of Definition 42, read at the finer ≃ of Definition 58 and hence at ≃𝐾, commutes 𝑔𝑢 𝑛 with Ψ𝑢; where they are entangled, Lemma 67 commutes the two at 𝛾𝑢, its first case outright. Either way
设第 𝑢 步作用于 𝑚 ≠ 𝑛。则由表 1 有 𝑔𝑢+1 𝑛 = 𝑔𝑢 𝑛,且 Ψ𝑢 ∈ 𝔐(𝑒𝑚),或当规则是编排规则时 Ψ𝑢 = idΓ。当 𝑚 与 𝑛 不相互纠缠时,引理 66 使 𝑒𝑚 与 𝑒𝑛 独立,而定义 42 的第 (1) 条——在定义 58 更细的 ≃ 上读出、从而在 ≃𝐾 上读出——使 𝑔𝑢 𝑛 与 Ψ𝑢 可交换;当二者相互纠缠时,引理 67 在 𝛾𝑢 处交换这两个映射,其第一种情形直接给出结论。两种情形下都有
𝑔𝑢 𝑛(𝛾𝑢+1) ≃𝐾 𝑔𝑢 𝑛(Ψ𝑢(𝛾𝑢)) ≃𝐾 Ψ𝑢(𝑔𝑢 𝑛(𝛾𝑢))
𝑔𝑢 𝑛(𝛾𝑢+1) ≃𝐾 𝑔𝑢 𝑛(Ψ𝑢(𝛾𝑢)) ≃𝐾 Ψ𝑢(𝑔𝑢 𝑛(𝛾𝑢))
which is the induction hypothesis with Ψ𝑢 appended, Ψ𝑢 carrying ≃𝐾-related states to ≃𝐾- related states by the paragraph above. □
也就是在归纳假设上追加 Ψ𝑢,而 Ψ𝑢 由上面那段把 ≃𝐾 相关的状态映到 ≃𝐾 相关的状态。□
Corollary 69. (Terminal recovery.) Let an episode of 𝑛 open at 𝑏 and close at 𝑢. Then, with 𝑡1 < ⋯ < 𝑡𝑙 as in Theorem 68, 46
推论 69.(终结恢复。)设 𝑛 的一个回合于 𝑏 开启、于 𝑢 关闭。则取 𝑡1 < ⋯ < 𝑡𝑙 如定理 68,
𝛾𝑢+1 ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏) (56)
𝛾𝑢+1 ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏) (56)
In particular 𝜎𝑢+1 𝑛 = ⌀, which is the premise an O-Remove of 𝑛 carries.
特别地 𝜎𝑢+1 𝑛 = ⌀,这正是 𝑛 的一次 O-Remove 所带的前提。
Proof. By Lemma 59(4) step 𝑢 is an L-Unload of 𝑛, whose Ψ𝑢 is 𝑔𝑢 𝑛 by Lemma 59(3), so 𝛾𝑢+1 ≃𝐾 𝑔𝑢 𝑛(𝛾𝑢) and Theorem 68 applies. For the table, the fiber enters the episode with 𝜎𝑛 empty, and the keys of 𝑝𝑛 enter dom(𝜎𝑛) only by 𝑛’s own extensions ( Definition 56), of which the right side applies none, so the right side leaves 𝜎𝑛 empty; Definition 33 relates two coeffect contexts only where their domains agree, so a table it relates to the empty one is empty. □
证明. 由引理 59(4),第 𝑢 步是 𝑛 的一次 L-Unload,由引理 59(3) 其 Ψ𝑢 就是 𝑔𝑢 𝑛,故 𝛾𝑢+1 ≃𝐾 𝑔𝑢 𝑛(𝛾𝑢),定理 68 适用。至于表,该纤程带着空的 𝜎𝑛 进入回合,而 𝑝𝑛 的各键只由 𝑛 自身的扩展进入 dom(𝜎𝑛)(定义 56),右端一个这样的扩展都没有应用,故右端让 𝜎𝑛 保持为空;定义 33 只在两个余效应上下文的域一致时才关联它们,故一个与空表相关联的表自身也是空的。□
What the two results compare is the tables, so what they assert is bounded by what the keys of a state bind, and inside each binding by the ≃𝑘 the key’s operations induce: each binding is restored only up to what its key’s equivalence forgets, so a monotone allocator is not rewound, a heap’s layout free does not restore, and a message already sent stays sent. This is the bound Section 3.3.2 takes on Theorem 7 and for the same reason, the physical state not being recoverable as it stood; a location the system reifies at no key lies outside the calculus altogether (Definition 56), and Section 6.1 is where a system decides what to reify.
这两个结果所比较者是各张表,故它们所断言的内容以状态中各键的绑定为界,而在每个绑定内部又以该键的操作所诱发的 ≃𝑘 为界:每个绑定只在该键的等价关系所遗忘的范围之内被恢复,因此一个单调的分配器不会被倒回、堆的布局 free 不会恢复、已经发出的消息仍然已发出。这正是第 3.3.2 节对定理 7 所取的那个界限,理由也相同:物理状态无法按原样恢复;一个系统没有在任何键上加以具体化的位置,完全落在演算之外(定义 56),而第 6.1 节才是系统决定具体化什么的地方。
The results above therefore assume nothing of the sequence. The witness and respect conditions of ℑ𝑑∪𝑝 Γ are Lemma 57, pairwise independence is Lemma 66, and both rest on the components alone: Definition 56 fixes the form of every effect function, the effect function’s witness holds each returned inverse to reverting, and the coeffect’s witness holds each key to commutativity ( Definition 46), an interface property Definition 31 turns into a design procedure.
因此上述结果对序列本身不作任何假设。ℑ𝑑∪𝑝 Γ 的见证条件与尊重条件即引理 57,两两独立性即引理 66,两者都只建立在各组件之上:定义 56 固定了每个效应函数的形式,效应函数的见证使每个被返回的逆操作确实起撤销作用,而余效应的见证使每个键确实可交换(定义 46)——一个由定义 31 转化为设计规程的接口性质。
4.3.3. 空间可组合性
4.3.3. Spatial Composability
Local spatial composability holds a component to its own specification, activating it only where its dependencies are provided and classifying every context change against them (Section 3.2.2). The global form adds what quantifies over other fibers: a provider withdraws a binding only after every dependent that resolved it has deactivated, and the resolution a transition installs its effects against does not shift under it. Two properties of the coeffect side deliver the two, and they are proved together, being two halves of one invariant, namely the fixity of 𝜔𝑛 over an episode that Lemma 59(2) establishes. The ordering theorem is what that fixity delivers over the part of the episode in which 𝑛 is 𝖠𝖼𝗍𝗂𝗏𝖾 and then 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀, and the coherence theorem what it delivers over the part in which 𝑛 is installing its effects.
局部空间可组合性使一个组件遵守它自己的规约,只在其依赖被提供之处激活它,并把每一次上下文变化对照这些依赖加以分类(第 3.2.2 节)。全局形式增添了那些对其他纤程作量化的内容:一个提供者只在解析了它的每个依赖方都已停用之后才撤回一个绑定;而一次转移据以安装其效应的那个解析,不会在它底下发生偏移。余效应一侧的两个性质给出这两点,而它们被一并证明,因为它们是同一个不变式的两半,即引理 59(2) 所确立的 𝜔𝑛 在一个回合上的固定性。排序定理是该固定性在这个回合中 𝑛 为 𝖠𝖼𝗍𝗂𝗏𝖾 继而 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的那一段上所推出之物,而一致性定理是它在 𝑛 正在安装其效应的那一段上所推出之物。
Theorem 70. (Ordering.) A fiber begins a transition only where its dependencies are provided:
定理 70.(排序性。)一个纤程只在其依赖被提供之处开始一次转移:
step𝑡 = L-Begin(𝑚) ⇒ 𝛾𝑡 ⊧ 𝑑𝑚 (57)
step𝑡 = L-Begin(𝑚) ⇒ 𝛾𝑡 ⊧ 𝑑𝑚 (57)
Let further [𝑏′, 𝑢′] be an episode of 𝑚 with 𝜔𝑏′ 𝑚(𝑘) = 𝑛 for some 𝑚 ≠ 𝑛 and 𝑘 ∈ 𝑑𝑚, let [𝑏, 𝑢] be the episode of 𝑛 containing 𝑏′, and let 𝑡 range over [𝑏′, 𝑢′]. Then
又设 [𝑏′, 𝑢′] 是 𝑚 的一个回合,其中对某个 𝑚 ≠ 𝑛 与 𝑘 ∈ 𝑑𝑚 有 𝜔𝑏′ 𝑚(𝑘) = 𝑛;设 [𝑏, 𝑢] 是包含 𝑏′ 的 𝑛 的那个回合;并设 𝑡 遍历 [𝑏′, 𝑢′]。则
- 1. 𝜔𝑡 𝑚(𝑘) = 𝑛;
𝜔𝑡 𝑚(𝑘) = 𝑛;
- 2. 𝑏 < 𝑏′, and 𝑢′ < 𝑢 if [𝑏, 𝑢] closes;
𝑏 < 𝑏′,且若 [𝑏, 𝑢] 关闭则 𝑢′ < 𝑢;
- 3. 𝑘 ∈ dom(𝜎𝑡 𝑛), and 𝜎𝑡 𝑛(𝑘) moves only by operations at 𝑘 of fibers declaring 𝑘.
𝑘 ∈ dom(𝜎𝑡 𝑛),且 𝜎𝑡 𝑛(𝑘) 只由声明了 𝑘 的纤程在 𝑘 处的操作所改动。
Proof. The first claim is the premise target𝑡 𝑚 ≠ ⊥ of L-Begin, which by Definition 53 gives 𝛾𝑡 ⊧ 𝑑𝑚.
证明. 第一个断言就是 L-Begin 的前提 target𝑡 𝑚 ≠ ⊥,由定义 53 它给出 𝛾𝑡 ⊧ 𝑑𝑚。
(1) is Lemma 59(2). 47
(1) 即引理 59(2)。
For (2), the L-Begin at 𝑏′ − 1 writes 𝜔𝑏′ 𝑚 = target𝑏′−1 𝑚 , whose values are providers, so 𝜃𝑏′ 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −); the L-Begin at 𝑏 − 1 leaves 𝜃𝑏 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −), so 𝑏 ≠ 𝑏′ and hence 𝑏 < 𝑏′, both episodes opening by Definition 58. Let [𝑏, 𝑢] close and suppose 𝑢 ≤ 𝑢′. Then 𝑢 ∈ [𝑏′, 𝑢′], so installed𝑢 𝑚 and, by (1), 𝜔𝑢 𝑚(𝑘) = 𝑛; that is relied𝑢 𝑛, which the L-Unload at 𝑢 denies. Hence 𝑢′ < 𝑢.
至于 (2):𝑏′ − 1 处的 L-Begin 写入 𝜔𝑏′ 𝑚 = target𝑏′−1 𝑚,其取值为提供者,故 𝜃𝑏′ 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −);𝑏 − 1 处的 L-Begin 留下 𝜃𝑏 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −),故 𝑏 ≠ 𝑏′ 从而 𝑏 < 𝑏′,两个回合都按定义 58 开启。设 [𝑏, 𝑢] 关闭并假设 𝑢 ≤ 𝑢′。则 𝑢 ∈ [𝑏′, 𝑢′],故 installed𝑢 𝑚 且由 (1) 有 𝜔𝑢 𝑚(𝑘) = 𝑛;此即 relied𝑢 𝑛,而 𝑢 处的那次 L-Unload 否定了它。故 𝑢′ < 𝑢。
For (3), 𝑛 is the provider of 𝑘 at 𝛾𝑏′ , so 𝑘 ∈ dom(𝜎𝑏′ 𝑛 ). No L-Unload of 𝑛 falls in [𝑏′, 𝑢′]: where [𝑏, 𝑢] closes it falls at 𝑢 > 𝑢′ by (2), and where it does not, Lemma 59(4) leaves 𝑛 with no L-Unload at all. Since 𝜃𝑏′ 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −), Table 1 therefore leaves L-Leave as the only rule 𝑛 can be acted on by within [𝑏′, 𝑢′], and its Ψ𝑡 is idΓ, so 𝑛 withdraws nothing and dom(𝜎𝑛) is constant there by Lemma 59(1). What a step of another fiber may move is values at keys its own declarations name (Definition 56), so a write to 𝜎𝑛(𝑘) is an operation at 𝑘 of a fiber with 𝑘 in its specification. □
至于 (3):𝑛 是 𝛾𝑏′ 处 𝑘 的提供者,故 𝑘 ∈ dom(𝜎𝑏′ 𝑛)。𝑛 的 L-Unload 不落在 [𝑏′, 𝑢′] 内:当 [𝑏, 𝑢] 关闭时,由 (2) 它落在 𝑢 > 𝑢′ 处;当它不关闭时,引理 59(4) 使 𝑛 根本没有任何 L-Unload。由于 𝜃𝑏′ 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −),表 1 因而在 [𝑏′, 𝑢′] 内只留下 L-Leave 作为 𝑛 所能被作用的规则,而其 Ψ𝑡 是 idΓ,故 𝑛 在那里不撤回任何东西,由引理 59(1) dom(𝜎𝑛) 在那里恒定。另一个纤程的一步所能挪动的,是其自身声明所命名的键处的值(定义 56),故对 𝜎𝑛(𝑘) 的一次写入,必是某个其规约含 𝑘 的纤程在 𝑘 处的一次操作。□
A transition spread over steps could otherwise install effects computed against a resolution that has changed under it, and two premises prevent that. L-Iter and L-Finish carry target𝑛(𝛾) = 𝜔, so a transition proceeds only while its committed view is still its target view, and L-Divert carries the negation, so any change to the target view takes the fiber out of the transition. The two directions of change are not distinguished: a component whose dependency has gone and one whose dependency has been replaced leave by the same route, because a target view that has become ⊥ and one that has become some other fiber are equally unequal to 𝜔.
否则,一次展开在若干步上的转移就可能把效应安装在一个已在它底下改变了的解析之上;有两个前提阻止这一点。L-Iter 与 L-Finish 带有 target𝑛(𝛾) = 𝜔,故一次转移只在其已提交视图仍是它的目标视图时才继续推进;而 L-Divert 带有其否定,故目标视图的任何变化都把该纤程带出这次转移。变化的两个方向并不被区分:依赖已经消失的组件与依赖已被替换的组件走同一条退出路径,因为一个变成了 ⊥ 的目标视图与一个变成了另一纤程的目标视图,同样都不等于 𝜔。
The landing alternative of L-Divert is what stops this from being a guarantee about every step: the iteration it lands installs an effect computed against a resolution that no longer holds. What the rules deliver is therefore a disjunction, and the second branch is what makes the first safe.
L-Divert 的落地分支,正是使这不成为关于每一步的保证的东西:它所落地的那次迭代,安装的是针对一个已不再成立的解析所计算出的效应。因此各规则所给出的是一个析取,而第二个分支正是使第一个分支安全的东西。
Theorem 71. (Resolution coherence.) Let an episode [𝑏, 𝑢] of 𝑛 open at 𝑏 with 𝜔𝑏 𝑛 = 𝜔. Then 𝜃𝑛 is 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −) on an initial interval [𝑏, 𝑟] of the episode, and every iteration of the transition runs against the one resolution 𝜔:
定理 71.(解析一致性。)设 𝑛 的一个回合 [𝑏, 𝑢] 于 𝑏 开启,且 𝜔𝑏 𝑛 = 𝜔。则 𝜃𝑛 在该回合的一个初始区间 [𝑏, 𝑟] 上为 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −),且该转移的每次迭代都针对同一个解析 𝜔 运行:
∀𝑡 ∈ [𝑏, 𝑟]. step𝑡 ∈ {L-Iter(𝑛), L-Finish(𝑛)} ⇒ target𝑡 𝑛 = 𝜔 (58)
∀𝑡 ∈ [𝑏, 𝑟]. step𝑡 ∈ {L-Iter(𝑛), L-Finish(𝑛)} ⇒ target𝑡 𝑛 = 𝜔 (58)
Where the fiber leaves that interval, so that 𝑟 < 𝑢, exactly one of the following holds:
当该纤程离开那个区间、即 𝑟 < 𝑢 时,下述两者恰有一个成立:
- 1. step𝑟 = L-Finish(𝑛) and 𝜃𝑟+1 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔);
step𝑟 = L-Finish(𝑛) 且 𝜃𝑟+1 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔);
- 2. step𝑟 = L-Divert(𝑛), and the episode closes at some 𝑢 > 𝑟 with 𝛾𝑢+1 ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏) as in Corollary 69.
step𝑟 = L-Divert(𝑛),且该回合在某个 𝑢 > 𝑟 处关闭,并有 𝛾𝑢+1 ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏),如推论 69。
Proof. The L-Begin at 𝑏 − 1 writes 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀, and by Table 1 it is the one rule leading into that lifecycle state; its premise 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 and Lemma 59(4) put any second application of it outside the episode. So 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 occupies an initial interval [𝑏, 𝑟] of [𝑏, 𝑢] and is not re-entered. The first claim is then the premise target𝑛(𝛾) = 𝜔′ that Table 1 gives L-Iter and L-Finish, together with 𝜔′ = 𝜔 by Lemma 59(2).
证明. 𝑏 − 1 处的 L-Begin 写入 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,而由表 1 它是导入该生命周期状态的唯一规则;其前提 𝜃𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 与引理 59(4) 把它的任何第二次应用都置于回合之外。故 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 占据 [𝑏, 𝑢] 的一个初始区间 [𝑏, 𝑟] 且不会被重新进入。于是第一个断言就是表 1 赋予 L-Iter 与 L-Finish 的前提 target𝑛(𝛾) = 𝜔′,再加上由引理 59(2) 得 𝜔′ = 𝜔。
For the dichotomy, step𝑟 is a rule whose premise has 𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −) and whose conclusion does not, of which Table 1 offers L-Finish and L-Divert; the first lands in 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔) and the second in 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, 𝜔), from which Lemma 59(4) makes an L-Unload the only exit and Corollary 69 supplies the equation. The iteration a landing L-Divert contributes is one of 𝑛’s own, hence among the maps that accumulator withdraws. Where instead 𝑟 = 𝑢, the sequence ends with the transition still in flight and the first claim is all that is asserted. □ 48
至于那个二分,step𝑟 是一条前提含 𝜃𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, −) 而结论不含它的规则,表 1 提供的此类规则是 L-Finish 与 L-Divert;前者落入 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔),后者落入 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, 𝜔),而由引理 59(4),从后者出发 L-Unload 是唯一的出口,推论 69 则给出那个等式。一次落地的 L-Divert 所贡献的迭代是 𝑛 自己的,因而属于那个累加器所撤销的那些映射之列。若反过来 𝑟 = 𝑢,则序列在转移仍在进行中时就结束了,此时所断言者只有第一个结论。□
4.3.4. 进展性
4.3.4. Progress
A guard that defers a provider’s withdrawal until its dependents are gone delivers Theorem 70 only if it eventually releases. One relation on the fibers of a registry carries the argument.
一个把提供者的撤出推迟到它的各个依赖方全部离去之后的守卫条件,只有当它最终会放行时,才给出定理 70。承载这一论证的是注册表诸纤程上的一个关系。
Definition 72. The precedence relation on the names of a registry is
定义 72. 注册表诸名字上的先行关系为
𝑛 ≺ 𝑚 ≔ 𝑝𝑛 ∩ 𝑑𝑚 ≠ ⌀ (59)
𝑛 ≺ 𝑚 ≔ 𝑝𝑛 ∩ 𝑑𝑚 ≠ ⌀ (59)so that 𝑛 may provide a key 𝑚 declares. It reads 𝑑 and 𝑝 alone, which by Lemma 59(5) come into existence with a fiber’s entry and are never written again. Theorem 73 and Theorem 80 are established on the hypothesis that ≺ is acyclic, which is an assumption and not something the definition delivers, 𝑛 ≺ 𝑛 holding of a component that declares a key it provides itself. What ≺ orders is the two fibers’ activations and not their lifetimes: 𝑛 ≺ 𝑚 says that 𝑛 has to become 𝖠𝖼𝗍𝗂𝗏𝖾 before 𝑚 can, whereas that a provider outlives its consumer is Theorem 70(2), a theorem about the guarded calculus.
意即 𝑛 可以提供 𝑚 所声明的某个键。它只读 𝑑 与 𝑝,而由引理 59(5),这两者随一个纤程的条目一同产生,此后永不再被写入。定理 73 与定理 80 是在 ≺ 无环这一假设之下建立的;这是一个假设,而不是该定义本身所给出的东西——对于一个声明了由它自己提供的某个键的组件,𝑛 ≺ 𝑛 成立。≺ 所排序的是两个纤程的激活先后,而非它们生命周期的长短:𝑛 ≺ 𝑚 说的是 𝑛 必须先于 𝑚 变成 𝖠𝖼𝗍𝗂𝗏𝖾,而提供者活得比它的消费者更久则是定理 70(2),一条关于带守卫演算的定理。
A fiber’s target view answers to the fiber that created it as well as to its providers. What a creator writes is 𝜏𝑛, through the primitive of Definition 52, and 𝜏 is monotone by Lemma 59(5). A creator can therefore turn its child’s target view at most once over that child’s whole existence.
一个纤程的目标视图既应于创建它的那个纤程,也应于它的各个提供者。创建者所写的是 𝜏𝑛,经由定义 52 的原语,而 𝜏 由引理 59(5) 是单调的。因此创建者在子纤程的整个存在期间,至多能把它这个子纤程的目标视图翻转一次。
Progress is a claim that some rule applies, so it is formulated over the rules a host must offer: L-Begin, L-Leave, L-Unload, the landing rules L-Iter and L-Finish, and L-Divert. It appeals to the aborting alternative of L-Divert nowhere, which Section 4.4 puts to use.
进展性所断言的是某条规则可适用,因此它是就宿主必须提供的那些规则来陈述的:L-Begin、L-Leave、L-Unload、落地规则 L-Iter 与 L-Finish,以及 L-Divert。它在任何地方都不诉诸 L-Divert 的中止那一支,那一支要到 4.4 节才派上用场。
Theorem 73. (Progress.) Assume ≺ acyclic, len(𝑒𝑛) ≤ 𝐾 for every 𝑛, and the set 𝑁 of names the sequence ever holds ( Definition 65) finite; and let every step apply a lifecycle rule. Write 𝑆(𝑛) for the number of steps acting on 𝑛 and
定理 73.(进展性。)假设 ≺ 无环,对每个 𝑛 有 len(𝑒𝑛) ≤ 𝐾,且该序列曾经持有的名字集合 𝑁(定义 65)有限;并设每一步都适用某条生命周期规则。记 𝑆(𝑛) 为作用于 𝑛 的步数,并记
𝑉 (𝑛) ≔ |{𝑡 : target𝑡 𝑛 ≠ target𝑡+1 𝑛 }| (60)
𝑉 (𝑛) ≔ |{𝑡 : target^𝑡_𝑛 ≠ target^{𝑡+1}_𝑛}| (60)for the number of times its target view turns. Then
为其目标视图翻转的次数。那么
1. (No deadlock.) ¬ quiet𝑡 implies that some lifecycle rule applies at 𝛾𝑡; 2. (Termination.) 𝑆(𝑛) ≤ (𝐾 + 3)(𝑉 (𝑛) + 1), and both 𝑉 (𝑛) and ∑𝑛 𝑆(𝑛) are finite.
1.(无死锁。)¬ quiet^𝑡 蕴涵在 𝛾^𝑡 处有某条生命周期规则可适用; 2.(终止性。)𝑆(𝑛) ≤ (𝐾 + 3)(𝑉 (𝑛) + 1),且 𝑉 (𝑛) 与 ∑_𝑛 𝑆(𝑛) 二者皆有限。
Consequently every maximal sequence of lifecycle steps ends in a quiescent state.
因此,生命周期步的每个极大序列都终止于一个静止状态。
Proof. No deadlock. Let ¬ quiet𝑡, so some fiber 𝑛 satisfies neither clause of the quiet of Defini- tion 53. Reading Table 1 against the four kinds it can then be:
证明. 无死锁。设 ¬ quiet^𝑡,于是某个纤程 𝑛 不满足定义 53 之静止(quiet)的任何一个子句。对照表 1 逐一考察它此时可能落入的四种情形:
- • 𝜃𝑡 𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 with target𝑡 𝑛 ≠ ⊥: L-Begin applies;
𝜃^𝑡_𝑛 = 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 且 target^𝑡_𝑛 ≠ ⊥:L-Begin 可适用;
- • 𝜃𝑡 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, 𝜔𝑛) with target𝑡 𝑛 = 𝜔𝑛: whichever of L-Iter and L-Finish the value of 𝑖𝑡 𝑛(𝛾𝑡) selects applies;
𝜃^𝑡_𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, 𝜔𝑛) 且 target^𝑡_𝑛 = 𝜔𝑛:由 𝑖^𝑡_𝑛(𝛾^𝑡) 的值在 L-Iter 与 L-Finish 中选出的那一条可适用;
- • 𝜃𝑡 𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, 𝜔𝑛) with target𝑡 𝑛 ≠ 𝜔𝑛: L-Divert applies, landing that iteration rather than aborting it;
𝜃^𝑡_𝑛 = 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −, 𝜔𝑛) 且 target^𝑡_𝑛 ≠ 𝜔𝑛:L-Divert 可适用,使那次迭代落地而不是把它中止;
- • 𝜃𝑡 𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔𝑛) with target𝑡 𝑛 ≠ 𝜔𝑛: L-Leave applies.
𝜃^𝑡_𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔𝑛) 且 target^𝑡_𝑛 ≠ 𝜔𝑛:L-Leave 可适用。
Let no fiber be of any of these kinds, leaving some 𝑚0 with 𝜃𝑡 𝑚0 = 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −). Construct 𝑚0, 𝑚1, … as follows: given 𝑚𝑗 in 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀, either ¬ relied𝑡 𝑚𝑗 , in which case L-Unload applies to 𝑚𝑗 and the construction stops, or there are 𝑚𝑗+1 ≠ 𝑚𝑗 and 𝑘𝑗 with installed𝑡 𝑚𝑗+1 and 𝜔𝑡 𝑚𝑗+1 (𝑘𝑗) = 𝑚𝑗. In the latter case
设没有任何纤程属于这些情形中的任何一种,于是留下某个 𝑚0 满足 𝜃^𝑡_{𝑚0} = 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀(−, −)。如下构造 𝑚0, 𝑚1, …:给定处于 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的 𝑚𝑗,或者 ¬ relied^𝑡_{𝑚𝑗},此时 L-Unload 对 𝑚𝑗 可适用且构造停止;或者存在 𝑚_{𝑗+1} ≠ 𝑚𝑗 与 𝑘𝑗,满足 installed^𝑡_{𝑚_{𝑗+1}} 且 𝜔^𝑡_{𝑚_{𝑗+1}}(𝑘𝑗) = 𝑚𝑗。在后一种情形下
𝑘𝑗 ∈ 𝑑𝑚𝑗+1 ∩ dom(𝜎𝑡 𝑚𝑗 ) ⊆ 𝑑𝑚𝑗+1 ∩ 𝑝𝑚𝑗 49
𝑘𝑗 ∈ 𝑑_{𝑚_{𝑗+1}} ∩ dom(𝜎^𝑡_{𝑚𝑗}) ⊆ 𝑑_{𝑚_{𝑗+1}} ∩ 𝑝_{𝑚𝑗}the second membership being Theorem 70(3) at the episode of 𝑚𝑗+1 that 𝑡 lies in, so that 𝑚𝑗 ≺ 𝑚𝑗+1. Moreover target𝑡 𝑚𝑗+1 ≠ 𝜔𝑡 𝑚𝑗+1 : an 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 fiber is outside the union defining 𝜎𝛾, so 𝑘𝑗 at 𝛾𝑡 is unprovided or provided by a fiber other than 𝑚𝑗. Were 𝑚𝑗+1 in 𝖠𝖼𝗍𝗂𝗏𝖾 or 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 it would then be of one of the four kinds excluded, so it is in 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 and the construction continues. The 𝑚𝑗 are ≺-increasing, hence distinct by acyclicity, and dom(𝐹 𝑡) is finite, so the construction stops.
第二个成员关系是定理 70(3) 在 𝑚_{𝑗+1} 的那一个 𝑡 所处的幕(episode)上读出的,因此 𝑚𝑗 ≺ 𝑚_{𝑗+1}。此外 target^𝑡_{𝑚_{𝑗+1}} ≠ 𝜔^𝑡_{𝑚_{𝑗+1}}:处于 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的纤程落在定义 𝜎^𝛾 的那个并集之外,因此 𝑘𝑗 在 𝛾^𝑡 处或者未被提供,或者由 𝑚𝑗 之外的某个纤程提供。若 𝑚_{𝑗+1} 处于 𝖠𝖼𝗍𝗂𝗏𝖾 或 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,它就会属于已被排除的四种情形之一,所以它处于 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀,构造得以继续。诸 𝑚𝑗 按 ≺ 递增,因而由无环性两两不同,而 dom(𝐹^𝑡) 有限,所以构造会停止。
Termination. Two claims bound 𝑆(𝑛).
终止性。两条论断给出 𝑆(𝑛) 的界。
(A) Over a maximal interval on which target𝑡 𝑛 is constant at 𝜔∗, at most 𝐾 + 3 steps act on 𝑛. Reading the 𝜃𝑛 columns of Table 1, from 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔) with 𝜔 ≠ 𝜔∗ the fiber takes an L-Leave and an L-Unload and then, if 𝜔∗ ≠ ⊥, an L-Begin and at most len(𝑒𝑛) ≤ 𝐾 landings; from 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 against an 𝜔 ≠ 𝜔∗ it takes an L-Divert in place of the L-Leave, and from any other state a suffix of that sequence. No further L-Divert or L-Leave falls in the interval, the 𝜔 that the L-Begin writes being target𝑡 𝑛 = 𝜔∗ itself, and at 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔∗) and at 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 with 𝜔∗ = ⊥ no rule applies at all.
(A)在 target^𝑡_𝑛 恒取 𝜔∗ 的极大区间上,至多有 𝐾 + 3 步作用于 𝑛。读表 1 的 𝜃_𝑛 各列:从 𝜔 ≠ 𝜔∗ 的 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔) 出发,纤程走一步 L-Leave 与一步 L-Unload,随后若 𝜔∗ ≠ ⊥,再走一步 L-Begin 与至多 len(𝑒_𝑛) ≤ 𝐾 次落地;从 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 相对 𝜔 ≠ 𝜔∗ 出发,它以一步 L-Divert 取代那步 L-Leave;而从任何其他状态出发则走该序列的一个后缀。该区间内不会再落入 L-Divert 或 L-Leave,因为 L-Begin 所写的那个 𝜔 就是 target^𝑡_𝑛 = 𝜔∗ 本身,而在 𝖠𝖼𝗍𝗂𝗏𝖾(−, 𝜔∗) 处、以及在 𝜔∗ = ⊥ 的 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 处,根本没有任何规则可适用。
(B) If target𝑡 𝑛 ≠ target𝑡+1 𝑛 and step 𝑡 acts on 𝑚, then either 𝑚 ≺ 𝑛 or step 𝑡 writes 𝜏𝑛. By Definition 53 the value of target𝑛 is a function of 𝜏𝑛, of the lifecycle states, and of the domains of the providers’ tables, never of a bound value; a provider satisfies 𝑘 ∈ dom(𝜎𝑚) ∩ 𝑑𝑛 and hence 𝑚 ≺ 𝑛, and a table’s domain changes only at a step acting on its own fiber by Lemma 59(1). Acyclicity gives 𝑚 ≠ 𝑛 in the first case, and the monotonicity of Lemma 59(5) admits the second at one 𝑡 per fiber.
(B)若 target^𝑡_𝑛 ≠ target^{𝑡+1}_𝑛 且第 𝑡 步作用于 𝑚,则或者 𝑚 ≺ 𝑛,或者第 𝑡 步写 𝜏_𝑛。由定义 53,target_𝑛 的值是 𝜏_𝑛、各生命周期状态以及各提供者表的定义域的函数,从不依赖于某个被绑定的值;一个提供者满足 𝑘 ∈ dom(𝜎_𝑚) ∩ 𝑑_𝑛,从而 𝑚 ≺ 𝑛,而一个表的定义域只在作用于它自己纤程的那一步上改变(引理 59(1))。无环性给出第一种情形下 𝑚 ≠ 𝑛,而引理 59(5) 的单调性允许第二种情形在每个纤程处至多出现于一个 𝑡。
By (A) the interval count bounds 𝑆(𝑛) as 𝑆(𝑛) ≤ (𝐾 + 3)(𝑉 (𝑛) + 1), and by (B) each turn of target𝑛 either consumes a step of a fiber strictly ≺-below 𝑛 or is the one turn 𝜏𝑛 affords, so 𝑉 (𝑛) ≤ 1 + ∑𝑚≺𝑛 𝑆(𝑚). Since ≺ is acyclic and 𝑁 is finite, the recursion
由(A),区间计数把 𝑆(𝑛) 界为 𝑆(𝑛) ≤ (𝐾 + 3)(𝑉 (𝑛) + 1);由(B),target_𝑛 的每次翻转要么消耗掉一个严格 ≺-低于 𝑛 的纤程的一步,要么是 𝜏_𝑛 所提供的那唯一一次翻转,因此 𝑉 (𝑛) ≤ 1 + ∑_{𝑚≺𝑛} 𝑆(𝑚)。由于 ≺ 无环且 𝑁 有限,递归
𝐵(𝑛) ≔ (𝐾 + 3)(2 + ∑ 𝑚≺𝑛 𝐵(𝑚))
𝐵(𝑛) ≔ (𝐾 + 3)(2 + ∑_{𝑚≺𝑛} 𝐵(𝑚))is well founded and defines 𝐵 with 𝑆(𝑛) ≤ 𝐵(𝑛); hence 𝑉 (𝑛) is finite and ∑𝑛 𝑆(𝑛) ≤ ∑𝑛 𝐵(𝑛). By (1) a sequence that cannot be extended is quiescent. □
是良基的,并定义了满足 𝑆(𝑛) ≤ 𝐵(𝑛) 的 𝐵;因此 𝑉 (𝑛) 有限,且 ∑_𝑛 𝑆(𝑛) ≤ ∑_𝑛 𝐵(𝑛)。由(1),无法再延长的序列是静止的。□
Finiteness of 𝑁 is assumed rather than derived, and one condition on the components delivers it. The components a host holds are finitely many programs given before anything runs, so if no component can instantiate, however indirectly, a fiber of a component that instantiates one of its own, the instantiations form a tree of bounded depth, and len(𝑒𝑛) ≤ 𝐾 bounds its branching. What the assumption rules out is a component that instantiates itself without bound.
𝑁 的有限性是假设出来的而不是推出来的,而组件上的一个条件即可给出它。宿主所持有的组件是在任何东西运行之前就给定的有限多个程序,因此若没有组件能够——无论多么间接地——实例化出这样一个纤程,即该纤程所属的组件又能实例化出它自己的一个纤程,那么诸实例化就构成一棵深度有界的树,而 len(𝑒_𝑛) ≤ 𝐾 给出其分支的界。该假设所排除的,是一个无界地实例化自身的组件。
The target records the providing fiber rather than a boolean, and under the single-source discipline of O-Insert the two drive the same transitions, a key having one possible provider there. The view supplies the vocabulary of the results above, Theorem 70 and Theorem 71 both speaking of the resolution a fiber activated against, and it is what makes those results survive the scoped resolution of Section 3.2.3, under which one key resolves to different providers in different realms and the provisions no longer force the view. The implementation carries that scoping and holds the view in fiber.committed (Section 5.1.3). 50
target 记录的是提供该键的那个纤程而不是一个布尔值;在 O-Insert 的单源约束下,两者驱动同样的转移,因为那里一个键只有一个可能的提供者。该视图为上述各结果提供了词汇——定理 70 与定理 71 说的都是一个纤程所据以激活的那个解析——也正是它使这些结果在第 3.2.3 节的作用域解析下依然成立:在那里同一个键在不同的域(realm)中解析到不同的提供者,而各提供声明不再决定该视图。实现承载了这种作用域化,并把该视图保存在 fiber.committed 中(第 5.1.3 节)。
4.3.5. 合流性
4.3.5. Confluence
The results so far are about individual fibers. The property that characterizes the system as a whole is that its dynamic history leaves no trace: whatever sequence of activations and deacti- vations a running system has been through, the state it quiesces at is the one the same insertions and retirements would have produced had each component that ends up active been loaded once, in dependency order, and none ever unloaded. The lifecycle relation is confluent, and the normal form it converges on is the statically assembled one. This is the analogue, for dynamic composition, of the consistency with a from-scratch evaluation that change propagation estab- lishes for incremental computation [47].
到目前为止的各结果都是关于单个纤程的。刻画整个系统的性质是:它的动态历史不留痕迹——无论一个运行中的系统经历过怎样的激活与去激活序列,它所静止于的那个状态,正是同样的那些插入与退役在如下情形下会产生的状态:每个最终处于激活的组件都按依赖顺序被加载一次,且从没有任何组件被卸载过。生命周期关系是合流的,而它收敛到的那个范式就是静态装配出来的那个。这是增量计算中变更传播所确立的"与从零开始的求值相一致"[47]在动态组合上的对应物。
The claim is about ⟶ alone. Orchestration steps are inputs, and two sequences given different inputs land in different places for no interesting reason; what is at issue is whether the lifecycle rules, which are nondeterministic in which fiber steps next and in which exit a 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 fiber takes, can be made to disagree. Which fiber provides a key is not among the choices, a key having one possible provider (Definition 50), so the schedule picks orders and exits and nothing else.
该论断只关乎 ⟶ 本身。编排步是输入,给定不同输入的两个序列落到不同的地方,并没有什么值得追究的原因;真正成问题的是生命周期规则——它们在"下一个走的是哪个纤程"以及"一个 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 纤程走哪个出口"上是非确定性的——是否会被弄得彼此不一致。由哪个纤程提供一个键并不在选择之列,因为一个键只有一个可能的提供者(定义 50),所以调度所挑选的只是顺序与出口,此外别无其他。
Three lemmas are needed first. The first fixes the set of fibers that end up 𝖠𝖼𝗍𝗂𝗏𝖾 without reference to any sequence of steps, which is what makes it a function of the input rather than of the schedule.
首先需要三条引理。第一条不参照任何步序列就确定了最终处于 𝖠𝖼𝗍𝗂𝗏𝖾 的那些纤程的集合,这正是使它成为输入的函数而不是调度的函数的原因。
Definition 74. A fiber is supported at 𝛾 when it is not retired, the fiber instantiating it is supported, and every key it declares is provided by a supported fiber. The support relation on dom(𝐹𝛾) is the union of the two relations those clauses read,
定义 74. 一个纤程在 𝛾 处是被支撑的,当它未被退役、实例化它的那个纤程是被支撑的,且它声明的每个键都由某个被支撑的纤程提供。dom(𝐹_𝛾) 上的支撑关系是这几个子句所读的那两个关系的并:
𝑚 ⊲ 𝑛 ≔ 𝑚 ≺ 𝑛 ∨ 𝜋𝑛 = 𝑚 (61)
𝑚 ⊲ 𝑛 ≔ 𝑚 ≺ 𝑛 ∨ 𝜋𝑛 = 𝑚 (61)and where it is well founded (Lemma 75) we write 𝐴 for the support set, the fibers supported at 𝛾:
而在它良基的地方(引理 75),我们用 𝐴 记支撑集,即在 𝛾 处被支撑的那些纤程:
𝑛 ∈ 𝐴 ≔ ¬𝜏𝑛 ∧ (𝜋𝑛 = 𝗋𝗈𝗈𝗍 ∨ 𝜋𝑛 ∈ 𝐴) ∧ ∀𝑘 ∈ 𝑑𝑛. ∃𝑚 ∈ 𝐴. 𝑘 ∈ 𝑝𝑚 (62)
𝑛 ∈ 𝐴 ≔ ¬𝜏𝑛 ∧ (𝜋𝑛 = 𝗋𝗈𝗈𝗍 ∨ 𝜋𝑛 ∈ 𝐴) ∧ ∀𝑘 ∈ 𝑑𝑛. ∃𝑚 ∈ 𝐴. 𝑘 ∈ 𝑝𝑚 (62)where 𝜋𝑛 = 𝗋𝗈𝗈𝗍 marks a fiber the orchestrator inserted and 𝜋𝑛 otherwise the fiber whose activation instantiates 𝑛. The clauses read no field but 𝜏, 𝜋, 𝑑, 𝑝. Both halves relate a fiber to one immediately below it, a parent rather than an ancestor and a direct provider rather than a transitive one, since that is what the clauses read; where the results below want an order they take the transitive closure, whose minimal elements, maximal elements, and linearizations are those of ⊲.
其中 𝜋_𝑛 = 𝗋𝗈𝗈𝗍 标记由编排器插入的纤程,否则 𝜋_𝑛 是其激活实例化了 𝑛 的那个纤程。这些子句除 𝜏、𝜋、𝑑、𝑝 之外不读任何字段。两半各自把一个纤程关联到紧邻其下的一个纤程——是父而非祖先,是直接提供者而非传递提供者——因为这正是这些子句所读的;下面各结果在需要序的时候取传递闭包,其极小元、极大元与线性化都是 ⊲ 的那些。
The clauses refer to 𝐴 itself, so the definition is a recursion along ⊲, and it is the following that makes it one with a solution.
这些子句提到了 𝐴 自身,因此该定义是一个沿 ⊲ 的递归,而下面这条保证了它是一个有解的递归。
Lemma 75. (Support is well founded.) Let ≺ be acyclic and let 𝛾 be reached by a sequence of steps. Then ⊲ is well founded, and 𝐴 is the one solution of Definition 74, a function of 𝜏, 𝜋, 𝑑, and 𝑝 alone.
引理 75.(支撑是良基的。)设 ≺ 无环,且 𝛾 由某个步序列到达。那么 ⊲ 良基,且 𝐴 是定义 74 的唯一解,一个只依赖于 𝜏、𝜋、𝑑、𝑝 的函数。
Proof. Order the names of dom(𝐹𝛾) by the index of the O-Insert that introduced each, which Definition 58 supplies by starting the sequence at an empty registry. The parent half of ⊲ descends in that index: an O-Insert has 𝜋 ∈ dom(𝐹𝛾) as a premise, so a parent pointer names a fiber introduced earlier, and iterating it reaches the whole ancestry of a name in finitely many steps. A cycle therefore has to use ≺, and since ≺ is acyclic it has to mix the two, which needs some 𝑚 to declare a key that a fiber of 𝑚’s own subtree may provide. Such a fiber is instantiated 51 by an activation of 𝑚 or of one of 𝑚’s descendants, hence at a step after the L-Begin of 𝑚; that L-Begin has 𝛾 ⊧ 𝑑𝑚 as a premise, so a fiber providing the key is 𝖠𝖼𝗍𝗂𝗏𝖾 already before it, and clause (2) of Definition 63 leaves the key no second possible provider. The fiber that would close the cycle is therefore never introduced, and the edge is absent from dom(𝐹𝛾). A well- founded recursion has one solution, and the clauses read the four fields alone. □
证明. 用引入每个名字的那次 O-Insert 的下标为 dom(𝐹_𝛾) 中的名字排序,这个下标由定义 58 通过让序列从一个空注册表开始而提供。⊲ 的父那一半在该下标上递减:一次 O-Insert 以 𝜋 ∈ dom(𝐹_𝛾) 为前提,因此父指针所指名的是一个更早已被引入的纤程,迭代它在有限多步内穷尽一个名字的整个祖先链。因此一个环必须使用 ≺;而由于 ≺ 无环,它必须把两者混合,这就要求某个 𝑚 声明一个可由 𝑚 自己子树中某个纤程提供的键。这样的纤程由 𝑚 或 𝑚 的某个后代的一次激活所实例化,因而在 𝑚 的 L-Begin 之后的某一步;而那次 L-Begin 以 𝛾 ⊧ 𝑑_𝑚 为前提,所以提供该键的纤程在它之前就已经是 𝖠𝖼𝗍𝗂𝗏𝖾,而定义 63 的第 (2) 子句使该键没有第二个可能的提供者。因此,本可闭合该环的那个纤程永不被引入,而这条边不在 dom(𝐹_𝛾) 之中。良基递归有唯一解,而这些子句只读那四个字段。□
The last clause reads 𝑝, the keys a component may provide, whereas the target reads dom(𝜎𝛾), the keys its fibers have installed, and Definition 48 relates the two by dom(𝜎𝑛) ⊆ 𝑝𝑛 alone. The support set therefore over-approximates the 𝖠𝖼𝗍𝗂𝗏𝖾 fibers in general, and the condition that closes the gap is the following.
最后一个子句读的是 𝑝,即一个组件可以提供的那些键,而目标视图读的是 dom(𝜎_𝛾),即它的纤程已经安装的那些键,定义 48 只用 dom(𝜎_𝑛) ⊆ 𝑝_𝑛 把两者联系起来。因此一般而言支撑集是对诸 𝖠𝖼𝗍𝗂𝗏𝖾 纤程的过近似,而弥合这一差距的条件如下。
Definition 76. A component (𝑑, 𝑝, 𝑒) is total on its provision when an activation of it that finishes has installed every key of 𝑝, so that dom(𝜎𝑛) = 𝑝𝑛 at every 𝖠𝖼𝗍𝗂𝗏𝖾 fiber instantiating it.
定义 76. 一个组件 (𝑑, 𝑝, 𝑒) 在其提供上是完全的(total),当它的一次运行完毕的激活已经安装了 𝑝 的每个键,从而在实例化它的每个 𝖠𝖼𝗍𝗂𝗏𝖾 纤程处都有 dom(𝜎_𝑛) = 𝑝_𝑛。
This is a condition on the components alone, mentioning no lifecycle state and no step, and independence (Lemma 66) already bounds how far it can fail: were a component to install a key only at context states another component’s effects reach, its forward map would not commute with that component’s, so the keys a fiber installs are fixed by its component rather than by the schedule. What totality adds is that the fixed set is all of 𝑝 rather than a proper subset of it.
这是只关于诸组件的一个条件,不提及任何生命周期状态、也不提及任何一步;而独立性(引理 66)已经限制了它能失败到什么程度:若一个组件只在另一个组件的效应所到达的那些上下文状态处才安装某个键,它的前向映射就不会与那个组件的前向映射可交换,因此一个纤程所安装的那些键是由它的组件所固定的,而不是由调度所固定的。完全性所补充的是:这个被固定的集合是 𝑝 的全部,而不是它的一个真子集。
Lemma 77. (Support at quiescence.) Let ≺ be acyclic, let quiet(𝛾), and let every component of 𝛾 be total on its provision (Definition 76). Then the support set is the set of 𝖠𝖼𝗍𝗂𝗏𝖾 fibers:
引理 77.(静止处的支撑。)设 ≺ 无环,设 quiet(𝛾),且设 𝛾 的每个组件都在其提供上完全(定义 76)。那么支撑集就是诸 𝖠𝖼𝗍𝗂𝗏𝖾 纤程的集合:
𝐴 = {𝑛 : 𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −)} (63)
𝐴 = {𝑛 : 𝜃𝑛 = 𝖠𝖼𝗍𝗂𝗏𝖾(−, −)} (63)Proof. Write 𝐴′ for the right-hand side. The quiet of Definition 53 leaves 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 and 𝖠𝖼𝗍𝗂𝗏𝖾 as the only states and reads
证明. 记右端为 𝐴′。定义 53 的静止只留下 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 与 𝖠𝖼𝗍𝗂𝗏𝖾 两种状态,并读作
𝑛 ∈ 𝐴′ ⟺ target𝑛(𝛾) ≠ ⊥
𝑛 ∈ 𝐴′ ⟺ target𝑛(𝛾) ≠ ⊥By Definition 53 the right side holds exactly when ¬𝜏𝑛 and every 𝑘 ∈ 𝑑𝑛 lies in dom(𝜎𝛾), and dom(𝜎𝛾) = ⋃𝑚∈𝐴′ 𝑝𝑚 by Definition 76. The middle clause is the one the target no longer carries, and instantiation supplies it: a fiber with 𝜋𝑛 ≠ 𝗋𝗈𝗈𝗍 is instantiated only by an activation of 𝜋𝑛, and if 𝜋𝑛 ∉ 𝐴′ then 𝜋𝑛 is not 𝖠𝖼𝗍𝗂𝗏𝖾, so its accumulator has run and retired 𝑛 by Definition 52, giving 𝜏𝑛. Hence 𝐴′ satisfies the clauses of Definition 74, and Lemma 75 gives them one solution, so 𝐴 = 𝐴′. □
由定义 53,右端恰好在 ¬𝜏_𝑛 且 𝑑_𝑛 中的每个 𝑘 都落在 dom(𝜎_𝛾) 中时成立,而由定义 76 有 dom(𝜎_𝛾) = ⋃_{𝑚∈𝐴′} 𝑝_𝑚。中间那个子句正是目标视图不再承载的那个,而实例化补上了它:𝜋_𝑛 ≠ 𝗋𝗈𝗈𝗍 的纤程只由 𝜋_𝑛 的一次激活所实例化,而若 𝜋_𝑛 ∉ 𝐴′,则 𝜋_𝑛 不是 𝖠𝖼𝗍𝗂𝗏𝖾,于是它的累加器已经运行过,并已按定义 52 退役了 𝑛,给出 𝜏_𝑛。因此 𝐴′ 满足定义 74 的各子句,而引理 75 给出它们唯一解,故 𝐴 = 𝐴′。□
Lemma 78. (Transposition.) Let 𝐹 𝑡 be well formed and let steps 𝑡 and 𝑡 + 1 act on distinct fibers 𝑚 and 𝑛.
引理 78.(换位。)设 𝐹^𝑡 良构,且第 𝑡 步与第 𝑡 + 1 步作用于不同的纤程 𝑚 与 𝑛。
- 1. If both apply an activation rule, namely L-Begin, L-Iter, or L-Finish, 𝑒𝑚 and 𝑒𝑛 are independent (Definition 65), and step 𝑡 + 1 is applicable at 𝛾𝑡, then step 𝑡 is applicable at the state step 𝑡 + 1 produces from 𝛾𝑡, and the two orders reach the same 𝛾𝑡+2.
若两步都适用激活规则,即 L-Begin、L-Iter 或 L-Finish,𝑒_𝑚 与 𝑒_𝑛 独立(定义 65),且第 𝑡 + 1 步在 𝛾^𝑡 处可适用,则第 𝑡 步在第 𝑡 + 1 步由 𝛾^𝑡 所产生的那个状态下可适用,且两种顺序到达同一个 𝛾^{𝑡+2}。
- 2. If step 𝑡 applies an activation rule at 𝑚, step 𝑡 + 1 an orchestration rule at 𝑛, and step 𝑡 does not instantiate 𝑛, then the same holds of the two.
若第 𝑡 步在 𝑚 处适用激活规则,第 𝑡 + 1 步在 𝑛 处适用编排规则,且第 𝑡 步不实例化 𝑛,则对这两步同样的结论成立。
Proof. For (1), by Table 1 the step of 𝑚 writes 𝜃𝑚 and, within Ψ𝑡 ∈ 𝔐(𝑒𝑚), the tables at keys of 𝑑𝑚 ∪ 𝑝𝑚. It therefore leaves 𝜃𝑛 and 𝑖𝑛 alone, and by clause (2) of Definition 42 leaves the inverse and the continuation that 𝑖𝑛 yields alone as well, so only the premises of step 𝑡 + 1 that mention target𝑛 remain to be checked. Its retirement half cannot fall, no activation rule writing a 𝜏. Its resolution half cannot move either: target𝑛 reads lifecycle states and table domains, of which Ψ𝑡 moves dom(𝜎𝑚) alone (Lemma 59(1)); step 𝑡 + 1 being applicable at 𝛾𝑡 puts every 52 𝑘 ∈ 𝑑𝑛 in dom(𝜎𝑡), and clause (2) of Definition 63 makes the fiber providing such a 𝑘 the only one that can, so 𝑘 ∉ 𝑝𝑚 and no domain at a key of 𝑑𝑛 moves. The same argument in the other direction leaves step 𝑡 applicable. Finally Ψ𝑡 ∈ 𝔐(𝑒𝑚) and Ψ𝑡+1 ∈ 𝔐(𝑒𝑛) commute by clause (1) of Definition 42, and the two edits write control fields of distinct fibers, so the composite is the same in either order.
证明. 对(1),由表 1,𝑚 的那一步写 𝜃_𝑚,并在 Ψ^𝑡 ∈ 𝔐(𝑒_𝑚) 之内写 𝑑_𝑚 ∪ 𝑝_𝑚 各键处的表。因此它不动 𝜃_𝑛 与 𝑖_𝑛,且由定义 42 的第 (2) 子句也不动 𝑖_𝑛 所产出的逆操作与延续,于是只剩第 𝑡 + 1 步中提及 target_𝑛 的那些前提有待检查。它的退役那一半不可能成立,因为没有激活规则会写任意一个 𝜏。它的解析那一半同样不会移动:target_𝑛 读的是生命周期状态与表的定义域,而 Ψ^𝑡 只移动其中的 dom(𝜎_𝑚)(引理 59(1));第 𝑡 + 1 步在 𝛾^𝑡 处可适用,使 𝑑_𝑛 中的每个 𝑘 都落在 dom(𝜎^𝑡) 中,而定义 63 的第 (2) 子句使得提供这样一个 𝑘 的那个纤程是唯一能提供它的,故 𝑘 ∉ 𝑝_𝑚,且 𝑑_𝑛 中任何键处的定义域都不移动。反方向作同样的论证则得到第 𝑡 步可适用。最后,Ψ^𝑡 ∈ 𝔐(𝑒_𝑚) 与 Ψ^{𝑡+1} ∈ 𝔐(𝑒_𝑛) 由定义 42 的第 (1) 子句可交换,而两次编辑写的是不同纤程的控制字段,故复合在两种顺序下相同。
For (2), the orchestration step has Ψ𝑡+1 = idΓ by Table 1, so the two state maps commute outright, and its edit𝑡+1 writes 𝜏𝑛 or dom(𝐹𝛾) at 𝑛 alone, which the activation step neither reads nor writes: the premises of the latter read 𝜃𝑚, 𝑖𝑚, 𝜏𝑚, and target𝑚, and an O-Insert of a fresh 𝑛 moves no target, a fresh fiber providing nothing, whereas an O-Retire or O-Remove of 𝑛 leaves 𝜎𝛾 where it was, 𝑛 being 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 in the one case and unaffected in its table in the other. So step 𝑡 remains applicable. Conversely each premise of the orchestration step is either read at 𝑛, which step 𝑡 does not write, or is one of the two premises of O-Insert that a smaller registry only relaxes, whence its applicability at 𝛾𝑡+1 gives its applicability at 𝛾𝑡; here step 𝑡 not instantiating 𝑛 is what keeps 𝑛 present at 𝛾𝑡 where O-Retire and O-Remove require it. □
对(2),由表 1,编排步有 Ψ^{𝑡+1} = id_Γ,故两个状态映射径直可交换;而它的 edit^{𝑡+1} 只在 𝑛 处写 𝜏_𝑛 或 dom(𝐹_𝛾),激活步既不读也不写它们:后者的前提读 𝜃_𝑚、𝑖_𝑚、𝜏_𝑚 与 target_𝑚,而对一个新引入的 𝑛 作 O-Insert 不移动任何目标视图(一个新纤程不提供任何东西),而对 𝑛 作 O-Retire 或 O-Remove 则让 𝜎_𝛾 保持原样——一种情形下 𝑛 是 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾,另一种情形下它的表不受影响。所以第 𝑡 步仍然可适用。反过来,编排步的每个前提或者在 𝑛 处读(而第 𝑡 步不写 𝑛),或者是 O-Insert 的那两个前提之一,而更小的注册表只会放松它们,因此它在 𝛾^{𝑡+1} 处的可适用性给出它在 𝛾^𝑡 处的可适用性;这里"第 𝑡 步不实例化 𝑛"正是使 𝑛 在 𝛾^𝑡 处存在的原因,而 O-Retire 与 O-Remove 要求它存在。□
Lemma 79. (Deletion.) Let every component be total on its provision ( Definition 76), let the sequence of steps reach a quiescent 𝛾𝑇 , let [𝑏, 𝑢] be an episode of 𝑛 that closes, let no episode of any 𝑚 with 𝑛 ≺ 𝑚 close in the sequence, and let no fiber 𝑛 instantiates during [𝑏, 𝑢] have an episode. Write 𝑅 for the names those instantiations draw. Then deleting the steps that act on 𝑛 in [𝑏, 𝑢], together with every step acting on a name of 𝑅, leaves a sequence of steps reaching a state ≃𝐾-equal to 𝛾𝑇 and ≃-equal to it outside 𝑅.
引理 79.(删除。)设每个组件都在其提供上完全(定义 76),设该步序列到达静止的 𝛾_𝑇,设 [𝑏, 𝑢] 是 𝑛 的一个闭合的幕,设序列中任何满足 𝑛 ≺ 𝑚 的 𝑚 的幕都不闭合,且设 𝑛 在 [𝑏, 𝑢] 期间所实例化的任何纤程都没有幕。记 𝑅 为这些实例化所取用的那些名字。那么删去 [𝑏, 𝑢] 中作用于 𝑛 的那些步,连同作用于 𝑅 中某个名字的每一步,剩下的仍是一个步序列,它到达一个与 𝛾_𝑇 为 ≃_𝐾-相等、且在 𝑅 之外与 𝛾_𝑇 为 ≃-相等的状态。
Proof. The deleted steps leave the state where they found it. Let 𝑡1 < ⋯ < 𝑡𝑙 be the steps of [𝑏, 𝑢] that act on fibers other than 𝑛. Corollary 69 reads
证明. 被删去的那些步把状态留在它们发现它的地方。设 𝑡_1 < ⋯ < 𝑡_𝑙 是 [𝑏, 𝑢] 中作用于 𝑛 以外的纤程的那些步。推论 69 读作
𝛾𝑢+1 ≃𝐾 (Ψ𝑡𝑙 ∘ ⋯ ∘ Ψ𝑡1)(𝛾𝑏)
𝛾_{𝑢+1} ≃_𝐾 (Ψ^{𝑡_𝑙} ∘ ⋯ ∘ Ψ^{𝑡_1})(𝛾_𝑏)whose right side is what the surviving steps of [𝑏, 𝑢] produce on their own, 𝛾𝑏−1 ≃𝐾 𝛾𝑏 and their edits writing control fields of fibers other than 𝑛 that the deletion does not touch. By Table 1 the deleted steps of 𝑛 edit no field but 𝜃𝑛, which Lemma 59(4) restores to 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 at 𝑢 and which it held at 𝛾𝑏−1.
其右端正是 [𝑏, 𝑢] 中幸存的那些步独自产生的结果,因为 𝛾_{𝑏−1} ≃𝐾 𝛾_𝑏,且它们的编辑所写的是 𝑛 以外的纤程的控制字段,而删除并不触及这些字段。由表 1,𝑛 的那些被删去的步只编辑 𝜃_𝑛,而引理 59(4) 在 𝑢 处把它恢复为 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾,并且它在 𝛾 处所持有的也正是这个值。
An invariant carries the suffix. Write 𝛾′𝑡 for the state the surviving steps reach at the point corresponding to 𝑡. We claim, for every 𝑡 > 𝑢, that 𝛾𝑡 ≃𝐾 𝛾′𝑡, that every name of 𝑅 is vestigial at 𝛾𝑡 and absent from 𝛾′𝑡, and that the two states agree on every field of every name outside 𝑅. At 𝑡 = 𝑢 + 1 this is the paragraph above together with Definition 52, which leaves each name of 𝑅 retired by the accumulator that ran at 𝑢, 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 and holding an empty table, the fibers of 𝑅 having no episode by hypothesis. The induction step is Lemma 62(1) applied at each name of 𝑅 in turn: a step acting outside 𝑅 has the same premises at the two states, reaches states again ≃𝐾-equal, and leaves the entries of 𝑅 vestigial. A step acting on a name of 𝑅 is one of the deleted ones, and Lemma 62(2) is why it has to be deleted rather than kept, an O-Retire or O-Remove of an absent name having no fiber to act on; by (1) again such a step moves no field outside 𝑅, so dropping it preserves the invariant. Hence the final states are ≃𝐾-equal, and equal outside 𝑅.
一个不变量承载着后缀。记 𝛾′^𝑡 为幸存的那些步在对应于 𝑡 的那一点所到达的状态。我们断言:对每个 𝑡 > 𝑢,𝛾^𝑡 ≃_𝐾 𝛾′^𝑡,𝑅 的每个名字在 𝛾^𝑡 处都是残留的(vestigial)且不出现在 𝛾′^𝑡 中,且两个状态在 𝑅 之外的每个名字的每个字段上都一致。在 𝑡 = 𝑢 + 1 处,这就是上一段连同定义 52:定义 52 使 𝑅 的每个名字都被在 𝑢 处运行过的那个累加器所退役,处于 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 并持有一个空表,而 𝑅 的各纤程按假设没有幕。归纳步是在 𝑅 的每个名字处依次应用引理 62(1):作用于 𝑅 之外的一步在两个状态下有同样的前提,到达的仍是 ≃_𝐾-相等的状态,并使 𝑅 的各条目保持残留。作用于 𝑅 中某个名字的一步是被删去的那些步之一,而引理 62(2) 说明了它为什么必须被删去而不是被保留:对一个不存在的名字作 O-Retire 或 O-Remove 没有可作用的纤程;再由(1),这样一步不移动 𝑅 之外的任何字段,因此丢掉它保持该不变量。于是最终状态是 ≃_𝐾-相等的,且在 𝑅 之外相等。
No surviving step loses a premise. A step acting on 𝑚 ∉ 𝑅 ∪ {𝑛} reads 𝑛 only through target𝑚(𝛾) or relied𝑚(𝛾). The first depends on 𝑛 when 𝑚 declares a key 𝑛 provides, hence 𝑛 ≺ 𝑚, and when 𝑛 instantiated 𝑚, which puts 𝑚 ∈ 𝑅. In the first case 𝑚’s episode does not close, by hypothesis, so it is open at 𝛾𝑇 , where quiet gives 𝜔𝑚 = target𝑇 𝑚 and Lemma 77 puts its values among the 𝖠𝖼𝗍𝗂𝗏𝖾 fibers, which 𝑛 is not; since a key has at most one possible provider, 𝑛 provided no key 53 of 𝑑𝑚 at 𝑚’s L-Begin either. The second reads 𝑛 only through the values of 𝜔𝑛, and deleting the episode can only make relied false, which relaxes the guard on L-Unload rather than blocking it. What such a step reads of a name of 𝑅 is covered by the invariant. □
没有任何幸存的步丢失前提。作用于 𝑚 ∉ 𝑅 ∪ {𝑛} 的一步只通过 target_𝑚(𝛾) 或 relied_𝑚(𝛾) 读 𝑛。前者在两种情形下依赖 𝑛:𝑚 声明了一个由 𝑛 提供的键(于是 𝑛 ≺ 𝑚),以及 𝑛 实例化了 𝑚(这使得 𝑚 ∈ 𝑅)。在第一种情形下,𝑚 的幕按假设不闭合,所以它在 𝛾_𝑇 处是开放的,而静止给出 𝜔_𝑚 = target^𝑇_𝑚,引理 77 又把它的取值置于诸 𝖠𝖼𝗍𝗂𝗏𝖾 纤程之中,而 𝑛 不在其中;由于一个键至多有一个可能的提供者,在 𝑚 的 L-Begin 处 𝑛 也没有提供 𝑑_𝑚 的任何键。后者只通过 𝜔_𝑛 的取值读 𝑛,而删去该幕只能使 relied 为假,这放松了 L-Unload 上的守卫而不是阻塞它。这样一个步从 𝑅 的某个名字所读到的一切,都由该不变量覆盖。□
Theorem 80. (Confluence.) Let a sequence of steps reach a quiescent 𝛾𝑇 , let every component be total on its provision (Definition 76), and let 𝐴 be as in Definition 74. Then
定理 80.(合流性。)设一个步序列到达静止的 𝛾_𝑇,设每个组件都在其提供上完全(定义 76),且设 𝐴 如定义 74。那么
1. (Canonical form.) 𝛾𝑇 is reached, up to the names whose entries the reduction withdraws, from 𝛾0 by a sequence that takes the same orchestration steps in their original order, those at a fiber the orchestrator inserted preceding every lifecycle step and each of the rest following the step that instantiated the fiber it acts on, and that takes, for an enumeration 𝑛1, …, 𝑛𝑘 of 𝐴 linearizing ⊲, one episode of each 𝑛𝑖 in that order. 2. (Confluence.) Any two such sequences from 𝛾0 taking the same orchestration steps reach states related, after a renaming as in Lemma 61, by the ≃ of Definition 58 and hence by ≃𝐾.
1.(范式。)𝛾_𝑇 可由如下一个序列从 𝛾_0 到达,至多相差那些其条目被该归约抽走的名字:该序列以原来的顺序取同样的编排步,其中作用于编排器所插入的纤程的那些步前于每一个生命周期步,其余每一个则紧跟在实例化了它所作用的那个纤程的那一步之后;并且对 𝐴 的一个线性化 ⊲ 的枚举 𝑛_1, …, 𝑛_𝑘,按该顺序取每个 𝑛_𝑖 的一幕。 2.(合流性。)任何两个这样的、从 𝛾_0 出发并取同样编排步的序列,所到达的状态在按引理 61 作一次重命名之后,由定义 58 的 ≃ 相关,从而由 ≃_𝐾 相关。
Proof. For (1), the episodes of the sequence are of two kinds: those that close and those still open at 𝛾𝑇 , which by quiet𝑇 and Lemma 77 are one episode of each fiber of 𝐴.
证明. 对(1),该序列的各幕有两种:闭合的那些,以及在 𝛾_𝑇 处仍然开放的那些;由 quiet_𝑇 与引理 77,后者是 𝐴 中每个纤程各一幕。
Closing episodes go first, by induction on their number. At each stage pick a closing episode of a fiber 𝑛 that is ⊲-maximal among the fibers whose episodes still close; one exists by Lemma 75 and the finiteness of 𝑁. The three hypotheses of Lemma 79 are then met. No 𝑚 with 𝑛 ≺ 𝑚 has a closing episode, by maximality. And no fiber 𝑛 instantiates during [𝑏, 𝑢] has an episode: such a fiber is retired by the accumulator that ran at 𝑢 (Definition 52) and by Lemma 59(5) stays retired, so its target view is ⊥ and Lemma 77 puts it outside 𝐴, whence it has no episode open at 𝛾𝑇 ; and ⊲ relates it to 𝑛 through its parent pointer, so by maximality it has no closing one either. The lemma removes the episode, together with the steps of the names it instantiated, leaving 𝛾𝑇 where it was up to those names. The measure drops by one, so no closing episode remains.
闭合的幕先行,对它们的数目作归纳。在每个阶段,取那些幕仍闭合的纤程中一个 ⊲-极大的纤程 𝑛 的闭合幕;由引理 75 与 𝑁 的有限性,这样的 𝑛 存在。此时引理 79 的三个假设都满足。由极大性,没有 𝑛 ≺ 𝑚 的 𝑚 拥有闭合的幕。而 𝑛 在 [𝑏, 𝑢] 期间所实例化的纤程也没有幕:这样的纤程被在 𝑢 处运行过的那个累加器所退役(定义 52),且由引理 59(5) 保持退役,因此它的目标视图是 ⊥,引理 77 把它置于 𝐴 之外,于是它在 𝛾_𝑇 处没有开放的幕;而 ⊲ 通过它的父指针把它与 𝑛 关联起来,故由极大性它也没有闭合的幕。该引理移走这一幕,连同它所实例化的那些名字的各步,使 𝛾_𝑇 在这些名字之外保持原样。测度下降一,于是不再剩下任何闭合的幕。
A fiber outside 𝐴 takes no lifecycle step. It has no open episode at 𝛾𝑇 , by Lemma 77 and quiet𝑇 , and no closing one now remains, so it has no episode at all and is 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 throughout; L-Begin is the only rule that applies there, and applying it would open an episode.
𝐴 之外的纤程不取任何生命周期步。由引理 77 与 quiet_𝑇,它在 𝛾_𝑇 处没有开放的幕,而现在也不再剩下闭合的幕,所以它根本没有任何幕,并且自始至终是 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾;L-Begin 是那里唯一可适用的规则,而应用它就会开启一幕。
Orchestration steps go next. An orchestration step at a fiber the orchestrator inserted moves one place earlier past a lifecycle step of a different fiber by Lemma 78(2), which applies because a step of a fiber of 𝐴 instantiates no such name: instantiations draw fresh names, whereas the name here is one an O-Insert of the original sequence introduced. With a lifecycle step of the same fiber there is nothing to exchange, an O-Insert of 𝑛 already preceding every step of 𝑛 and an O-Retire or O-Remove of 𝑛 applying only outside 𝐴, which takes no lifecycle step. Moving each to the front in turn preserves their relative order. An orchestration step at a fiber some activation instantiated cannot go to the front, its premises requiring that fiber to be present, so it stays where the instantiation put it; it acts outside 𝐴 by the paragraph above and therefore commutes with everything between it and the instantiation by the same clause of Lemma 78.
编排步紧随其后。作用于编排器所插入的纤程的一个编排步,可通过引理 78(2) 越过另一个纤程的一个生命周期步而前移一位;该条之所以适用,是因为 𝐴 中某个纤程的一步不会实例化这样的名字:实例化取用的是全新的名字,而这里的名字是原序列中某次 O-Insert 所引入的。与同一个纤程的生命周期步之间则没有可交换的东西:对 𝑛 的 O-Insert 已经前于 𝑛 的每一步,而对 𝑛 的 O-Retire 或 O-Remove 只在 𝐴 之外适用,而 𝐴 之外不取生命周期步。逐个把它们移到最前保持它们的相对顺序。作用于某个激活所实例化的纤程的编排步不能移到最前——它的前提要求该纤程存在——因此它留在实例化把它放下的那个位置;由上一段,它作用于 𝐴 之外,因此按引理 78 的同一子句,它与它和那次实例化之间的一切都可交换。
Episodes are sorted and made contiguous, by induction on |𝐴|. Let 𝑛1 be ⊲-minimal in 𝐴. Then 𝑑𝑛1 = ⌀ and 𝜋𝑛1 = 𝗋𝗈𝗈𝗍, since Definition 74 puts a provider of a key of 𝑑𝑛1 and the fiber instantiating 𝑛1 in 𝐴 while ⊲ puts both below 𝑛1. So target𝑛1 reads no field of another fiber and, no orches- tration step remaining to write 𝜏𝑛1 and no fiber below 𝑛1 remaining to retire it, is constant. Every step acting on 𝑛1 is an activation step, no episode closing, and its remaining premises read 𝜃𝑛1 and 𝑖𝑛1 , which by Table 1 only 𝑛1 writes; each is therefore applicable at every earlier state, and Lemma 78 moves it one place earlier without moving the endpoint. Its independence 54
各幕被排序并变得连续,对 |𝐴| 作归纳。设 𝑛_1 在 𝐴 中 ⊲-极小。那么 𝑑_{𝑛_1} = ⌀ 且 𝜋_{𝑛_1} = 𝗋𝗈𝗈𝗍,因为定义 74 把 𝑑_{𝑛_1} 中某个键的提供者与实例化 𝑛_1 的那个纤程都放在 𝐴 中,而 ⊲ 把两者都放在 𝑛_1 之下。于是 target_{𝑛_1} 不读任何其他纤程的字段,并且由于不再剩下编排步去写 𝜏_{𝑛_1}、也不再剩下 𝑛_1 之下的纤程去退役它,它是常量。作用于 𝑛_1 的每一步都是激活步,没有幕闭合,而它其余的前提读 𝜃_{𝑛_1} 与 𝑖_{𝑛_1},由表 1 只有 𝑛_1 写它们;因此每一步在每个更早的状态都可适用,而引理 78 把它前移一位而不移动端点。它的独立性
hypothesis is met because no fiber entangled with 𝑛1 takes a step in the region crossed — 𝑑𝑛1 = ⌀ leaves 𝑛1 no provider, and a fiber declaring a key of 𝑝𝑛1 has target ⊥ until 𝑛1 is 𝖠𝖼𝗍𝗂𝗏𝖾, so its steps all follow the last step of 𝑛1 — and Lemma 66 makes every other pair independent. The number of steps of other fibers preceding a step of 𝑛1 drops by one at each application, so the episode of 𝑛1 becomes an initial contiguous block. The argument repeats on 𝐴 ∖ {𝑛1} over the suffix that follows the block, where 𝑛1 is 𝖠𝖼𝗍𝗂𝗏𝖾 throughout and takes no further step, so it too contributes a constant target; the providers a later 𝑛𝑗 declares lie in earlier blocks and take no step in the suffix, so the entanglement argument above holds at every stage. The enumeration this produces linearizes ⊲ by construction.
……假设得以满足,是因为与 𝑛1 相互纠缠的纤程在所跨越的区域内不执行任何步骤 —— 𝑑𝑛1 = ⌀ 使 𝑛1 没有提供者,而声明 𝑝𝑛1 某个键的纤程在 𝑛1 变为 𝖠𝖼𝗍𝗂𝗏𝖾 之前其目标为 ⊥,因而它的所有步骤都排在 𝑛1 的最后一步之后 —— 而引理 66 使其他每一对纤程相互独立。在 𝑛1 的某一步之前其他纤程的步数每应用一次就减少一,于是 𝑛1 的这一段情节成为开头处的一个连续块。该论证在 𝐴 ∖ {𝑛1} 上针对该块之后的后缀重复进行,其中 𝑛1 全程为 𝖠𝖼𝗍𝗂𝗏𝖾 且不再执行任何步骤,因而它同样只贡献一个恒定的目标;较晚的 𝑛𝑗 所声明的提供者位于较早的块中,且在后缀中不执行任何步骤,因此上述纠缠论证在每一阶段都成立。由此产生的枚举按构造将 ⊲ 线性化。
For (2), both sequences reduce by (1) to a canonical one, and the two reductions run over the same 𝐴 up to a renaming. Definition 74 reads 𝜏, 𝜋, 𝑑, and 𝑝, of which the last three are written once with a fiber’s entry (Lemma 59(5)), so what has to be seen is that the same names come into existence carrying the same 𝑑, 𝑝, and 𝜋, and that the same names are retired. Insertions the two sequences share by hypothesis. Instantiations they share as well: an activation of a fiber of 𝐴 instantiates, at each of its iterations, the component the iterator names there, which the interleaved steps hold fixed — clause (2) of Definition 42 for a fiber not entangled with the activating one ( Lemma 66), and Lemma 67(3) leaving an entangled fiber no Ψ ≠ idΓ while the activation runs — so the tree of instantiations below an 𝐴-fiber is a function of that fiber’s component; the names those instantiations draw are not shared, and it is here that Lemma 61 is applied, matching the two trees by a bijection. And a retirement is either an orchestration step, shared, or the O-Retire an accumulator takes, which retires exactly the names the same activation instantiated. Two enumerations linearizing ⊲ differ by transpositions of incomparable episodes; Lemma 78 leaves each endpoint unchanged up to the ≃ of Definition 58, and Lemma 60 carries the steps that follow across that relation, so the two canonical sequences agree. With the termination of Theorem 73, the lifecycle relation therefore has unique normal forms. □
对于 (2),两个序列都可由 (1) 归约到一个规范序列,且两次归约在重命名意义下遍历同一个 𝐴。定义 74 读取 𝜏、𝜋、𝑑 和 𝑝,其中后三者连同纤程的条目只写入一次(引理 59(5)),因此需要验证的是:相同的名字带着相同的 𝑑、𝑝 和 𝜋 产生出来,并且相同的名字被退役。插入操作由假设为两个序列所共有。实例化同样共有:𝐴 中某个纤程的一次激活在其每次迭代中实例化迭代器在该处所指名的组件,而交错的各步骤保持该组件不变 —— 对于与正在激活的纤程不相纠缠的纤程,这是定义 42 的第 (2) 款(引理 66),而引理 67(3) 在激活运行期间不给纠缠的纤程留下任何 Ψ ≠ idΓ —— 因此 𝐴 中某个纤程之下的实例化树只是该纤程组件的函数;这些实例化所取用的名字并不共享,正是在此处应用引理 61,通过一个双射把两棵树匹配起来。而一次退役要么是编排步骤(为两者共有),要么是某个累加器执行的 O-Retire,它恰好退役同一次激活所实例化的那些名字。两个将 ⊲ 线性化的枚举之间相差若干不可比较情节的对换;引理 78 使每个端点在定义 58 的 ≃ 下保持不变,而引理 60 把随后的各步骤沿该关系搬运过去,因此两个规范序列一致。结合定理 73 的终止性,生命周期关系因而具有唯一的范式。□
The theorem is what licenses reasoning about a Cordis application as though it were statically assembled. An orchestrator that adds a component, removes it, replaces a provider, and undoes the replacement is guaranteed to arrive at the state it would have obtained by writing the final composition down at the outset, and a component author reasoning about which coeffects are in scope may reason about the quiescent state alone. It also delimits the guarantee: it speaks of the state, not of the emissions the system produced along the way, which is the distinction Section 6.1 draws between an acquisition, tracked inside the boundary, and an emission, which crosses it.
该定理正是许可把 Cordis 应用当作静态组装而成来推理的依据。一个编排器依次添加组件、移除它、替换提供者、再撤销该替换,保证会到达这样的状态:它与一开始就写下最终组合所会得到的状态相同;而组件作者在推理哪些余效应处于作用域内时,可以只针对静止状态进行推理。它也界定了这一保证的边界:它谈论的是状态,而非系统沿途产生的对外发射,这正是第 6.1 节在获取(acquisition,在边界内部被追踪)与发射(emission,跨越边界)之间所作的区分。
4.4. 扩展
4.4. Extensions
We give four extensions of the calculus, each realized by the implementation of Section 5 and each leaving the results of Section 4.3 intact.
我们给出该演算的四项扩展,每一项都由第 5 节的实现所实现,且每一项都使第 4.3 节的结果保持完好。
Asynchrony. The rules are synchronous: the state map Ψ𝑡 of a step ( Definition 58) is applied whole at that step, and the environment moves only between one map and the next. In an asynchronous host the iterations and the inverses yield futures, so a map in flight runs to completion whether or not it is still wanted, and the aborting alternative of L-Divert is not one such a host can offer. Such a host is inertial: of L-Divert it takes the landing alternative alone, and a fiber whose target view turns during an iteration deactivates after that iteration lands, from 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀, holding the inverse it produced. Inertia is therefore a restriction on which alternative of L-Divert a host may take; every result of Section 4.3 quantifies over all sequences 55 of steps and so covers the inertial ones, and Theorem 73 appeals to the aborting alternative nowhere, so a host bound by inertia still quiesces. An inverse in flight calls for no counterpart of inertia: the rules never decline a deactivation, 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 being left by L-Unload alone with no premise on the target view, and a step of another fiber falling within the accumulator’s application commutes with the inverses not yet applied, by the arguments Theorem 68 rests on (Definition 65, Lemma 67, Lemma 66), so an application spread over an interval reaches the state the one-step application reaches, up to ≃𝐾. A deactivation may also chain straight back into an activation: the accumulator runs whatever the target view has become, and from 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 an L-Begin may immediately follow, which is the mutual chaining of reload and unload in the implementation (Section 5.1.3).
异步性。 各规则是同步的:一步的状态映射 Ψ𝑡(定义 58)在该步被整体应用,而环境只在相邻两个映射之间变动。在一个异步宿主中,迭代与各逆操作产生 future,因此在途的映射无论是否仍被需要都会运行至完成,而 L-Divert 的中止分支不是这样的宿主所能提供的。这样的宿主具有惯性:对于 L-Divert,它只取落地分支,而在迭代期间目标视图发生转向的纤程,会在该迭代落地之后从 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 停用,并持有它所产生的逆操作。因此惯性是对宿主可以取 L-Divert 的哪个分支的一种限制;第 4.3 节的每个结果都对所有的步骤序列作量化,因而也覆盖具有惯性的那些序列,而定理 73 在任何地方都不求助于中止分支,所以受惯性约束的宿主仍然会静止。在途的逆操作不需要任何与惯性对应的东西:规则从不拒绝一次停用,𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 只能由 L-Unload 离开,且后者对目标视图没有任何前提,而落在累加器应用期间内的另一纤程的步骤与尚未应用的各逆操作可交换 —— 依据定理 68 所依赖的那些论证(定义 65、引理 67、引理 66)—— 因此跨越一个区间展开的应用所到达的状态,与一步应用所到达的状态在 ≃𝐾 意义下相同。一次停用还可以直接链式地接回一次激活:累加器运行目标视图已经变成的任何内容,而从 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 出发可以紧接一个 L-Begin,这正是实现中重载与卸载的相互链式衔接(第 5.1.3 节)。
Failure. The effects a component installs reach outside the context that tracks them, and a location they reach may refuse: a port already bound, a file that is not there, a peer that does not answer. Refine the iterator so that an iteration may raise an error in place of yielding a triple, Γ → 𝖤𝗂𝗍𝗁𝖾𝗋(Ξ, Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)) for a set Ξ of errors, the witness constraining the 𝖱𝗂𝗀𝗁𝗍 case alone, a raise having nothing to undo. A raise exits 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀 by the route of an aborting L-Divert whose premise on the target view is dropped, the iteration rather than the environment choosing the abort: the fiber routes into 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 with the accumulator built up to the failing iteration, arrives at 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 having installed nothing (Corollary 69), and the exit writes the error as an outcome on the fiber. The outcome withholds re-entry: L-Begin is read as requiring an error-free fiber, so an effect function that raised is not retried against an unchanged environment, and quiet admits a failed fiber whatever its target view; the failure likewise stays on the fiber rather than propagating to its parent, leaving siblings running. A retry is a revision: the reinserted fiber of the Configuration paragraph starts without an outcome. Preservation and recovery hold unchanged, a raise leaving by the same 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 route every deactivation takes. Confluence excludes failed fibers, and has to: whether an iteration raises depends on the state it meets, so one schedule may fail a fiber where another completes it, and the two quiescent states then differ in that fiber’s lifecycle state and, by Corollary 69, in nothing else. The FAILED state of the implementation carries this outcome (Section 5.1.3).
故障。 组件所安装的效应会抵达追踪它们的上下文之外,而它们所抵达的某个位置可能拒绝:端口已被绑定、文件并不存在、对端没有应答。细化迭代器,使一次迭代可以抛出一个错误以取代产出一个三元组,即 Γ → 𝖤𝗂𝗍𝗁𝖾𝗋(Ξ, Γ × (Γ → Γ) × 𝖬𝖺𝗒𝖻𝖾(ℑ)),其中 Ξ 为错误的集合,见证条件只约束 𝖱𝗂𝗀𝗁𝗍 分支,抛出错误没有需要撤销的东西。抛出错误沿着一条中止型 L-Divert 的路径离开 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,该 L-Divert 丢弃对目标视图的前提,由迭代而非环境来选择中止:纤程带着累积到失败那次迭代为止的累加器转入 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀,在 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾 处到达时什么也没有安装(推论 69),退出时把错误作为结果写到纤程上。该结果阻止重新进入:L-Begin 被解读为要求一个无错误的纤程,因此抛错的效应函数不会在环境未变时被重试,而静止状态接受一个失败的纤程而不论其目标视图为何;该失败同样停留在纤程上而不向上传播到其父级,从而使兄弟纤程继续运行。一次重试就是一次修订:Configuration 段中重新插入的纤程在启动时没有结果。保持性与恢复性不变地成立,抛出错误所走的正是每次停用都要走的那条 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 路径。合流性排除失败的纤程,而且必须如此:一次迭代是否抛错取决于它所遇到的状态,因此一个调度可能使某个纤程失败而另一个调度使其完成,此时两个静止状态的差异只在于该纤程的生命周期状态,而由推论 69,再无其他差异。实现中的 FAILED 状态承载的正是这一结果(第 5.1.3 节)。
Isolation. Section 4.1 reads every key at one shared realm and names the relaxation a calculus carrying realms would make: provisions disjoint within a realm rather than outright, each declared key resolved against the realm of the fiber declaring it. For realms fixed at a fiber’s insertion, the relaxed calculus is the present one read at a larger key set. Take the key set to be 𝐾 × 𝑅, each pair (𝑘, 𝑟) carrying the value set 𝒱︀𝑘 and the operations 𝒜︀𝑘 of its underlying key, so that ≃(𝑘,𝑟) is ≃𝑘, a key commutative in the sense of Definition 44 is commutative at every realm, and the coeffect at a pair inherits the witness of its underlying key ( Definition 46). A fiber inserted under a realm table 𝜌 then declares and provides pairs, a key 𝑘 of its interface standing for (𝑘, 𝜌(𝑘)); the last premise of O-Insert, read at pairs, is disjointness within a realm, two fibers providing one key in different realms provide different pairs, and the one shared realm is the diagonal (𝑘, 𝑘), a key outside dom(𝜌) resolving to its own realm ( Definition 24). Keys are as atomic to the rules as names: no rule computes one, inspects its structure, or relates two of them by anything but equality, so the rules and the results of Section 4.3 apply at 𝐾 × 𝑅 as they stand. The reading stops at isolate itself (Definition 25), which reassigns 𝜌 on a running context: under the pairing a reassignment moves a declaration from one pair to another, and 𝑑𝑛 and 𝑝𝑛 are written once with a fiber’s entry ( Lemma 59(5)), so a fiber whose realm turns at runtime is one whose interface has changed, a revision the Configuration paragraph carries. Interception (Definition 26) calls for no extension: metadata adjusts how a binding is used rather than what a key resolves to, is consulted when the binding is accessed, and is discarded with the context that carries it, so no premise reads it and no field of a fiber holds it. 56
隔离。 第 4.1 节在唯一共享的 realm 处读取每个键,并指出了携带 realm 的演算会作出的放松:在同一 realm 内各提供互不相交,而非全局地 outright 不相交,每个被声明的键都针对声明它的纤程所属的 realm 进行解析。对于在纤程插入时就固定的 realm,放松后的演算就是本演算在一个更大的键集上的读法。取键集为 𝐾 × 𝑅,每个对 (𝑘, 𝑟) 携带其底层键的值集 𝒱︀𝑘 与操作集 𝒜︀𝑘,于是 ≃(𝑘,𝑟) 就是 ≃𝑘,定义 44 意义下可交换的键在每个 realm 上都可交换,而某个对上的余效应继承其底层键的见证(定义 46)。在 realm 表 𝜌 下插入的纤程于是声明并提供对,其接口中的键 𝑘 代表 (𝑘, 𝜌(𝑘));O-Insert 的最后一条前提在对的层面来读就是同一 realm 内的不相交性,在不同 realm 中提供同一个键的两个纤程提供的是不同的对,而那个唯一共享的 realm 就是对角线 (𝑘, 𝑘),dom(𝜌) 之外的键解析到它自己的 realm(定义 24)。键对规则而言与名字一样是原子的:没有规则计算键、检查其结构,或以相等之外的任何方式关联两个键,因此第 4.3 节的规则与结果原样适用于 𝐾 × 𝑅。这种读法止步于 isolate 本身(定义 25),它在运行中的上下文上重新赋值 𝜌:在配对之下,一次重新赋值把一条声明从一个对移到另一个对,而 𝑑𝑛 与 𝑝𝑛 连同纤程的条目只写入一次(引理 59(5)),所以一个 realm 在运行时发生转向的纤程,就是一个接口发生了改变的纤程,这是 Configuration 段所承载的一次修订。拦截(定义 26)不需要任何扩展:元数据调整的是某个绑定如何被使用,而不是某个键解析为什么,它在绑定被访问时被查询,并随承载它的上下文一起被丢弃,因此没有前提读取它,纤程的任何字段也不持有它。
Configuration. A component of the implementation takes a configuration, and instantiation binds the payload into the effect function the fiber runs (Section 5.1.3). The calculus therefore carries configuration inside 𝑒: one such component bound to two payloads is two components of Definition 48, differing in their effect functions. The declarative layer of Section 5.2.1 further lets the orchestrator revise a running fiber, replacing its configuration, reassigning its realms, or disabling and later re-enabling it. Each revision is a composite of the rules: disabling is an O-Retire, and every other revision retires the fiber, lets the lifecycle rules deactivate it, removes the entry, children before parent as O-Remove requires, and reinserts the fiber at the same name, with the new effect function, the new realm pairs, or, at a re-enablement, the component unchanged; the name may be reissued, no stale committed view naming a removed fiber (Section 4.3.1). Dependents follow unprompted: the guard on L-Unload orders the withdrawal after the deactivations it causes, and the target-view comparison reactivates each dependent once the reinserted fiber provides the keys again. Re-enablement takes the composite rather than a rule writing 𝜏 back to ⊥, for two reasons: Theorem 80 rests on a fiber an accumulator retires staying retired (Lemma 59(5)), and Definition 53 reads no parent pointer, so a fiber un- retired after the accumulator of the fiber that instantiated it has run would activate with its creator gone. The implementation keeps the same division: re-enabling an entry instantiates a fresh fiber, the entry being the identity that survives revision and the fiber the identity of one enablement. The composite is held to its endpoint rather than to its steps: by Theorem 80 the system quiesces where a load of the revised configuration from scratch would have left it, and that endpoint is what the loader’s shorter routes answer to, a new payload handed to a component that reloads only on a material change, a realm moved without reloading its provider (Section 5.2.1).
配置。 实现中的组件接受一份配置,而实例化把载荷绑定到纤程所运行的效应函数中(第 5.1.3 节)。因此演算把配置承载在 𝑒 之中:同一个这样的组件绑定到两份载荷,就是定义 48 的两个组件,其差异在于各自的效应函数。第 5.2.1 节的声明层进一步允许编排器修订一个运行中的纤程:替换其配置、重新指派其 realm,或禁用它并在稍后重新启用。每一项修订都是规则的复合:禁用就是一次 O-Retire,而其他每种修订都是先退役该纤程,让生命周期规则将其停用,再按 O-Remove 所要求的先子后父的顺序移除条目,然后以同一名字重新插入该纤程,并带上新的效应函数、新的 realm 对,或在重新启用时保持组件不变;名字可以被重新发放,因为不会有陈旧的已提交视图指名一个已被移除的纤程(第 4.3.1 节)。依赖方无需提示便随之调整:L-Unload 上的守卫把撤回排在它所引起的各次停用之后,而一旦重新插入的纤程重新提供那些键,目标视图的比较就会重新激活每个依赖方。重新启用采用复合操作,而不是引入一条把 𝜏 写回 ⊥ 的规则,原因有二:定理 80 依赖于被累加器退役的纤程保持退役状态(引理 59(5)),而定义 53 不读取父指针,因此在实例化它的那个纤程的累加器运行之后才解除退役的纤程,会在其创造者已离去的情况下激活。实现保持同样的划分:重新启用一个条目会实例化一个新的纤程,条目是经受修订而存续的同一性,而纤程是某一次启用的同一性。复合操作被要求达到其端点,而非被要求经由哪些步骤:由定理 80,系统会静止在从头加载修订后的配置所会到达的地方,而正是这个端点是加载器那些更短路径所要应答的东西 —— 把新载荷交给一个只在发生实质变化时才重载的组件,或者移动一个 realm 而不重载其提供者(第 5.2.1 节)。
5. 实现与案例研究
5. Implementation and Case Study
This section presents Cordis, which realizes the formal models of Section 3 as a practical programming abstraction. Cordis is a meta-framework of spatiotemporal composability: unlike application frameworks that target a specific domain (e.g., web routing, ORM, UI rendering), it prescribes no concrete scenario; its sole responsibility is to supply universal dynamic composition semantics. The implementation is layered into three tiers: (1) the core library (Section 5.1) implements the effect and coeffect systems directly; (2) the component loader ( Section 5.2) extends the core with configuration reconciliation and hot module replacement; and (3) appli- cation frameworks such as Koishi (Section 5.3) build domain-specific functionality on top of the former two tiers.
本节介绍 Cordis,它把第 3 节的形式模型实现为一种实用的编程抽象。Cordis 是一个面向时空可组合性的元框架:与面向特定领域(例如 Web 路由、ORM、UI 渲染)的应用框架不同,它不预设任何具体场景;其唯一职责是提供通用的动态组合语义。该实现分为三层:(1) 核心库(第 5.1 节)直接实现效应系统与余效应系统;(2) 组件加载器(第 5.2 节)在核心之上扩展配置调和与模块热替换;(3) 诸如 Koishi(第 5.3 节)这样的应用框架在前两层之上构建特定领域的功能。
5.1. 核心库
5.1. Core Library
Table 2 summarizes the correspondence between theoretical constructs and their runtime counterparts. In particular, we use the runtime names introduced below throughout this section, reserving the theoretical symbols for the formal correspondence. We also write @@name for a framework-internal symbol key, so the brackets in ctx[@@store] denote symbol-keyed access to an opaque slot on the context, rather than indexing into a string-keyed map. 57
表 2 总结了理论构造与其运行时对应物之间的对应关系。特别地,我们在本节全程使用下文引入的运行时名称,而把理论符号保留给形式对应关系。我们还把框架内部的符号键写作 @@name,因此 ctx[@@store] 中的方括号表示以符号键访问上下文上的某个不透明槽位,而不是对字符串键映射做索引。
Theory (Section 3, Section 4) Implementation
| 理论(第 3 节、第 4 节) | 实现 |
|---|---|
| Γ∞ | ctx,头等上下文 |
| 𝛾 ∈ Γ | 上下文树连同运行中的系统所触达的一切 |
| 𝔈Γ, ℑΓ | 返回 / 产出逆操作的 Effect 回调 |
| effectΓ(𝑒) | ctx.effect(callback) |
| Σ, Σiso, Σinter | ctx[@@store]、ctx[@@isolate]、ctx[@@intercept] |
| get(𝑘), set(𝑘, 𝑣) | ctx.get(key)、ctx.set(key, value) |
| isolate(𝑘, 𝑟) | ctx.isolate(key, realm) |
| intercept(𝑘, 𝜈) | ctx.intercept(key, metadata) |
| ⟨𝑑, 𝑝, 𝑒, 𝜋, 𝜎, 𝜏, 𝜃⟩ | fiber,组件在 ℭΓ 中的实例化 |
| dom(𝐹𝛾) | 通过 ctx.registry 枚举 |
| 𝑛 : 𝔑 | fiber.uid |
| 𝑑 : 𝔇Γ | fiber.inject |
| 𝑝 : 𝔓Γ | 组件的 provide |
| 𝑒 : ℑ𝑑∪𝑝Γ | fiber.apply |
| 𝜋 : 𝔑 | fiber.parent.fiber.uid,拥有其被实例化所在上下文的那个纤程 |
| derived realization(定义 23) | fiber.ctx,纤程运行于其中的子上下文 |
| 𝜃(定义 49) | fiber.state,生命周期状态,其中 LOADING 即 𝖱𝖾𝗅𝗈𝖺𝖽𝗂𝗇𝗀,FAILED 承载第 4.4 节的错误结果 |
| recover,累加器 𝑔 | fiber.dispose,累加器 |
| 𝜔(定义 49) | fiber.committed,已提交视图 |
| provider𝑘(𝛾) | 一个其提供者纤程为 ACTIVE 的 Impl |
| target(𝛾, 𝑛) | fiber.target,由 refresh 重算(算法 5),其中 ⊥ 为 INACTIVE |
| 𝖥𝗎𝗍𝗎𝗋𝖾,inertia(第 4.4 节) | fiber.inertia,在途转换的句柄 |
| O-Insert、O-Retire(定义 52) | ctx.use 及其回调的逆操作(算法 4) |
| O-Remove | 纤程从其运行时中被丢弃,uid 被清除 |
| L-Begin、L-Iter、L-Finish | execute 的迭代循环(算法 1) |
| L-Divert | 在迭代边界处守卫失败(算法 1),或重载链入卸载 |
| L-Leave | refresh 把纤程标记为 UNLOADING(第 10 行) |
| L-Unload | unload 及其惯性式链式衔接(算法 5) |
| guard on L-Unload | unload 等待被通知的各依赖方(第 25 行) |
| failure(第 4.4 节) | 记录在纤程上的错误,其目标被置为 ⊥ |
Table 2 | Theory-to-implementation correspondence
表 2 | 理论到实现的对应关系
The remainder of this section builds the core library from the bottom up. Section 5.1.1 realizes revertible effects, the sole primitive through which a context is mutated; Section 5.1.2 realizes reactive coeffects over it; Section 5.1.3 composes both into the component lifecycle; and Section 5.1.4 exposes the context-level operations built on them. 58
本节的其余部分自下而上地构建核心库。第 5.1.1 节实现可撤销效应,这是上下文被变更所经由的唯一原语;第 5.1.2 节在其之上实现响应式余效应;第 5.1.3 节把二者组合为组件生命周期;第 5.1.4 节暴露建立在它们之上的上下文级操作。
5.1.1. 效应追踪
5.1.1. Effect Tracking
This section realizes revertible effects ( Section 3.1). Every context mutation in Cordis flows through a single primitive, ctx.effect: coeffect provision, component instantiation, and every other context-mutating operation reduces to a ctx.effect call, so any operation performed through the context is automatically tracked and reverted upon component unloading. Operationally, ctx.effect is the realization of effectiter Γ (Definition 18): it takes a callback of type ℑΓ and lifts it to ℑ𝜕Γ, yielding a dispose closure that, when invoked, reverts the effect. Cordis accepts both 𝔈Γ and ℑΓ through this one operation (ad-hoc polymorphism); we take the iterator form as representative, since a plain effect function is the degenerate iterator that yields a single inverse. What the operation does not check is the witness that 𝔈∗ Γ carries: the callback supplies an inverse, and that the inverse reverts the effect it accompanies is an obligation on the component author rather than a property the runtime verifies. Theorem 68 is where the calculus appeals to it, and Section 6.1 is where the obligation is delimited. The witness of a coeffect (Definition 46) is unchecked in the same way: that the operations published at a key commute is an obligation on the component providing it, discharged by the representation choice of Section 3.4.2.
本节实现可撤销效应(第 3.1 节)。Cordis 中每一次上下文变更都流经单一原语 ctx.effect:余效应提供、组件实例化,以及所有其他变更上下文的操作,都归约为一次 ctx.effect 调用,因此任何通过上下文执行的操作都会被自动追踪,并在组件卸载时被撤销。在操作语义上,ctx.effect 是 effectiterΓ(定义 18)的实现:它接受一个类型为 ℑΓ 的回调,并将其提升到 ℑ𝜕Γ,产出一个 dispose 闭包,该闭包被调用时撤销该效应。Cordis 通过这一个操作同时接受 𝔈Γ 与 ℑΓ(特设多态);我们以迭代器形式为代表,因为一个普通的效应函数就是只产出单个逆操作的退化迭代器。该操作不检查的是 𝔈∗Γ 所携带的见证:回调提供一个逆操作,而该逆操作确实撤销其所伴随的效应,这是组件作者的一项义务,而非运行时所校验的性质。定理 68 正是演算求助于它的地方,而第 6.1 节则是这项义务被界定清楚的地方。余效应的见证(定义 46)以同样的方式不被检查:在某个键上公布的各操作可交换,是提供它的组件的一项义务,由第 3.4.2 节的表示选择来履行。
Algorithm 1 shows the construction of ctx.effect. We write 𝑓 ∘ 𝑔 for the disposer that runs 𝑓 after 𝑔, and id for the no-op; prepending each new inverse therefore yields LIFO recovery.
算法 1 展示了 ctx.effect 的构造。我们把 𝑓 ∘ 𝑔 记作在 𝑔 之后运行 𝑓 的处置器,把 id 记作空操作;因此把每个新的逆操作前置就得到 LIFO 式的恢复。
Algorithm 1 Effect tracking
算法 1 效应追踪
1 async function execute(callback, guard) 2 iter ← callback() 3 inverse ← id 4 while guard() 5 (value, done) ← await iter.next() 6 if value then inverse ← value ∘ inverse 7 if done then break 8 return inverse 9 function effect(ctx, callback) 10 armed ← true 11 task ← execute(callback, () ↦ armed) 12 async function dispose() 13 if not armed then return 14 armed ← false 15 recover ← await task 16 recover() 17 ctx.dispose ← dispose ∘ ctx.dispose 18 return dispose
1 async function execute(callback, guard)
2 iter ← callback()
3 inverse ← id
4 while guard()
5 (value, done) ← await iter.next()
6 if value then inverse ← value ∘ inverse
7 if done then break
8 return inverse
9 function effect(ctx, callback)
10 armed ← true
11 task ← execute(callback, () ↦ armed)
12 async function dispose()
13 if not armed then return
14 armed ← false
15 recover ← await task
16 recover()
17 ctx.dispose ← dispose ∘ ctx.dispose
18 return disposeThe engine execute drives the callback as an effect iterator ( ℑΓ, Definition 17) and folds the inverse yielded at each step into a single composite. Before each step it consults a caller- supplied guard; once the guard trips, iteration stops and only the inverses accumulated so far remain. This is the step-boundary interruption of Section 4.2.2: the 𝖬𝖺𝗒𝖻𝖾(ℑ) continuation is realized by the iterator’s done flag together with guard.
引擎 execute 把回调当作效应迭代器(ℑΓ,定义 17)来驱动,并把每一步产出的逆操作折叠为单个复合逆操作。在每一步之前,它会查询调用方提供的守卫;一旦守卫触发,迭代停止,只剩此前累积的各逆操作。这正是第 4.2.2 节的步边界中断:𝖬𝖺𝗒𝖻𝖾(ℑ) 延续由迭代器的 done 标志连同守卫一起实现。
ctx.effect is a thin wrapper over execute that adds two things. First, self-disposal: the guard reports the armed flag, and the returned dispose flips armed to false, which simultaneously 59 halts any in-flight iteration and makes recovery fire at most once. Firing twice would apply an inverse at a state no application of the effect produced, where nothing holds it to reverting anything. Second, parent composition: dispose is prepended to the enclosing context’s accumulated inverse ctx.dispose, so a child effect’s inverse is itself an effect on the parent, which is the recursive structure of 𝜕2Γ. The component level (Section 5.1.3) reuses the same execute with a guard that tests the stability of fiber.target instead of armed.
ctx.effect 是 execute 之上的一个薄包装,增加了两点。第一,自我处置:守卫报告 armed 标志,而返回的 dispose 把 armed 翻转为 false,这同时中止任何在途的迭代并使恢复最多触发一次。触发两次会在一个并非由该效应的任何应用所产生的状态上应用一次逆操作,而那里没有任何东西保证它能撤销什么。第二,父级组合:dispose 被前置于外层上下文已累积的逆操作 ctx.dispose,于是子效应的逆操作本身就是父级上的一个效应,这正是 𝜕2Γ 的递归结构。组件层(第 5.1.3 节)复用同一个 execute,只是守卫改为检验 fiber.target 的稳定性而非 armed。
5.1.2. 余效应操作
5.1.2. Coeffect Operations
This section realizes reactive coeffects (Section 3.2). All coeffect operations act on three symbol- keyed slots that each context carries:
本节实现响应式余效应(第 3.2 节)。所有余效应操作都作用于每个上下文所携带的三个符号键槽位:
- • @@store: the value store 𝜎 : (𝑟 : 𝑅) ⇀ 𝒱︀𝑟 from realm symbols to typed values;
@@store:值存储 𝜎 : (𝑟 : 𝑅) ⇀ 𝒱︀𝑟,从 realm 符号到带类型的值;
- • @@isolate: the realm table 𝜌 : Map(𝐾, 𝑅) from coeffect keys to realm symbols;
@@isolate:realm 表 𝜌 : Map(𝐾, 𝑅),从余效应键到 realm 符号;
- • @@intercept: the interception table 𝜄 : (𝑘 : 𝐾) → ℳ︀𝑘 assigning each key its metadata.
@@intercept:拦截表 𝜄 : (𝑘 : 𝐾) → ℳ︀𝑘,为每个键指派其元数据。
The first two compose into the two-layer resolution 𝑘 → 𝜌(𝑘) → 𝜎(𝜌(𝑘)): ctx.get(key) (Algorithm 2) reads the realm symbol 𝜌(𝑘) from @@isolate, then the bound value 𝜎(𝜌(𝑘)) from @@store. The 𝜌 indirection lets isolation redirect a key to an independent binding, whereas @@intercept is consulted only when a binding is accessed, adjusting how it is used rather than what it resolves to. We realize these operations in two parts: (1) provision and notification, which install or withdraw bindings and propagate the change to dependents; and (2) isolation and interception, which reshape how a key resolves.
前两者组合为两层解析 𝑘 → 𝜌(𝑘) → 𝜎(𝜌(𝑘)):ctx.get(key)(算法 2)先从 @@isolate 读出 realm 符号 𝜌(𝑘),再从 @@store 读出绑定值 𝜎(𝜌(𝑘))。𝜌 这一层间接使得隔离可以把一个键重定向到一个独立的绑定,而 @@intercept 只在某个绑定被访问时被查询,调整的是它如何被使用,而不是它解析为什么。我们分两部分实现这些操作:(1) 提供与通知,安装或撤回绑定并把变更传播给依赖方;(2) 隔离与拦截,重塑一个键的解析方式。
Provision and notification. Since set(𝑘, 𝑣) has type 𝔈Σ (Section 3.1), coeffect provision is a ctx.effect call and inherits its automatic tracking and recovery. Algorithm 2 implements ctx.set(key, value), the concrete set(𝑘, 𝑣): the callback binds a value into the store under the realm symbol 𝜌(𝑘), and the returned dispose function removes it. Both installation and removal invoke notify to propagate the change to dependent components.
提供与通知。 由于 set(𝑘, 𝑣) 的类型为 𝔈Σ(第 3.1 节),余效应提供就是一次 ctx.effect 调用,并继承其自动追踪与恢复。算法 2 实现 ctx.set(key, value),即具体的 set(𝑘, 𝑣):回调把一个值绑定到存储中 realm 符号 𝜌(𝑘) 之下,而返回的 dispose 函数将其移除。安装与移除都调用 notify,以把变更传播给依赖的组件。
Algorithm 2 Coeffect operations
算法 2 余效应操作
1 function get(ctx, key) 2 realm ← ctx[@@isolate][key] ▷ 𝜌(𝑘) 3 return ctx[@@store][realm] ▷ 𝜎(𝜌(𝑘)) 4 function set(ctx, key, value) 5 function callback() 6 realm ← ctx[@@isolate][key] ▷ 𝜌(𝑘) 7 ctx[@@store][realm] ← value ▷ 𝜎[𝜌(𝑘) ↦ 𝑣] 8 notify(ctx, [key]) 9 return function() 10 delete ctx[@@store][realm] ▷ 𝜎 ∖ 𝜌(𝑘) 11 notify(ctx, [key]) 12 return ctx.effect(callback)
1 function get(ctx, key)
2 realm ← ctx[@@isolate][key] ▷ 𝜌(𝑘)
3 return ctx[@@store][realm] ▷ 𝜎(𝜌(𝑘))
4 function set(ctx, key, value)
5 function callback()
6 realm ← ctx[@@isolate][key] ▷ 𝜌(𝑘)
7 ctx[@@store][realm] ← value ▷ 𝜎[𝜌(𝑘) ↦ 𝑣]
8 notify(ctx, [key])
9 return function()
10 delete ctx[@@store][realm] ▷ 𝜎 ∖ 𝜌(𝑘)
11 notify(ctx, [key])
12 return ctx.effect(callback)Algorithm 3 propagates each binding change to dependents by testing, for each live fiber, whether a changed key appears in its fiber.inject and resolves to the same realm; if so, it calls refresh (Section 5.1.3) to re-evaluate that fiber against the new state, and it returns the fibers it re-evaluated so that a caller can wait for them. This is the reactive classification of Definition 22: a change that flips satisfaction activates or deactivates the fiber, and refresh’s idempotence 60
算法 3 把每次绑定变更传播给依赖方,其做法是对每个存活的纤程检验:某个发生变更的键是否出现在它的 fiber.inject 中并解析到同一个 realm;若是,则调用 refresh(第 5.1.3 节)针对新状态重新求值该纤程,并返回它所重新求值的那些纤程,以便调用方可以等待它们。这正是定义 22 的反应式分类:一次翻转满足性的变更会激活或停用该纤程,而 refresh 的幂等性
renders a neutral change harmless. The interaction of this re-evaluation with diverse control flows is developed in Section 5.1.3.
使得一次中立的变更无害。这种重新求值与各种控制流的相互作用在第 5.1.3 节中展开。
Algorithm 3 Reactive notification
算法 3 响应式通知
1 function notify(ctx, keys) 2 affected ← ⌀ 3 for fiber in all_fibers do 4 for key in keys do 5 if key ∈ fiber.inject and fiber.ctx[@@isolate][key] = ctx[@@isolate][key] then 6 refresh(fiber) 7 affected ← affected ∪ {fiber} 8 break 9 return affected
1 function notify(ctx, keys)
2 affected ← ⌀
3 for fiber in all_fibers do
4 for key in keys do
5 if key ∈ fiber.inject and fiber.ctx[@@isolate][key] = ctx[@@isolate][key] then
6 refresh(fiber)
7 affected ← affected ∪ {fiber}
8 break
9 return affectedA binding counts as available to a dependent only while the fiber that installed it is ACTIVE, so refresh resolves each declared key against an active provider rather than against the store alone. This is the provided by relation of Definition 53, and it is what makes a withdrawal visible to dependents one step before it happens: a provider that has entered UNLOADING has stopped providing, so its dependents recompute an unsatisfied target view and begin their own teardown while its bindings are all still in place.
一个绑定只有在安装它的纤程处于 𝖠𝖼𝗍𝗂𝗏𝖾 时才算对一个依赖方可用,因此 refresh 是把每个已声明的键对着一个活跃的提供者来解析,而不是仅仅对着存储来解析。这正是定义 53 的"由……提供"(provided by)关系,也正是它使得一次撤回在发生的前一步就对依赖方可见:一个已进入 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 的提供者已经停止提供,于是它的依赖方在它的绑定全都仍在原处的情况下,重算出一个未被满足的目标视图,并开始自己的拆除。
Isolation and interception. The two operations do structurally the same thing: each derives a child context that adjusts one inherited table for key, leaving the parent untouched, so recovery is implicit: discarding the child context suffices, with no explicit inverse to run. ctx.isolate(key, realm) overrides the realm mapping 𝜌 with realm, or a freshly generated symbol by default (realizing isolate, Definition 25), so two contexts that assign different symbols to the same key resolve to independent bindings. ctx.intercept(key, metadata) merges metadata into the interception table 𝜄 (realizing intercept, Definition 27): following that definition, the new metadata is combined with whatever the context already carries for key and takes priority over it.
隔离与拦截。 这两个操作在结构上做的是同一件事:各自派生出一个子上下文,为某个键调整一张继承而来的表,而父上下文保持不变,因此恢复是隐式的:丢弃该子上下文就已足够,无须运行任何显式的逆操作。ctx.isolate(key, realm) 用 realm 覆写 realm 映射 𝜌,默认情况下则用一个新生成的符号(实现 isolate,定义 25),于是把不同符号指派给同一个键的两个上下文解析到相互独立的绑定。ctx.intercept(key, metadata) 把 metadata 并入拦截表 𝜄(实现 intercept,定义 27):依照该定义,新的元数据与上下文已经为该键承载的元数据合并,并优先于后者。
5.1.3. 组件生命周期
5.1.3. Component Lifecycle
A component is instantiated as a fiber by ctx.use. This section gives the fiber (introduced in Section 5.1) operational meaning as the inertial state machine of Section 4.4. Two fields drive the algorithm below: fiber.parent, the parent context of fiber.ctx that forms the component hierarchy (the recursive structure of Γ∞, Section 3.3.1), and fiber.inertia, a handle to the in- flight asynchronous transition (or null if idle).
一个组件由 ctx.use 实例化为一个纤程。本节赋予纤程(在第 5.1 节引入)以第 4.4 节那台惯性状态机的操作含义。驱动下面算法的是两个字段:fiber.parent,即构成组件层次结构的 fiber.ctx 的父上下文(Γ∞ 的递归结构,第 3.3.1 节);以及 fiber.inertia,即在途异步迁移的句柄(若空闲则为 null)。
Algorithm 4 shows component instantiation. A component pairs a coeffect specification component.inject ( 𝑑) with an effect function component.apply; instantiation binds the component’s config into fiber.apply (Line 9), the config-applied effect function ( 𝑒) that the lifecycle then runs. The callback function (Line 2) is the effect tracked in the parent fiber: when executed, it initiates the child’s lifecycle by calling refresh (Algorithm 5); when reverted, it forces the child’s target to ⊥ and triggers unload. This is the instantiation primitive of Defin- ition 52, with callback as its O-Insert and the closure callback returns as its O-Retire: an instantiation is an ordinary tracked effect of the parent, so unloading a parent cascades to its children. 61
算法 4 展示组件的实例化。一个组件把余效应规约 component.inject(𝑑)与效应函数 component.apply 配对;实例化把组件的配置绑定到 fiber.apply(第 9 行),即那个已应用配置的效应函数(𝑒),生命周期随后运行的正是它。回调函数(第 2 行)是追踪在父纤程中的效应:它在被执行时通过调用 refresh(算法 5)启动子纤程的生命周期;它在被撤销时把子纤程的目标强制为 ⊥ 并触发卸载。这就是定义 52 的实例化原语,其中 callback 是它的 O-Insert,而 callback 返回的闭包是它的 O-Retire:一次实例化就是父纤程的一个普通的被追踪效应,因此卸载一个父纤程会级联到它的各个子纤程。
Algorithm 4 Component instantiation
算法 4 组件实例化
1 function use(ctx, component, config) 2 function callback() 3 refresh(fiber) 4 return function() 5 fiber.target ← ⊥ 6 unload(fiber) 7 fiber ← Fiber(parent: ctx, inject: component.inject) 8 fiber.ctx ← ctx[fiber ↦ fiber] 9 fiber.apply ← () ↦ component.apply(fiber.ctx, config) 10 ctx.effect(callback) 11 return fiber
1 function use(ctx, component, config)
2 function callback()
3 refresh(fiber)
4 return function()
5 fiber.target ← ⊥
6 unload(fiber)
7 fiber ← Fiber(parent: ctx, inject: component.inject)
8 fiber.ctx ← ctx[fiber ↦ fiber]
9 fiber.apply ← () ↦ component.apply(fiber.ctx, config)
10 ctx.effect(callback)
11 return fiberAlgorithm 5 realizes the inertial state machine of Section 4.4, in which reload and unload are inertial: once entered, a transition runs to completion before the system responds to a target- state change. It uses two auxiliary lookups over the coeffect store: resolve(inject) returns the bindings the declared keys currently resolve to, and provided(fiber) returns the keys whose binding this fiber installed. The refresh function recomputes fiber.target from the coeffect store and, if the fiber is not already in a transition, initiates either a reload or unload task2. The reload function records the current target and executes the component’s effect function apply. Upon completion, it checks whether the target still matches: if so, the fiber enters ACTIVE; if not (regardless of whether the new target is ⊥ or a different set of providers), it chains into unload. Symmetrically, unload reverts all tracked effects in LIFO order and then either enters INACTIVE or chains into reload. This mutual recursion implements the inertial property: once a transition begins, it completes before any new transition can start.
算法 5 实现第 4.4 节的惯性状态机,其中重载与卸载是惯性的:一旦进入,一次迁移会运行至完成,系统才会响应目标状态的变更。它用到对余效应存储的两种辅助查询:resolve(inject) 返回各已声明键当前所解析到的绑定,而 provided(fiber) 返回其绑定由该纤程所安装的那些键。refresh 函数根据余效应存储重算 fiber.target,并且若该纤程尚未处于某次迁移之中,就启动一个重载任务或卸载任务(脚注 2)。reload 函数记下当前目标并执行组件的效应函数 apply。在完成时,它检验目标是否仍然匹配:若仍匹配,纤程进入 𝖠𝖼𝗍𝗂𝗏𝖾;若不匹配(无论新目标是 ⊥ 还是另一组提供者),它就链式进入卸载。对称地,unload 以 LIFO 序撤销所有被追踪的效应,然后要么进入 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾,要么链式进入重载。这种相互递归实现了惯性性质:一次迁移一旦开始,就先运行至完成,此后任何新的迁移才能开始。
Algorithm 5 Component lifecycle
算法 5 组件生命周期
1 function refresh(fiber) 2 target ← target(𝛾, 𝑛) 3 if target = fiber.target then return 4 fiber.target ← target 5 if fiber.inertia then return 6 if target ≠ ⊥ then 7 fiber.state ← LOADING 8 fiber.inertia ← create_task(reload(fiber)) 9 else 10 fiber.state ← UNLOADING ▷ out of service before any inverse is scheduled 11 fiber.inertia ← create_task(unload(fiber)) 12 async function reload(fiber) 13 target0 ← fiber.target 14 fiber.committed ← resolve(fiber.inject) ▷ commit the view 15 recover ← await execute(fiber.apply, () ↦ fiber.target = target0) 16 fiber.dispose ← recover ∘ fiber.dispose
1 function refresh(fiber)
2 target ← target(𝛾, 𝑛)
3 if target = fiber.target then return
4 fiber.target ← target
5 if fiber.inertia then return
6 if target ≠ ⊥ then
7 fiber.state ← LOADING
8 fiber.inertia ← create_task(reload(fiber))
9 else
10 fiber.state ← UNLOADING ▷ 在任何逆操作被调度之前先停止服务
11 fiber.inertia ← create_task(unload(fiber))
12 async function reload(fiber)
13 target0 ← fiber.target
14 fiber.committed ← resolve(fiber.inject) ▷ 提交视图
15 recover ← await execute(fiber.apply, () ↦ fiber.target = target0)
16 fiber.dispose ← recover ∘ fiber.dispose
17 if fiber.target = target0 then
18 fiber.state ← ACTIVE
19 notify(fiber.ctx, provided(fiber))
20 fiber.inertia ← null
21 else
22 fiber.state ← UNLOADING
23 fiber.inertia ← create_task(unload(fiber))
24 async function unload(fiber)
25 await all(notify(fiber.ctx, provided(fiber)).map(f ↦ f.await())) ▷ 排空依赖方
26 await fiber.dispose()
27 fiber.dispose ← id
28 fiber.committed ← ⊥
29 if fiber.target = ⊥ then
30 fiber.state ← INACTIVE
31 fiber.inertia ← null
32 else
33 fiber.state ← LOADING
34 fiber.inertia ← create_task(reload(fiber))2create_task schedules an async function to run concurrently and returns a handle to it (stored in fiber.inertia). We write it explicitly for language independence: with eager scheduling (e.g., TypeScript promises), the call is implicit and the returned promise is the handle, whereas with lazy scheduling (e.g., Python coroutines, Rust futures) the host must spawn the task for it to progress. 62 17 if fiber.target = target0 then 18 fiber.state ← ACTIVE 19 notify(fiber.ctx, provided(fiber)) 20 fiber.inertia ← null 21 else 22 fiber.state ← UNLOADING 23 fiber.inertia ← create_task(unload(fiber)) 24 async function unload(fiber) 25 await all(notify(fiber.ctx, provided(fiber)).map(f ↦ f.await())) ▷ drain dependents 26 await fiber.dispose() 27 fiber.dispose ← id 28 fiber.committed ← ⊥ 29 if fiber.target = ⊥ then 30 fiber.state ← INACTIVE 31 fiber.inertia ← null 32 else 33 fiber.state ← LOADING 34 fiber.inertia ← create_task(reload(fiber))
脚注 2:create_task 调度一个异步函数并发运行,并返回指向它的句柄(存放在
fiber.inertia中)。我们显式写出它是为了语言无关性:在即时调度下(例如 TypeScript 的 promise),该调用是隐式的,返回的 promise 就是句柄;而在惰性调度下(例如 Python 协程、Rust future),宿主必须亲手派生该任务,它才能推进。
fiber.target is computed by resolving each declared key against the current coeffect store and tupling the uid of the fiber that provides it, so it is a digest of target(𝛾, 𝑛) (Definition 53). Identifying a binding by its provider rather than by its value is what makes a single comparison against the recorded target sufficient: a uid is drawn fresh and never reused, so a provider that is replaced cannot be mistaken for the one it replaced, even when the two provide equal values. Since notify (Section 5.1.2) recomputes the target on every coeffect change, a fiber reloads precisely when one of its declared keys comes to be provided by a different fiber. A provider that overwrites its own binding in place is therefore not observed; a component that wants its replacement to propagate withdraws the binding and installs it afresh.
fiber.target 的计算方式是:把每个已声明的键对着当前的余效应存储解析并把提供它的那个纤程的 uid 组成元组,因此它是 target(𝛾, 𝑛)(定义 53)的一份摘要。用一个绑定的提供者而非它的值来标识该绑定,正是使得与已记录目标的一次比较就足够的原因:uid 是新鲜取出的且从不复用,因此一个被替换掉的提供者不可能与它所替换的那个相混淆,即使二者提供相等的值。由于 notify(第 5.1.2 节)在每次余效应变更时重算目标,一个纤程恰好在它的某个已声明键转由另一个纤程提供时重载。因此,一个就地覆写自身绑定的提供者不会被观测到;一个希望自己的替换能被传播出去的组件,会先撤回该绑定再重新安装它。
The algorithm operates at two complementary levels. At the transition level, reload and unload check the target at completion, enabling inertial chaining across transitions. At the iteration level within each transition, the effect execution (Algorithm 1) checks the target at each iteration boundary, enabling partial rollback within a single transition. These two mechanisms correspond to the inter-transition chaining of Section 4.4 and the intra-transition staleness check that Theorem 71 rests on.
该算法在两个互补的层次上运行。在迁移层次上,reload 与 unload 在完成时检验目标,从而启用跨迁移的惯性链式衔接。在每次迁移内部的迭代层次上,效应执行(算法 1)在每个迭代边界处检验目标,从而在单次迁移内启用部分回滚。这两种机制分别对应于第 4.4 节的迁移间链式衔接,以及定理 71 所依赖的迁移内陈旧性检验。
Three lines carry the coeffect ordering of Theorem 70, and where each of them sits is what makes the ordering hold. reload commits the resolved view at Line 14 and unload discards it only after every inverse has run, so a fiber reads the same bindings for as long as it is loaded, its own teardown included. refresh marks the fiber UNLOADING at Line 10 before the transition task is created, which is the L-Leave step: the fiber stops providing, and the dependents recompute against that before any of its inverses is scheduled. unload then waits at Line 25 for each notified dependent to reach INACTIVE, which is the guard on L-Unload; notify admits a dependent only when its declared key resolves to the same realm symbol as the provider’s, which is the runtime form of the guard’s demand that the dependent see the key from this fiber rather than merely declare it. The wait sits ahead of the whole recovery rather than inside one of the inverses being waited on, since fiber.dispose initiates a fiber’s effects concurrently and a wait placed within one of them would leave the rest unordered. Termination follows Theorem 73: a fiber only ever waits on dependents that have already stopped being satisfiable, and a dependent that is itself 63 a provider waits the same way for its own, so the provider graph is traversed on demand rather than analyzed in advance.
有三行承载着定理 70 的余效应顺序,而它们各自所处的位置正是该顺序成立的原因。reload 在第 14 行提交已解析的视图,而 unload 只在每个逆操作都运行完之后才丢弃它,于是一个纤程只要处于已加载状态,就始终读到同一批绑定,包括它自己的拆除过程在内。refresh 在第 10 行把纤程标记为 UNLOADING,位置在迁移任务被创建之前,这就是 L-Leave 步骤:该纤程停止提供,依赖方据此重算,而这发生在该纤程的任何一个逆操作被调度之前。unload 接着在第 25 行等待每个被通知的依赖方到达 𝖨𝗇𝖺𝖼𝗍𝗂𝗏𝖾,这就是 L-Unload 上的守卫;notify 只在一个依赖方的已声明键解析到与提供者相同的 realm 符号时才接纳它,这正是该守卫"依赖方须从该纤程处看到该键,而不仅仅是声明它"这一要求的运行时形态。这个等待位于整个恢复之前,而不是位于被等待的某个逆操作内部,因为 fiber.dispose 会并发地启动一个纤程的各个效应,而放在其中某一个内部的等待会让其余的无序。终止性由定理 73 得出:一个纤程只会等待那些已经不再可满足的依赖方,而一个本身也是提供者的依赖方会以同样的方式等待它自己的依赖方,因此提供者图是按需遍历的,而不是预先分析的。
5.1.4. 上下文访问
5.1.4. Context Access
The coeffect operations of Section 5.1.2 form a reflective API: a coeffect is written with ctx.set(key, value) and read with ctx.get(key), both keyed by name. Cordis layers a second, more native way to extend and consume the context on top of this reflective API: property access. A component can access a coeffect as the property ctx[key], as if it were native structure of the context, rather than through a method call. In TypeScript, Cordis realizes this with a Proxy whose get trap mediates every property access. Algorithm 6 shows how a context resolves such an access to a coeffect, atop the primitive get of Section 5.1.2.
第 5.1.2 节的各个余效应操作构成一个反射式 API:一个余效应以 ctx.set(key, value) 写入,以 ctx.get(key) 读出,两者都按名字索引。Cordis 在这个反射式 API 之上叠加了第二种、更原生的扩展与消费上下文的方式:属性访问。一个组件可以把一个余效应作为属性 ctx[key] 来访问,仿佛它就是上下文的原生结构,而无须通过一次方法调用。在 TypeScript 中,Cordis 用一个 Proxy 来实现这一点,其 get 陷阱中介着每一次属性访问。算法 6 展示一个上下文如何在第 5.1.2 节的原生 get 之上,把这样一次访问解析为一个余效应。
Algorithm 6 Proxy-mediated context access
算法 6 代理中介的上下文访问
1 function resolve(ctx, key) 2 fiber ← ctx.fiber 3 repeat 4 if key ∈ fiber.committed then return fiber.committed[key] 5 if key ∈ fiber.inject then throw INACTIVE_ACCESS 6 if fiber = root then throw UNDECLARED_ACCESS 7 fiber ← fiber.parent.fiber
1 function resolve(ctx, key)
2 fiber ← ctx.fiber
3 repeat
4 if key ∈ fiber.committed then return fiber.committed[key]
5 if key ∈ fiber.inject then throw INACTIVE_ACCESS
6 if fiber = root then throw UNDECLARED_ACCESS
7 fiber ← fiber.parent.fiberAlgorithm 6 walks the fiber chain upward from the accessing context: at the first fiber whose committed view binds key, the access is authorized and that binding is returned; if the walk reaches a fiber that declares key without having committed it, the fiber is not loaded and the access fails; and if it reaches the root without any declaration, the access is rejected as undeclared. This is where the proxy differs from the bare ctx.get: ctx.get(key) is a lookup against the store that returns the bound value or nothing and never fails, whereas the proxy resolves against the accessing fiber’s own view and enforces the coeffect specification 𝑑 at the point of use. Reading the view rather than the store is also what Theorem 70 rests on, since it is what keeps a dependency readable to a component whose teardown was triggered by that dependency going away.
算法 6 从发起访问的上下文起沿着纤程链向上行走:在其已提交视图绑定了 key 的第一个纤程处,该访问被授权,并返回那个绑定;若行走抵达一个声明了 key 却尚未提交它的纤程,则该纤程并未加载,访问失败;而若它在没有任何声明的情况下抵达根,则该访问被判为未声明而遭拒绝。这正是代理与裸 ctx.get 的分歧所在:ctx.get(key) 是一次针对存储的查询,返回所绑定的值或什么都不返回,且从不失败;而代理则是针对发起访问的纤程自身的视图来解析,并在使用点处强制执行余效应规约 𝑑。读视图而非读存储同样是定理 70 的立足之处,因为正是它使得一个依赖对一个拆除正是由该依赖消失所触发的组件而言仍然可读。
This rejection is a runtime check performed at the point of access. Because a component’s coeffect specification 𝑑 is declared statically, the same violation is in principle detectable at compile time, by resolving each ctx[key] against the declared 𝑑 before execution; Section 6.4 discusses how a host language’s type-level dependency declarations and compile-time metaprogramming can carry out exactly this mediation.
这种拒绝是在访问点处执行的运行时检查。由于一个组件的余效应规约 𝑑 是静态声明的,同样的违规原则上在编译期就可检出:在执行前把每个 ctx[key] 对着已声明的 𝑑 解析一遍即可;第 6.4 节讨论宿主语言的类型级依赖声明与编译期元编程如何能够做到这种中介。
5.2. 组件加载器
5.2. Component Loader
The core library equips component developers with imperative primitives for dynamic composition, such as ctx.effect, ctx.use, and ctx.set. A separate concern arises for application orchestrators, who assemble pre-existing components into a running system and adjust the composition over its lifetime. The component loader addresses this concern by introducing a de- clarative configuration layer: the orchestrator specifies the desired composition as a persistent data structure, and the loader translates changes to this specification into the corresponding imperative fiber operations. 64
核心库为组件开发者配备了用于动态组合的命令式原语,例如 ctx.effect、ctx.use 和 ctx.set。对于应用编排者而言,则出现了一项不同的关切:他们要把既有的组件装配成一个运行中的系统,并在其存续期内调整组合。组件加载器通过引入一个声明式配置层来处理这项关切:编排者把所期望的组合描述为一个持久化数据结构,而加载器把对该规约的变更翻译成相应的命令式纤程操作。
5.2.1. 声明式配置
5.2.1. Declarative Configuration
Section 4 decomposes a running system into fibers, each an instantiation of one component. Everything an instantiation needs can be declared, so an orchestrator can describe a whole system as a declarative configuration: a persistent record that the loader realizes as fibers and keeps in step with them.
第 4 章把一个运行中的系统分解为若干纤程,每个纤程是某个组件的一次实例化。一次实例化所需要的一切都可以被声明,因此一个编排者可以把整个系统描述为一份声明式配置:一条持久化记录,加载器把它实现为若干纤程并与它们保持一致。
Entries. A configuration consists of entries. Each entry specifies a fiber and manages it, and the binding runs in both directions: the loader responds to a change in an entry’s fields by adjusting the fiber, and a component that revises its own configuration or disables itself has the change written back to its entry.
条目。 一份配置由若干条目组成。每个条目指名一个纤程并管理它,而这种绑定是双向的:加载器通过调节该纤程来响应条目字段的变更,而一个修订自身配置或自行停用的组件,则会把该变更写回它自己的条目。
Definition 81. An entry declares a single fiber, recording:
定义 81. 一个条目声明单个纤程,记录如下:
- • id — a stable identifier, used as the reconciliation key when its group’s child list changes;
id —— 一个稳定的标识符,在其所属组的子列表发生变动时用作调和键;
- • url — the URL of the component module to instantiate;
url —— 待实例化的组件模块的 URL;
- • isolate — an isolation annotation applied to the entry’s context;
isolate —— 施加于该条目上下文的隔离标注;
- • intercept — an interception annotation applied to the entry’s context;
intercept —— 施加于该条目上下文的拦截标注;
- • config — the configuration bound into the component to form its effect function apply;
config —— 绑定进组件以形成其效应函数 apply 的那份配置;
- • disabled — whether the entry is administratively turned off.
disabled —— 该条目是否在管理上被关闭。
An entry can serve as a faithful specification because what supports a fiber is exactly what an entry records. The support set of Definition 74 reads 𝜏, 𝜋, 𝑑, and 𝑝 and nothing else, and an entry gives all four: disabled gives 𝜏, the entry’s parent in the tree gives 𝜋, and url selects the component which declares 𝑑 and 𝑝. The fields the support set leaves unread are the fiber’s runtime state, which an instantiation does not need either, and Lemma 77 identifies the support set with the 𝖠𝖼𝗍𝗂𝗏𝖾 fibers of a quiescent state (Definition 53) as far as each component installs every key it declares (Definition 76).
一个条目之所以可以充当一份忠实的规约,是因为支撑一个纤程的东西恰好就是一个条目所记录的东西。定义 74 的支撑集读取 𝜏、𝜋、𝑑 和 𝑝,此外别无他物,而一个条目给出了全部四项:disabled 给出 𝜏,该条目在树中的父给出 𝜋,而 url 选中那个声明了 𝑑 与 𝑝 的组件。支撑集不读的那些字段是纤程的运行时状态,而这也同样不是一次实例化所需要的;引理 77 在"每个组件都安装它声明的每一个键"(定义 76)这一限度内,把支撑集等同于一个静止状态(定义 53)的那些 𝖠𝖼𝗍𝗂𝗏𝖾 纤程。
These entries form a configuration tree that is the authoritative record of what the system loads. An entry may be a leaf mapping to a single fiber, or its component may in turn load further components, making the entry a branch node. Cordis provides components for such grouped and nested loading: @cordisjs/group takes a list of child entries as its configuration and loads them as a subgroup, and @cordisjs/include loads an external configuration file (YAML or JSON) and grafts its entries in as a nested subtree. Both are ordinary components resting on the instantiation primitive of Definition 52 (Algorithm 4), so a nested tree stays within the calculus and the results below hold of it.
这些条目构成一棵配置树,它是系统加载了什么的权威记录。一个条目可以是映射到单个纤程的叶子,也可以是其组件转而加载更多组件的内部节点。Cordis 为这种成组与嵌套的加载提供了相应组件:@cordisjs/group 把一份子条目列表作为它的配置并把它们作为一个子组加载,而 @cordisjs/include 加载一个外部配置文件(YAML 或 JSON)并把它的条目作为一棵嵌套子树嫁接进来。两者都是建立在定义 52 的实例化原语之上的普通组件(算法 4),因此一棵嵌套树仍处在演算之内,下述结果对它同样成立。
Reconciliation. When an entry’s record changes, the loader reconciles incrementally rather than tearing the fiber down and rebuilding it wholesale. Reconciling this way is sound for reasons the metatheory supplies.
调和。 当一个条目的记录发生变化时,加载器增量地加以调和,而不是把该纤程拆除再从整体重建。以这种方式调和之所以可靠,其理由正由元理论提供。
- • Theorem 80 makes the quiescent state a function of the final configuration alone: whatever instantiations and retirements the loader performs on the way, and in whatever order, the system quiesces where a load of the final configuration from scratch would have left it. Which components end up loaded is read off the declarations only as far as each of them installs every key it declares ( Definition 76); a component that declares a key and installs it under some configurations alone is one the loader can still reconcile, but the set of loaded components then answers to those configurations as well.
定理 80 使得静止状态成为仅由最终配置所决定的函数:无论加载器沿途执行了哪些实例化与退役、也无论以何种顺序,系统所静止之处,与从头加载那份最终配置所会到达之处相同。哪些组件最终被加载,只是在"每个组件都安装它声明的每一个键"(定义 76)这一限度内从各声明读出;一个声明了某键却只在某些配置下安装它的组件,仍是加载器所能调和的组件,但此时被加载组件的集合还须应答于那些配置。
- • Theorem 73 proves that the system does quiesce, so a reconciliation is complete once its instantiations and retirements have been issued.
定理 73 证明系统确实会静止,因此一次调和在其各次实例化与退役被发出之后即为完成。
- • Corollary 69 puts a departing fiber’s contribution to the state at nothing, so rebuilding one entry withdraws what its fiber installed and leaves the fibers around it as they were. 65
推论 69 把一个离去纤程对状态的贡献归结为空无,因此重建一个条目会撤回它的纤程所安装的各绑定,而使其周围的纤程保持原样。
- • Theorem 70 lets the entries be instantiated together, with no load order for the orchestrator to arrange: a fiber whose declared keys are not yet provided waits at its L-Begin, and one whose provider leaves is deactivated ahead of it. A dependency therefore constrains when a fiber activates rather than when its module is fetched and evaluated, so the loader loads modules concurrently, where bringing up a large configuration spends its time.
定理 70 让各条目可以被一并实例化,编排者无须安排任何加载顺序:一个其已声明键尚未被提供的纤程在它的 L-Begin 处等待,而一个其提供者已离去的纤程则先于它而被停用。因此,一个依赖约束的是一个纤程何时激活,而不是它的模块何时被获取与求值,于是加载器可以并发地加载各模块,而这正是拉起一份庞大配置所耗时间的去处。
On top of the fiber that an entry declares, the loader dispatches on which of the entry’s fields changed and applies the least disruptive operation for each.
在一个条目所声明的纤程之上,加载器依据该条目的哪些字段发生了变化来分派,并对每种变化施加扰动最小的操作。
- • id, url — rebuilds the entry, since its identity or its component has changed;
id、url —— 重建该条目,因为它的同一性或其组件已经改变;
- • isolate — reassigns the entry’s realms (Algorithm 7);
isolate —— 重新指派该条目的各个 realm(算法 7);
- • intercept — updated in place, as interception metadata is consulted at read time and needs no reload;
intercept —— 就地更新,因为拦截元数据是在读取时被查阅的,不需要重载;
- • config — handed to the component, which decides how to apply the new payload, typically by diffing it against the previous one and reloading only on a material change. In particular, an @cordisjs/group entry’s config is its list of child entries, so it applies the update as a keyed diff over child ids, creating, removing, or updating each child; since updating a surviving child re-enters this same per-field dispatch, group reconciliation and entry update recurse together down the tree;
config —— 交给组件,由它决定如何应用这份新载荷,典型做法是与前一份做 diff,并只在发生实质变化时才重载。特别地,一个
@cordisjs/group条目的 config 就是它的子条目列表,因此它把该更新作为对各子 id 的一次带键 diff 来应用,创建、移除或更新每个子条目;由于更新一个存续的子条目会重新进入同一套逐字段分派,组的调和与条目的更新便沿树一起向下递归; - • disabled — unloads the fiber when set and reloads it when cleared.
disabled —— 置位时卸载该纤程,清除时重载它。
Managed realms. Isolation in the core derives a child context overriding the realm table 𝜌 at one key (Section 5.1.2), which suffices while the context tree stands still. An entry may be moved between groups at runtime, so the loader manages realms of its own, and the isolate field selects between two scoping rules per key. A value of true selects a local realm, private to the entry and tagged by its id, which the entry carries with it wherever it moves; a string selects a global realm shared by every entry naming that string, so moving such an entry changes which entries it shares a binding with rather than which realm it belongs to. A realm is discarded once no entry names it.
受管 realm。 核心中的隔离派生出一个在某个键处覆写 realm 表 𝜌 的子上下文(第 5.1.2 节),这在上下文树静止不动时已经足够。一个条目可能在运行时于各组之间被移动,因此加载器自行管理 realm,而 isolate 字段在每个键上于两条作用域规则之间做选择。取值 true 选中一个局部 realm,它为该条目私有并以其 id 作标记,无论该条目移动到何处都随身携带;一个字符串则选中一个由每个指名该字符串的条目所共享的全局 realm,因此移动这样一个条目所改变的是它与哪些条目共享绑定,而不是它所属的 realm。一旦没有任何条目指名某个 realm,它就被丢弃。
Reassigning an entry’s realms turns on which keys changed realm, whether the entry is itself the provider at a changed key, and which dependents to notify. The middle question is the hard one, since a realm symbol may be shared by several fibers of which only one is the provider. The loader answers it with delimiters: one symbol 𝛿𝑘 per key, under which each context stores a tag of its own. A delimiter is written on a context and inherited by its descendants, so the entry’s tag and the provider’s agree exactly when the two were derived within one isolate scope for 𝑘, which is the case in which the binding at 𝑘 is the entry’s own and has to move with it.
重新指派一个条目的 realm 取决于三个问题:哪些键改变了 realm;在一个改变了的键上,该条目本身是否就是提供者;以及要通知哪些依赖方。中间那个问题是困难的那个,因为一个 realm 符号可能被若干纤程共享,而其中只有一个是提供者。加载器用分隔符来回答它:每个键一个符号 𝛿𝑘,每个上下文在其下存放属于自己的一个标记。分隔符写在一个上下文上并被其后代继承,因此该条目的标记与提供者的标记恰好在二者是在同一个针对 𝑘 的 isolate 作用域内派生出来时一致 —— 而这一情形正是 𝑘 处的绑定属于该条目自己、因而必须随它一起移动的情形。
Algorithm 7 Isolation realm reassignment
算法 7 隔离 realm 重指派
1 function patch_isolation(entry, 𝜌′) 2 𝜌 ← entry.ctx[@@isolate] 3 store ← entry.ctx[@@store] 4 Δ ← {𝑘 | 𝜌(𝑘) ≠ 𝜌′(𝑘)} ▷ keys whose realm changes 5 for 𝑘 in Δ do 6 entry.ctx[𝛿𝑘] ← fresh tag 7 diff[𝑘] ← (𝜌(𝑘), 𝜌′(𝑘), entry.ctx[𝛿𝑘], store[𝜌(𝑘)].fiber.ctx[𝛿𝑘]) 8 entry.ctx[@@isolate] ← 𝜌′ 9 reload(entry.fiber) 10 for 𝑘 in Δ do 66
1 function patch_isolation(entry, 𝜌′)
2 𝜌 ← entry.ctx[@@isolate]
3 store ← entry.ctx[@@store]
4 Δ ← {𝑘 | 𝜌(𝑘) ≠ 𝜌′(𝑘)} ▷ realm 发生变化的各键
5 for 𝑘 in Δ do
6 entry.ctx[𝛿𝑘] ← fresh tag
7 diff[𝑘] ← (𝜌(𝑘), 𝜌′(𝑘), entry.ctx[𝛿𝑘], store[𝜌(𝑘)].fiber.ctx[𝛿𝑘])
8 entry.ctx[@@isolate] ← 𝜌′
9 reload(entry.fiber)
10 for 𝑘 in Δ do11 (𝑠1, 𝑠2, 𝑑1, 𝑑2) ← diff[𝑘] 12 if 𝑑1 = 𝑑2 and store[𝑠1] and not store[𝑠2] then ▷ the binding is the entry’s own 13 store[𝑠2] ← store[𝑠1] 14 delete store[𝑠1] 15 function affected(fiber, 𝑘) 16 (𝑠1, 𝑠2, 𝑑1, 𝑑2) ← diff[𝑘] 17 return fiber.ctx[@@isolate][𝑘] ∈ {𝑠1, 𝑠2} and (fiber.ctx[𝛿𝑘] = 𝑑1) ≠ (𝑑2 = 𝑑1) 18 notify(entry.ctx, Δ, affected) ▷ in place of the realm test of Algorithm 3
11 (𝑠1, 𝑠2, 𝑑1, 𝑑2) ← diff[𝑘]
12 if 𝑑1 = 𝑑2 and store[𝑠1] and not store[𝑠2] then ▷ 该绑定是条目自身的
13 store[𝑠2] ← store[𝑠1]
14 delete store[𝑠1]
15 function affected(fiber, 𝑘)
16 (𝑠1, 𝑠2, 𝑑1, 𝑑2) ← diff[𝑘]
17 return fiber.ctx[@@isolate][𝑘] ∈ {𝑠1, 𝑠2} and (fiber.ctx[𝛿𝑘] = 𝑑1) ≠ (𝑑2 = 𝑑1)
18 notify(entry.ctx, Δ, affected) ▷ 取代算法 3 中的域检验The test turns on one property of delimiters. The tag under 𝛿𝑘 is written on the entry’s context and inherited by every context derived from it, and it is drawn afresh at each reassign- ment, so for a context 𝛾′
该检验取决于分隔符的一个性质。写在 𝛿𝑘 之下的标签被写在该条目的上下文上,并由派生自它的每一个上下文所继承,而且它在每次重新赋值时都被重新取用,因此对于上下文 𝛾′,有
𝛾′[𝛿𝑘] = 𝑑1 ⟺ 𝛾′ is derived from the entry's context (64)
𝛾′[𝛿𝑘] = 𝑑1 ⟺ 𝛾′ 派生自该条目的上下文 (64)
Write own(𝛾′) for that condition, of which 𝑑2 = 𝑑1 is the instance at the provider. The reassignment moves the contexts satisfying own from 𝑠1 to 𝑠2 and leaves the others where they are, and by the loop above it moves the binding to 𝑠2 exactly when the provider satisfies own. A dependent sees the binding while its own realm at 𝑘 is the realm the binding sits in. Where own agrees on the dependent and the provider, both move or neither does, so the dependent sees the binding afterwards exactly when it saw it before. Where own separates them, one side moves and the other stays, so the dependent gains or loses the binding. The inequality is that separation, and the membership test drops the dependents resolving 𝑘 in neither realm, which no part of the move reaches.
记 own(𝛾′) 表示这一条件,𝑑2 = 𝑑1 是它在提供者处的那一实例。重新赋值把满足 own 的上下文从 𝑠1 移到 𝑠2,其余的上下文留在原处;由上面的循环可知,绑定被移到 𝑠2 恰好当提供者满足 own。一个依赖方在它自身于 𝑘 处的域(realm)正是绑定所坐落的那个域时,会看到该绑定。在 own 于依赖方与提供者处一致的情形下,双方要么同时移动,要么同时不移动,因此依赖方之后看到该绑定恰好当它此前看到该绑定。在 own 把二者区分开的情形下,一侧移动而另一侧停留,依赖方于是获得或失去该绑定。那个不等式正是这种区分,而成员检验则丢弃那些在两个域中都不解析 𝑘 的依赖方——此次移动的任何部分都不会触及它们。
5.2.2. 模块热替换
5.2.2. Hot Module Replacement
Hot module replacement (HMR) applies the revertible-effect pattern at the module level: when source files change, typically during development, the system replaces the affected modules in-place without restarting the process. Because a fiber already bounds all of its component’s effects and coeffects, a module that is itself a component can be replaced through fiber operations alone: disposing the old fiber recovers everything the component installed, and a new fiber instantiated from the reloaded module reinstalls it. HMR therefore needs no developer- annotated acceptance boundaries, as opposed to Webpack [48] or Vite [49] HMR.
模块热替换(HMR)把可撤销效应模式应用在模块层级:当源文件发生变化时(通常发生在开发过程中),系统就地替换受影响的模块,而无需重启进程。由于一个纤程已经界定了其组件的全部效应与余效应,一个本身就是组件的模块可以仅通过纤程操作来替换:销毁旧纤程即可回收该组件所安装的一切,而从重载后的模块实例化出的新纤程则把它重新安装回去。因此,与 Webpack [48] 或 Vite [49] 的热替换不同,HMR 不需要开发者标注接受边界。
The @cordisjs/hmr component provides the HMR engine, which operates in three phases.
@cordisjs/hmr 组件提供 HMR 引擎,它分三个阶段工作。
Phase 1: Module classification. The engine takes two inputs: the stashed set (file URLs whose contents have changed since the last reload) and the externals set (modules that cannot be hot-replaced and instead trigger a full restart). Writing get_imports(url) for the modules that url directly imports, it classifies the changes’ dependency subgraph, marking each module accepted or declined:
阶段 1:模块分类。 引擎接受两个输入:暂存集(stash 集,即自上次重载以来内容发生变化的文件 URL)与外部集(无法热替换、只能触发完全重启的模块)。记 get_imports(url) 为 url 直接导入的那些模块,引擎据此对变更的依赖子图进行分类,把每个模块标记为接受(accepted)或拒绝(declined):
Algorithm 8 Module classification
算法 8 模块分类
1 function classify(stashed, externals) 2 accepted ← stashed 3 declined ← externals 4 pending ← ⌀ 5 for url in stashed do 67 6 pending ← pending ∪ (get_imports(url) ∖ (accepted ∪ declined)) 7 repeat 8 progress ← false 9 for url in pending do 10 if get_imports(url) ∩ accepted ≠ ⌀ then 11 accepted ← accepted ∪ {url} 12 pending ← pending ∖ {url} 13 progress ← true 14 else if get_imports(url) ⊆ declined then 15 declined ← declined ∪ {url} 16 pending ← pending ∖ {url} 17 progress ← true 18 else 19 pending ← pending ∪ (get_imports(url) ∖ (accepted ∪ declined)) 20 until not progress 21 declined ← declined ∪ pending 22 return (accepted, declined)
1 function classify(stashed, externals)
2 accepted ← stashed
3 declined ← externals
4 pending ← ⌀
5 for url in stashed do
6 pending ← pending ∪ (get_imports(url) ∖ (accepted ∪ declined))
7 repeat
8 progress ← false
9 for url in pending do
10 if get_imports(url) ∩ accepted ≠ ⌀ then
11 accepted ← accepted ∪ {url}
12 pending ← pending ∖ {url}
13 progress ← true
14 else if get_imports(url) ⊆ declined then
15 declined ← declined ∪ {url}
16 pending ← pending ∖ {url}
17 progress ← true
18 else
19 pending ← pending ∪ (get_imports(url) ∖ (accepted ∪ declined))
20 until not progress
21 declined ← declined ∪ pending
22 return (accepted, declined)Seeded with the imports of the stashed files, the fixed point accepts a module once one of its imports is accepted and declines one once all of its imports are declined; any module left undecided, caught in an import cycle, defaults to declined.
以暂存文件的导入为种子,该不动点一旦某个模块的某个导入被接受就接受它,一旦某个模块的全部导入被拒绝就拒绝它;任何未能判定、陷入导入环的模块默认归入拒绝集。
Phase 2: Stale-entry detection. Using accepted and declined, the engine then filters the component entries down to the stale ones, whose dependency tree reaches a changed module. It walks each entry’s tree with get_dependencies, which collects the transitive imports of a module while respecting declined as a boundary:
阶段 2:陈旧条目检测。 引擎接着利用 accepted 与 declined,把组件条目筛除到只剩下陈旧条目,即依赖树能到达某个已变更模块的那些条目。它用 get_dependencies 遍历每个条目的树,该函数收集一个模块的传递导入,同时把 declined 当作边界来遵守:
Algorithm 9 Stale-entry detection
算法 9 陈旧条目检测
1 function get_dependencies(root, declined) 2 deps ← ⌀ 3 function traverse(url) 4 if url ∈ deps or url ∈ declined then return 5 deps ← deps ∪ {url} 6 for child in get_imports(url) do traverse(child) 7 traverse(root) 8 return deps 9 function detect(entries, accepted, declined) 10 stale_entries ← ⌀ 11 for entry in entries do 12 tree ← get_dependencies(entry.url, declined) 13 if tree ∩ accepted ≠ ⌀ then 14 accepted ← accepted ∪ tree 15 stale_entries ← stale_entries ∪ {entry} 16 return stale_entries
1 function get_dependencies(root, declined)
2 deps ← ⌀
3 function traverse(url)
4 if url ∈ deps or url ∈ declined then return
5 deps ← deps ∪ {url}
6 for child in get_imports(url) do traverse(child)
7 traverse(root)
8 return deps
9 function detect(entries, accepted, declined)
10 stale_entries ← ⌀
11 for entry in entries do
12 tree ← get_dependencies(entry.url, declined)
13 if tree ∩ accepted ≠ ⌀ then
14 accepted ← accepted ∪ tree
15 stale_entries ← stale_entries ∪ {entry}
16 return stale_entriesAn entry is stale exactly when its tree intersects accepted; that tree is then folded into accepted, so every stale module along it is invalidated in the next phase. 68
一个条目恰好在它的树与 accepted 相交时为陈旧;此时这棵树被并入 accepted,于是沿途的每个陈旧模块都会在下一阶段失效。
Phase 3: Transactional reload. Finally, the engine reloads the stale entries. It invalidates the accepted modules’ caches 3, backing up each removed module to enable rollback, then re- imports each stale entry’s component module by its url and swaps in a fresh fiber:
阶段 3:事务性重载。 最后,引擎重载这些陈旧条目。它让 accepted 中各模块的缓存失效 3,为每一个被移除的模块备份以支持回滚,然后按 url 重新导入每个陈旧条目的组件模块,并换入一个全新的纤程:
3On Node.js, this means clearing the caches of both the ES module and CommonJS module systems, since a module imported through the ES loader can appear in both.
脚注 3:在 Node.js 上,这意味着同时清除 ES 模块系统与 CommonJS 模块系统的缓存,因为通过 ES 加载器导入的模块可能同时出现在两者之中。
Algorithm 10 Transactional module reload
算法 10 事务性模块重载
1 function reload(ctx, accepted, stale_entries) 2 backup ← invalidate_caches(accepted) 3 try 4 for entry in stale_entries do 5 entry.fiber.dispose() 6 entry.fiber ← ctx.use(import(entry.url), entry.config) 7 catch error 8 restore_caches(backup) 9 for entry in stale_entries do 10 entry.fiber.dispose() 11 entry.fiber ← ctx.use(backup[entry.url], entry.config) 12 throw error
1 function reload(ctx, accepted, stale_entries)
2 backup ← invalidate_caches(accepted)
3 try
4 for entry in stale_entries do
5 entry.fiber.dispose()
6 entry.fiber ← ctx.use(import(entry.url), entry.config)
7 catch error
8 restore_caches(backup)
9 for entry in stale_entries do
10 entry.fiber.dispose()
11 entry.fiber ← ctx.use(backup[entry.url], entry.config)
12 throw errorThe transactional guarantee ensures that the system never enters a half-reloaded state: if any module fails to import (e.g., due to a syntax error), the caches are restored and every stale entry is rebuilt from backup[entry.url], the previous component whose cache was just restored, undoing the swaps already made.
事务性保证确保系统永远不会进入半重载状态:如果任何模块导入失败(例如由于语法错误),缓存被恢复,并且每个陈旧条目都依据 backup[entry.url] 重建——即那个缓存刚刚被恢复的先前组件——从而撤销已经完成的替换。
5.3. 案例研究:Koishi
5.3. Case Study: Koishi
Koishi is an open-source chatbot application framework built on Cordis 4. Over four years of development, it has accumulated over 4000 community-contributed plugins 5, ranging from instant-messaging (IM) adapters and database drivers to administrative consoles and end- user features. Its scale and diversity make it a representative validation of Cordis’s dynamic composability in a production setting.
Koishi 是一个构建在 Cordis 之上的开源聊天机器人应用框架 4。经过四年多的开发,它积累了 4000 多个社区贡献的插件 5,涵盖即时通讯(IM)适配器、数据库驱动、管理控制台以及面向终端用户的功能。它的规模与多样性使其成为在生产环境中检验 Cordis 动态可组合性的代表性验证。
4Koishi currently uses Cordis v3. This paper presents Cordis v4, which refines the effect and coeffect semantics and redesigns the loader; the core compositional model is shared across both versions.
脚注 4:Koishi 目前使用的是 Cordis v3。本文呈现的是 Cordis v4,它细化了效应与余效应的语义并重新设计了加载器;两个版本共享同一套核心组合模型。
5Koishi uses the term plugin for the concept this paper formalizes as component. 69 host. Koishi routinely performs this operation: an orchestrator disables a plugin from the console and its effects are reverted in place; during development, the HMR engine re-applies edited plugins on save while preserving cache state and live connections elsewhere in the system. Cordis makes such removal not merely possible but effortless for the plugin author. Because effects performed through the context are tracked and their inverses composed automatically (Section 3.1), even an inexperienced author obtains ordered cleanup for a plugin’s context-mediated effects without writing an uninstall path. This achieves the locality of concern whose absence Section 1.2.1 identifies: correctness that would otherwise rest on each author’s diligence is instead discharged once, by the abstraction.
脚注 5:Koishi 用 plugin 一词指称本文形式化为组件(component)的那个概念。
Expressiveness and generality of the meta-framework. Koishi runs as a server-side bot whose every feature is realized as a plugin over the context primitives of Section 5.1; Koishi itself contributes only the chatbot-domain vocabulary. The same model reappears in a wholly different runtime: Koishi’s web console is a second, independent Cordis application whose plugins compose the primitives of the browser and its user interface rather than those of the server. The disparate settings above establish two properties of the model of Section 3. (1) It is expressive: its primitives suffice to carry a complete production system, the host framework supplying only domain vocabulary. (2) It is general: it fixes how effects and coeffects compose while leaving their meaning to each application, and so presupposes neither a particular domain nor a particular runtime.
元框架的表达力与通用性。 Koishi 作为一个服务端机器人运行,其每一项功能都实现为第 5.1 节那些上下文原语之上的插件;Koishi 自身只贡献聊天机器人领域的词汇。同一个模型又重现于一个完全不同的运行时中:Koishi 的网页控制台是第二个独立的 Cordis 应用,其插件组合的是浏览器及其用户界面的原语,而非服务器的原语。上述迥异的场景确立了第 3 节模型的两条性质。(1)它具有表达力:其原语足以支撑一整个生产系统,宿主框架只需提供领域词汇。(2)它是通用的:它规定了效应与余效应如何组合,而把它们的含义留给每个应用自行决定,因而既不预设某个特定领域,也不预设某个特定运行时。
Temporal composability without cognitive overhead. The plugin systems surveyed in Section 1.2.1 cannot unload an individual extension’s effects without restarting the extension
无认知负担的时间可组合性。 第 1.2.1 节所考察的那些插件系统,若不重启扩展宿主就无法卸载单个扩展的效应。Koishi 则日常地执行这一操作:编排器从控制台禁用一个插件,其效应就被就地撤销;在开发过程中,HMR 引擎在保存时重新载入被编辑的插件,同时保留系统其他地方的缓存状态与活动连接。Cordis 使得这种移除不仅可能,而且对插件作者来说毫不费力。由于通过上下文执行的效应会被跟踪、其逆操作会被自动组合(第 3.1 节),即便是缺乏经验的作者,也能为其插件的上下文中介效应获得有序的清理,而无需编写一条卸载路径。这就实现了第 1.2.1 节指出其缺失的关注点局部性:原本要依赖每位作者细心的正确性,如今由抽象一次性地履行了。
Spatial composability across an open ecosystem. In contrast to the plugin systems of Section 1.2.1, where inter-plugin dependencies are largely absent, Koishi’s ecosystem exhibits a genuine dependency topology: IM adapters provide access to each messaging platform, database drivers provide persistent storage, and functional plugins declare these as coeffects and access them. Reconfiguring a provider at runtime, such as switching the storage backend or reconnecting an adapter, reactivates only the dependents whose resolved dependency changed (Section 3.2); a plugin whose dependency is unavailable stays inactive until it appears, without erroring. What the case study substantiates is that this composition holds across independently authored code: a plugin and its dependencies are typically written by different authors who coordinate on nothing beyond the coeffect that connects them, so reactive coeffects keep the assembly consistent across an open ecosystem of independent contributors.
跨越开放生态的空间可组合性。 与第 1.2.1 节那些插件间依赖基本缺席的插件系统相反,Koishi 的生态呈现出真正的依赖拓扑:IM 适配器提供对各消息平台的访问,数据库驱动提供持久化存储,而功能型插件把这些声明为余效应并加以访问。在运行时重新配置某个提供者,例如切换存储后端或重连某个适配器,只会重新激活那些已解析依赖发生变化的依赖方(第 3.2 节);依赖不可用的插件则保持未激活,直到它出现,而不会报错。这一案例研究所证实的是:这种组合在彼此独立编写的代码之间依然成立——一个插件及其依赖通常由不同作者编写,他们除了连接二者的那个余效应之外不做任何协调,因此响应式余效应得以在由独立贡献者组成的开放生态中保持装配的一致性。
Threats to validity. The evidence here is drawn from a single ecosystem in a single host lan- guage, so it cannot separate the merits of the paradigm from those of its TypeScript realization or of Koishi’s particular domain, and it is observational rather than a controlled comparison against an alternative architecture. What the case study establishes is thus an existence-and- adoption result rather than a quantitative one; measuring the abstraction’s overhead and its effect on developer productivity against a baseline remains future work.
有效性威胁。 这里的证据取自单一生态、单一宿主语言,因而无法把范式本身的优点与其 TypeScript 实现或 Koishi 特定领域的优点区分开来;并且它是观察性的,而非与某种替代架构做的受控对比。因此,该案例研究所确立的是一个"存在性与采用"的结论,而非量化的结论;针对某个基线度量该抽象的开销及其对开发者生产力的影响,仍是未来的工作。
6. 讨论
6. Discussion
The formal model and implementation presented in the preceding sections introduce a programming paradigm for dynamic composability. This section examines how the paradigm extends to broader engineering concerns, and discusses the design tensions and open problems.
前几节给出的形式化模型与实现,为动态可组合性引入了一种编程范式。本节考察该范式如何延伸到更广泛的工程关切,并讨论其中的设计张力与开放问题。
6.1. 系统边界
6.1. System Boundary
Every effect in Section 3.1 carries an inverse, and what that inverse amounts to is settled by the system boundary. The boundary divides the environment a system runs against into two parts. (1) A location lies inside when the system is able to modify it exclusively and to restore the state before that modification, so an operation on it is tracked in Γ and can be reverted later. (2) A location lies outside when either ability fails, so an operation on it acts as idΓ and is therefore neither tracked nor reverted. This section develops the properties of this boundary and their consequences for recovery.
第 3.1 节中的每个效应都带有逆操作,而该逆操作究竟是什么,则由系统边界来决定。边界把系统所运行的环境划分为两部分。(1)当系统能够排他地修改某个位置,并能够恢复修改之前的状态时,该位置位于内部,于是对它的操作被记录在 Γ 中,并可在之后被撤销。(2)当上述两种能力中任一失效时,该位置位于外部,于是对它的操作表现为 idΓ,因此既不被跟踪也不被撤销。本节展开这一边界的性质及其对恢复的影响。
Boundaries from coeffects. A coeffect moves the boundary by reifying an external location: it confines every access to that location to a set of operations it provides, each of which it can supply an inverse for, so operations that acted as idΓ come to be tracked in Γ and reverted. The 70 boundary is therefore drawn per location rather than per medium, since both aforementioned abilities are properties of a location, and reification changes how a location is accessed while leaving its medium as it was. For example, a memory region lies inside when the system alone writes it, and outside when other processes write it too; a file lies inside when only the system can reach it, as with a scratch file under a private path, and outside when it is a path other programs read or write. Moving the boundary is itself a trade-off, between whether the environment provides revertible semantics for a location and what supplying those semantics costs on every access. We take up the co-design this suggests in Section 6.7.
源自余效应的边界。 余效应通过使一个外部位置具体化(reify)来移动边界:它把对该位置的每一次访问限定在它所提供的一组操作之内,并且它能为其中每个操作提供逆操作,于是那些原本表现为 idΓ 的操作转而记录在 Γ 中并被撤销。因此,边界是按位置而非按介质来划定的,因为前述两种能力都是位置的属性,而具体化改变的是一个位置被访问的方式,却并不改变其介质。例如,一块内存区域在系统独自写入时位于内部,而当其他进程也写入它时则位于外部;一个文件在只有系统能够触及时位于内部(如私有路径下的临时文件),而当它是其他程序会读写的路径时则位于外部。移动边界本身就是一种权衡:一边是环境是否为某个位置提供可撤销的语义,另一边是每次访问时提供这些语义要付出什么代价。我们将在第 6.7 节讨论由此引出的协同设计问题。
Acquisition and emission. An operation that reaches outside the boundary generally proceeds in two stages. (1) In the acquisition stage, the operation obtains access and installs a record inside the boundary: open installs a descriptor that close removes, malloc reserves a block that free releases, fork starts a child process that kill terminates. The record itself is part of the coeffect that reifies the location, e.g. an entry in a map it keeps, and installing that entry is a revertible effect. That record is at the same time the channel along which data can leave. (2) In the emission stage, the operation pushes data through that channel, as with the bytes a write hands to the file or the datagram a send puts on the wire, and the push acts as idΓ, leaving the data where other parties may read and write it. The two stages therefore fall on opposite sides of the boundary: the acquisition stays inside it, whereas the emission crosses to the outside.
获取与发出。 一个触及边界之外的操作通常以两个阶段进行。(1)在获取阶段,操作获得访问权,并在边界之内安装一条记录:open 安装一个描述符,由 close 移除;malloc 预留一个内存块,由 free 释放;fork 启动一个子进程,由 kill 终止。该记录本身是使该位置具体化的那个余效应的一部分,例如它所维护的一张映射表中的一个条目,而安装该条目就是一个可撤销效应。这条记录同时也是数据得以离开系统的通道。(2)在发出阶段,操作把数据推过该通道,例如 write 交给文件的那些字节,或者 send 放到线路上的那个数据报;这一推送表现为 idΓ,把数据留在其他方可以读写的地方。因此这两个阶段落在边界的两侧:获取阶段留在边界之内,而发出阶段跨到边界之外。
Withholding and compensation. A system that must nonetheless recover from an emission has two approaches available. One is to withhold an emission until the state that produced it is certain to persist, which is the output commit problem of rollback-recovery [50]. The other is compensation [51]: an action that restores the state up to an equivalence the application supplies, coarser than the ≃ of Definition 33, as in deleting a file that was created or refunding a charge that was made. Such actions compose in the same LIFO order as inverses do, so the composition of Section 3.1 transfers to them. The metatheory does not: the commutation of Definition 65 is proved against ≃ and has to be re-established against the coarser one.
抑制与补偿。 一个仍然必须从发出动作中恢复的系统有两种可用的途径。其一是抑制(withhold)一次发出,直到产生它的状态确定会持久存在,这正是回滚恢复中的输出提交问题 [50]。其二是补偿 [51]:一种把状态恢复到应用所提供的某个等价关系之内的动作,该等价关系比定义 33 的 ≃ 更粗,例如删除一个被创建的文件,或退还一笔已发生的扣款。这类动作以与逆操作相同的后进先出(LIFO)顺序组合,因此第 3.1 节的组合机制同样适用于它们。元理论则不然:定义 65 的可交换性是相对于 ≃ 证明的,必须针对那个更粗的等价关系重新建立。
6.2. 服务多路复用
6.2. Service Multiplexing
Dynamic component platforms such as OSGi [52] organize composition around services: units of functionality that a provider publishes under an interface and a consumer binds to. The Cordis coeffect model echoes this notion, with a service corresponding to the interface behind a key. Components that provide a service are its providers, and components that inject a service are its consumers. A single service may be implemented by multiple providers, and this multiplicity can be realized in two forms. (1) Exclusive binding: several implementations share one interface but at most one is bound at a time; the orchestrator selects which implementation is bound, and switching between them requires unloading one provider and loading another, momentarily perturbing every consumer’s dependency. (2) Service broker : a central service that acts as the entrypoint for the interface is injected by both the backing providers and the consumers, so that multiple providers coexist and the broker dispatches each request among them. Compared to exclusive binding, the broker absorbs this perturbation: updating a backing provider leaves the broker in place, so consumers see no change to their dependency and no reload is triggered.
OSGi [52] 这类动态组件平台围绕服务来组织组合:服务是提供者在一个接口之下发布、由消费者绑定到的功能单元。Cordis 的余效应模型呼应了这一观念,其中服务对应于某个键背后的那个接口。提供服务的组件是它的提供者,而注入服务的组件是它的消费者。同一个服务可以由多个提供者实现,而这种多重性可以有两种实现形式。(1)排他绑定:若干实现共用一个接口,但任一时刻最多只有一个被绑定;编排器决定绑定哪个实现,在它们之间切换需要卸载一个提供者并加载另一个,从而瞬时扰动每个消费者的依赖。(2)服务代理(service broker):一个充当该接口入口点的中心服务,由背后的提供者与消费者共同注入,于是多个提供者可以共存,代理在它们之间分发每个请求。与排他绑定相比,代理吸收了这种扰动:更新一个后台提供者时代理仍在原位,因此消费者看不到其依赖的任何变化,也不会触发重载。
The service broker underlies three capabilities: load balancing, rolling updates, and cross- process invocation.
服务代理支撑着三项能力:负载均衡、滚动更新与跨进程调用。
Load balancing. When several providers coexist, the broker distributes requests among them according to a configurable policy (e.g., round-robin, least-loaded, latency-weighted) or 71 an explicit target named by the consumer. Because providers are ordinary components, they can be added or removed to scale capacity up or down; each provider registers with the broker through a revertible effect, so unloading it reverts the registration and drops it from the broker’s routing set automatically.
负载均衡。 当若干提供者共存时,代理按照可配置的策略(例如轮转、最少负载、延迟加权)或由消费者指名的显式目标,在它们之间分发请求。由于提供者就是普通组件,可以增删它们以扩缩容量;每个提供者通过一个可撤销效应向代理注册,因此卸载它会撤销该注册,并自动把它从代理的路由集中摘除。
Rolling updates. Upgrading a service implementation at runtime reduces to a controlled provider transition [53, 54]. To carry out the transition, the new provider is loaded as an additional fiber and registers with the broker; once it becomes ACTIVE, traffic is gradually shifted from the old providers to the new one (e.g., by adjusting selection weights), and the old providers are unloaded once they no longer carry in-flight requests. This provider transition turns what is traditionally an infrastructure-level operation (e.g., container orchestration, blue- green deployment) into an application-level composition pattern.
滚动更新。 在运行时升级一个服务实现,可归结为一次受控的提供者切换 [53, 54]。为完成这次切换,新提供者作为一个额外的纤程被加载,并向代理注册;一旦它变为 ACTIVE,流量就被逐步从旧提供者转移到新提供者(例如通过调整选择权重),而旧提供者在不再承载进行中的请求后即被卸载。这种提供者切换把传统上属于基础设施层级的操作(例如容器编排、蓝绿部署)转变为应用层的组合模式。
Cross-process invocation. The service broker can also be applied across process boundaries [55]. Each process hosts its own Cordis context with local providers; a coordinating component links them, treating each as a remote provider. Cross-process service access is mediated by an RPC mechanism that preserves the interface, making the distribution transparent to consumers. One caveat is that a cross-process call incurs latency and may fail mid-flight, so exposing it synchronously would block the caller. An interface intended to be exposed across processes must therefore be designed against an asynchronous contract.
跨进程调用。 服务代理也可以跨进程边界使用 [55]。每个进程承载自己的 Cordis 上下文与本地提供者;一个协调组件把它们连接起来,将各自视为远程提供者。跨进程的服务访问由一种保持接口的 RPC 机制来中介,使分布对消费者透明。需要注意的一点是,跨进程调用会带来延迟,并可能在调用进行中失败,因此以同步方式暴露它会阻塞调用者。打算跨进程暴露的接口,因而必须按照异步契约来设计。
6.3. 访问控制与沙箱化
6.3. Access Control and Sandboxing
Given an application assembled from independent components, securing the application calls for two complementary mechanisms: (1) constraining what dependencies a component may access, and (2) sandboxing untrusted code from the host environment. Cordis supports the first through dependency declarations and interception; the second requires an external sandbox.
给定一个由彼此独立的组件装配而成的应用,保护该应用需要两种互补的机制:(1)约束一个组件可以访问哪些依赖;(2)把不可信代码与宿主环境沙箱化隔离。Cordis 通过依赖声明与拦截支持前者;后者则需要外部沙箱。
Capability-based access control. The dependency access mechanism ( Section 5.1.4) already constitutes a form of access control over proxy-mediated properties: a component can only access dependencies it has declared; an undeclared access raises an error. This is structurally similar to capability-based security [56–58], where authority is conferred by possession of a reference rather than by ambient authority. The inject declaration acts as a capability request, and the context proxy acts as a capability mediator. Since these requests are declared statically, the complete set of proxy-mediated capabilities a component requires is known before it runs, letting the orchestrator review and approve them at load time rather than discovering accesses as they happen.
基于能力的访问控制。 依赖访问机制(第 5.1.4 节)已经构成了对代理中介属性的一种访问控制形式:组件只能访问它已声明的依赖,未声明的访问会抛错。这在结构上类似于基于能力的安全 [56–58],其中权限由持有一个引用而授予,而非由环境权限(ambient authority)授予。inject 声明充当一次能力请求,而上下文代理充当能力中介者。由于这些请求是静态声明的,一个组件所需的全部代理中介能力在它运行之前就已获知,这使得编排器可以在加载时审查并批准它们,而不必等到访问发生时才去发现。
This mediation generalizes to fine-grained policy through the interception mechanism. Access-control metadata can be carried by contexts or declared by components (Definition 26), and the provider consults it when the dependency is invoked to decide whether a request is permitted. For example, a filesystem dependency may carry metadata declaring which paths a component may read or write, and the provider checks each call against the metadata. Because this interception lives on the context rather than in either party’s code, an orchestrator can adjust it to constrain any component’s access to a dependency without modifying the provider, e.g., granting read-only database access to a community component whereas a core component retains full access. Moreover, since interception affects only how a dependency is invoked, not whether it is satisfied, it can be installed, reconfigured, or removed at runtime without triggering any reload or perturbing the dependency graph. 72
这种中介作用通过拦截机制可以推广为细粒度策略。访问控制元数据既可以由上下文携带,也可以由组件声明(定义 26),提供者在依赖被调用时查阅它,以判定某个请求是否被允许。例如,一个文件系统依赖可以携带元数据,声明某个组件可以读或写哪些路径,提供者则依据该元数据检查每一次调用。由于这种拦截位于上下文之上、而不是位于任何一方代码之中,编排器可以调整它以约束任何组件对某个依赖的访问,而无需修改提供者,例如给某个社区组件授予只读的数据库访问,而某个核心组件保留完全访问权。此外,由于拦截只影响依赖被调用的方式、而不影响它是否被满足,它可以在运行时安装、重新配置或移除,而不会触发任何重载,也不会扰动依赖图。
Sandboxing untrusted components. When a component’s code cannot be trusted, language-level access control is insufficient, since a malicious component with access to the host runtime can reach the underlying objects directly, rendering such checks moot. Sandboxing requires an execution boundary beyond the reach of language-level means, such as software fault isolation [59], a separate language runtime, a sandboxed process, or a virtualized container [60]. Whatever the mechanism, the untrusted component runs in its own sandboxed context and reaches host-provided dependencies through a bridge, generalizing the cross- process invocation of Section 6.2: the same transparency argument renders this bridged access indistinguishable from local injection. On the host side, the bridge is an ordinary fiber whose capabilities can be attenuated by the access control described above.
沙箱化不可信组件。 当组件的代码不可信时,语言级的访问控制就不再充分,因为一个能够访问宿主运行时的恶意组件可以直接触达底层对象,从而使这类检查形同虚设。沙箱化需要一道位于语言级手段之外的执行边界,例如软件故障隔离 [59]、独立的语言运行时、沙箱化进程,或虚拟化容器 [60]。无论采用哪种机制,不可信组件都运行在自己的沙箱化上下文中,并通过一座桥接来访问宿主提供的依赖,这是对第 6.2 节跨进程调用的推广:同样的透明性论证使得这种桥接访问与本地注入无法区分。在宿主一侧,该桥接就是一个普通的纤程,其能力可以通过上文所述的访问控制加以削弱。
6.4. 语言独立性与选择
6.4. Language Independence and Selection
Although Cordis is implemented in TypeScript, the context paradigm is language-agnostic: spatiotemporal composability is defined only by its two composability dimensions, and thus can be realized in any language that meets certain requirements along both. We analyze these requirements along each dimension in turn.
尽管 Cordis 是用 TypeScript 实现的,上下文范式却与语言无关:时空可组合性仅由它的两个可组合性维度定义,因而可以在任何沿这两个维度满足相应要求的语言中实现。我们依次沿每个维度分析这些要求。
Temporal composability. At its most basic, temporal composability requires closures: a revertible effect pairs an action with an inverse, and that inverse must be captured as a value, along with the state it restores, so it can be replayed on teardown. Beyond this, a component’s code and the side effects of loading it must be introducible and retractable at runtime.
时间可组合性。 最基础地看,时间可组合性要求闭包:一个可撤销效应把一个动作与一个逆操作配对,而该逆操作必须连同它所恢复的状态一起被捕获为一个值,从而可以在拆卸时重放。除此之外,组件的代码以及加载它的副作用必须能够在运行时被引入和撤回。
How a language meets this second requirement depends on its execution model. In managed runtimes, this takes the form of a programmatic module registry, where a loaded module can be evicted from the registry and garbage-collected once unreferenced; Node.js, for instance, exposes such a registry.6 Native code exposes no module registry, so introduction and retraction take the form of explicit dynamic linking and unlinking (e.g., dlopen/dlclose on Unix, LoadLibrary/FreeLibrary on Windows) [61], i.e., loading object code into a running process and later detaching it. WebAssembly takes one path or the other depending on its embedder: a module instance is reclaimed by the host’s collector under a managed embedder (e.g., a JavaScript host), or released when a native embedder drops it (e.g., Wasmtime). Across these mechanisms, the revertible effects model treats loading as an effect on the context, with inverses that undo the registration of symbols, types, or handlers the module introduced.
一种语言如何满足这第二项要求,取决于它的执行模型。在托管运行时中,这表现为一个可编程的模块注册表:已加载的模块可以从注册表中逐出,并在不再被引用时被垃圾回收;例如 Node.js 就暴露了这样的注册表。⁶ 原生代码不暴露模块注册表,因此引入与撤回采取显式动态链接与解除链接的形式(例如 Unix 上的 dlopen/dlclose、Windows 上的 LoadLibrary/FreeLibrary)[61],即把目标代码加载进一个正在运行的进程,之后再将其分离。WebAssembly 采取哪一种路径取决于它的嵌入方:在托管型嵌入方(例如 JavaScript 宿主)下,模块实例由宿主的回收器回收;在原生嵌入方(例如 Wasmtime)丢弃它时被释放。在这些机制之上,可撤销效应模型把加载视为作用于上下文的一个效应,其逆操作撤销该模块所引入的符号、类型或处理器的注册。
6CommonJS exposes the module cache via require.cache; ES modules provide no public eviction API, though modules can still be managed through engine-internal interfaces. 73 contexts. The language therefore needs a way to interpose on access transparently, leaving the consumer’s code unchanged, e.g., via JavaScript’s Proxy object [66] or Python’s descriptor protocol (__get__) [67]. Absent such a primitive, runtime reflection [68, 69] can mediate access dynamically, at the cost of type safety and developer experience.
脚注:CommonJS 通过 require.cache 暴露模块缓存;ES 模块没有提供公开的逐出 API,不过模块仍可通过引擎内部接口加以管理。
Spatial composability. Spatial composability requires a mechanism for components to declare their dependencies and for the runtime to provide and inject these dependencies. This reduces to a dependency injection (DI) problem [39], which manifests at two levels that differ across languages: how dependencies are typed and how their access is mediated.
空间可组合性。 空间可组合性要求一种机制,使组件能够声明自己的依赖,并使运行时能够提供并注入这些依赖。这归结为一个依赖注入(DI)问题 [39],它在两个层次上表现出来,而这两个层次在不同语言间存在差异:依赖如何被类型化,以及对依赖的访问如何被中介。
At the type level, the language should provide a way for developers to express well-typed dependency access. A consumer obtains a coeffect by reading its key from the context, so the context type ( Section 3.2.1) must record each key’s coeffect. Typeclasses (Haskell) [62] and traits (Rust) [63] achieve this by letting a provider extend the context type from its own module through an instance or impl [64]. TypeScript’s module augmentation [65] likewise lets a provider module merge declarations into the context type.
在类型层面,语言应当为开发者提供一种表达良类型依赖访问的方式。消费方通过从上下文中读取某个键来获得一个余效应,因此上下文类型(第 3.2.1 节)必须记录每个键所对应的余效应。类型类(Haskell)[62] 与 trait(Rust)[63] 通过让提供者从自己的模块经由 instance 或 impl 扩展上下文类型来做到这一点 [64]。TypeScript 的模块增强(module augmentation)[65] 同样允许提供者模块把声明合并进上下文类型。
At the runtime level, dependency access must be dynamically mediated: the coeffect behind a key may change as providers are loaded and unloaded, and may be resolved differently across
在运行时层面,依赖访问必须被动态地中介:某个键背后的余效应可能随着提供者的加载与卸载而改变,也可能在不同上下文中被以不同方式解析。因此,语言需要一种方式透明地介入访问,而保持消费方的代码不变,例如通过 JavaScript 的 Proxy 对象 [66] 或 Python 的描述符协议(get)[67]。若缺少这样的原语,运行时反射 [68, 69] 可以动态地中介访问,代价是牺牲类型安全与开发者体验。
Across both levels, metaprogramming facilities supply the typing and the mediation together. Annotations [70] and decorators attach metadata to a declaration, which a processor expands into the accessor that mediates access; compile-time metaprogramming (e.g., Rust procedural macros, Scala macros [71], Zig comptime) emits, for each dependency, a typed declaration together with such an accessor, dispensing with a general-purpose interception primitive.
在这两个层次上,元编程设施同时提供了类型化与中介能力。标注(annotation)[70] 与装饰器把元数据附加到声明上,由处理器把它展开为中介访问的访问器;编译期元编程(例如 Rust 的过程宏、Scala 宏 [71]、Zig 的 comptime)则为每个依赖产出一个带类型的声明以及这样一个访问器,从而无需通用的拦截原语。
6.5. 相互依赖与组件粒度
6.5. Mutual Dependencies and Component Granularity
In the reactive coeffect model, a dependency cycle simply leaves the involved components permanently inactive: given two components 𝐴 and 𝐵, if 𝐴 requires a key provided by 𝐵 and 𝐵 a key provided by 𝐴, neither’s satisfaction predicate can ever become true. Unlike deadlock in concurrent systems, which depends on the schedule and must be detected as it happens, this condition is predictable from the dependency declarations alone, so a runtime can report it when components are loaded.
在响应式余效应模型中,一个依赖环只会让所涉及的组件永久停留在未激活状态:给定两个组件 𝐴 和 𝐵,若 𝐴 需要由 𝐵 提供的某个键,而 𝐵 需要由 𝐴 提供的某个键,则二者的满足谓词都不可能变为真。与并发系统中的死锁不同——后者取决于调度,且必须在发生时才被检出——这种情形可以仅从依赖声明中预测出来,因此运行时可以在组件加载时就报告它。
In practice, most apparently mutual dependencies can be decomposed into finer-grained components that eliminate the cycle. Consider two components: a server (providing a network interface) and an access controller (enforcing authorization policies). The two components interact bidirectionally: the access controller mediates requests arriving at the server, and the server exposes an endpoint for modifying access-control policies. A monolithic design would make each component depend on the other. However, the two interaction directions are logically independent concerns. Decomposing them yields four components: server-core, access- control-core, request-mediation (depending on both cores to apply access control to incoming requests), and policy-management (depending on both cores to expose policy modification via the server). Through this approach, the cycle is eliminated because neither core depends on the other; only the integration components depend on both.
在实践中,大多数表面上的相互依赖都可以被分解为更细粒度的组件,从而消除该环。考虑两个组件:一个服务器(提供网络接口)和一个访问控制器(执行授权策略)。这两个组件双向交互:访问控制器中介抵达服务器的请求,而服务器暴露一个用于修改访问控制策略的端点。整体式(monolithic)设计会让两个组件彼此依赖。然而,这两个交互方向在逻辑上是独立的关注点。将它们分解后得到四个组件:server-core、access-control-core、request-mediation(同时依赖两个核心,以把访问控制施加于 incoming 请求)以及 policy-management(同时依赖两个核心,以经由服务器暴露策略修改)。通过这种做法,环被消除了,因为两个核心彼此都不依赖对方;只有集成组件才同时依赖两者。
This decomposition is always possible in principle, since every bidirectional interaction can be factored into independent unidirectional bindings, but it increases the number of components: in the general case, given 𝑛 mutually interacting components, the number of integration components can grow quadratically with 𝑛, since each pair of interacting components may require a distinct component for each direction of interaction. This does not affect correctness or runtime performance (components are lightweight), and finer granularity can be beneficial: users gain the ability to load only the specific integration bindings they need, effectively increasing the system’s composability. However, it may affect developer experience: more components require more configuration, more naming, and more cognitive overhead in understanding the dependency graph.
原则上这种分解总是可行的,因为每一个双向交互都可以被分解为独立的单向绑定,但它会增加组件的数目:在一般情形下,给定 𝑛 个相互交互的组件,集成组件的数目可能随 𝑛 呈二次增长,因为每一对交互的组件都可能为交互的每个方向各需要一个不同的组件。这并不影响正确性或运行时性能(组件是轻量级的),而且更细的粒度反而可能有益:用户得以只加载他们需要的特定集成绑定,从而实际上提高了系统的可组合性。不过,它可能影响开发者体验:更多的组件意味着更多的配置、更多的命名,以及理解依赖图时更多的认知负担。
Mitigating this granularity cost is an engineering concern rather than a theoretical one. Practical strategies include package bundling (i.e., grouping related fine-grained components into a single installable unit), convention-based wiring (i.e., automatically connecting compo- nents whose names or types match a pattern), and scaffold tooling (i.e., generating boilerplate integration components from declarative specifications). These strategies preserve the formal guarantees of the acyclic model while reducing the authoring burden to something closer to the monolithic case. 74
缓解这种粒度开销属于工程问题而非理论问题。实用的策略包括打包(即把相关的细粒度组件归组为单个可安装单元)、基于约定的接线(即自动连接名称或类型匹配某一模式的组件),以及脚手架工具(即从声明式规约生成样板式的集成组件)。这些策略在保留无环模型的形式保证的同时,把编写负担降低到接近整体式方案的量级。
6.6. 依赖类型化与版本管理
6.6. Dependency Typing and Versioning
In the formal model, a dependency link is established purely by key identity: a component providing key 𝑘 satisfies any component declaring 𝑘 in its dependency set. The type family 𝒱︀𝑘 ensures type-level agreement within a single compilation unit, but this guarantee breaks down when components are developed and built independently, which is a common scenario in component ecosystems. This breakage leads to two distinct problems.
在形式模型中,一条依赖链接纯粹由键的同一性建立:一个提供键 𝑘 的组件,满足任何在其依赖集中声明了 𝑘 的组件。类型族 𝒱︀𝑘 保证了单一编译单元内部的类型层面一致,但当组件被独立开发与构建时——这在组件生态系统中是常见情形——这一保证就会失效。这种失效导致两个不同的问题。
Interface drift. A provider may modify the interface associated with 𝑘 (adding fields, changing method signatures, altering behavioral contracts) between versions, while a consumer compiled against an earlier interface continues to declare the same key 𝑘. The dependency is satisfied at the coeffect level ( 𝑘 ∈ dom(𝜎)), yet the runtime value no longer conforms to the consumer’s expectations, leading to type errors, method-not-found failures, or silent behavioral divergence [72].
接口漂移。 提供者可能在不同版本之间修改与 𝑘 相关联的接口(增加字段、改变方法签名、更改行为契约),而针对较早接口编译的消费方继续声明同一个键 𝑘。该依赖在余效应层面是被满足的( 𝑘 ∈ dom(𝜎)),但运行时值已不再符合消费方的预期,从而导致类型错误、方法未找到(method-not-found)失败,或静默的行为偏离 [72]。
Key collision. Two independently developed providers may use the same key name 𝑘 to denote entirely unrelated interfaces. Since key identity alone establishes the link, a consumer expecting one provider’s interface will accept the other’s value without any compatibility check. Unlike interface drift, where the provider and consumer at least share a common lineage, key collision involves no relationship whatsoever between the expected and actual types, making the resulting failures unpredictable and difficult to diagnose.
键冲突。 两个独立开发的提供者可能使用同一个键名 𝑘 来表示完全无关的接口。由于仅凭键的同一性就建立了链接,期望某一个提供者接口的消费方会接受另一个提供者的值,而不做任何兼容性检查。与接口漂移不同——在接口漂移中,提供者与消费方至少共享共同的谱系——键冲突中期望类型与实际类型之间不存在任何关系,这使得由此产生的失败不可预测且难以诊断。
Both problems point to the same gap: the coeffect model provides only nominal linking (by key name) but no versioned or structural linking (by interface compatibility) [73]. We discuss three approaches to the gap, from most infrastructure-coupled to most language-agnostic.
两个问题都指向同一处缺口:余效应模型只提供了名义链接(按键名),而没有提供带版本的或结构化的链接(按接口兼容性)[73]。我们讨论填补该缺口的三种途径,从与基础设施耦合最深到最与语言无关的。
Key namespacing. Extending the key space from 𝐾 to 𝐾 × 𝑃 , where 𝑃 identifies the interface-defining package, eliminates key collision by construction: independently developed interfaces with the same local name occupy distinct keys. This is the most direct solution but also the most coupled: it embeds the package namespace into the formal model itself, making the system dependent on an external package registry for key identity.
键命名空间化。 把键空间从 𝐾 扩展为 𝐾 × 𝑃 ,其中 𝑃 标识定义接口的包,从构造上就消除了键冲突:独立开发但具有相同局部名的接口占据不同的键。这是最直接的解决方案,但也是耦合最重的:它把包的命名空间嵌入形式模型本身,使得系统在键的同一性上依赖于外部的包注册表。
Peer dependencies. A lighter coupling is to declare version constraints through the host- language package manager [74]. This is the approach Cordis currently adopts. Component dependencies are semantically peer dependencies: a component does not bundle its dependencies internally but expects the runtime context to supply them. Package managers with peer dependency support (e.g., npm) can enforce version compatibility: if the version of the package providing a key falls outside a consumer’s declared peer range, the incompatibility is caught at install time rather than surfacing as a runtime failure. However, this approach has two limitations: (1) it depends on providers faithfully adhering to semantic versioning, which is an unenforceable convention; (2) package managers typically resolve each dependency to a single version, which prevents loading components from multiple versions of the same package within one application.
对等依赖。 一种较轻的耦合是通过宿主语言的包管理器声明版本约束 [74]。这正是 Cordis 目前采用的方案。组件依赖在语义上是对等依赖(peer dependency):组件并不在内部捆绑自己的依赖,而是期待运行时上下文来供给它们。支持对等依赖的包管理器(例如 npm)可以强制版本兼容性:若提供某个键的包的版本落在消费方所声明的对等范围之外,这种不兼容会在安装时就被捕获,而不是在运行时才暴露为失败。然而,该方案有两项局限:(1)它依赖提供者忠实地遵循语义化版本(semantic versioning),而这是一个不可强制执行的约定;(2)包管理器通常把每个依赖解析为单一版本,这就使得在一个应用内无法加载来自同一包的多个版本的组件。
Structural compatibility. A fully language-agnostic approach would replace the membership check 𝑘 ∈ dom(𝜎) with a compatibility predicate that verifies the provider’s actual interface structurally subsumes the consumer’s expectation. This is analogous to structural subtyping [75]: a provider satisfies a consumer if the provided interface is a subtype of the re- quired interface. The challenge lies in defining this predicate language-agnostically: structural compatibility is straightforward for record types (width subtyping) but becomes complex for behavioral contracts (e.g., pre/postconditions [76], effect specifications [22]), and undecidable once parametric polymorphism introduces bounded quantification [77]. 75
结构兼容性。 一种完全与语言无关的方案是把成员检查 𝑘 ∈ dom(𝜎) 替换为一个兼容性谓词,该谓词验证提供者的实际接口在结构上包含(subsumes)消费方的期望。这类似于结构化子类型化 [75]:若所提供的接口是所需求接口的子类型,则提供者满足消费方。难点在于如何以与语言无关的方式定义该谓词:结构兼容性对于记录类型(宽度子类型化)而言是直截了当的,但对于行为契约(例如前置/后置条件 [76]、效应规约 [22])就变得复杂,而一旦参数多态引入了受限量化(bounded quantification)[77],则更是不可判定的。
These three approaches address different aspects of the problem. Designing a unified dependency model that combines these approaches while preserving the dynamic composition guarantees of the coeffect model remains an open problem.
这三种途径处理的是问题的不同方面。设计一个统一上述途径、同时保留余效应模型的动态组合保证的依赖模型,仍然是一个开放问题。
6.7. 与语言和操作系统的协同设计
6.7. Co-Design with Languages and Operating Systems
Section 6.4 identifies the minimum a host language must supply for the context paradigm. This section takes up the converse question, what a language or operating system co-designed with the paradigm can offer beyond that minimum.
第 6.4 节指出了宿主语言为实现上下文范式所必须提供的最低限度。本节讨论相反的问题,即一种与该范式协同设计的语言或操作系统,能够在这一最低限度之外提供什么。
Co-design with languages. A language designed around the context paradigm can improve on a library in two respects: the semantics it gives to contexts, and the primitives it gives to effects and coeffects.
与语言协同设计。 一种围绕上下文范式设计的语言可以在两方面优于库实现:它赋予上下文的语义,以及它赋予效应与余效应的原语。
Such a language can make the context implicit again while preserving the context semantics of Section 3.3. An imperative language already runs every statement against an implicit context, and that single context neither tracks effects nor resolves coeffects. The context paradigm instead distinguishes multiple contexts, where an operation either modifies the context it runs against or derives another from it ( Definition 23). An in-place realization modifies the ambient context, just as an imperative language does. A derived realization instead introduces a separate context, for which the language must provide a construct. Making the context implicit brings both an ergonomic and a safety benefit. (1) In a library realization, every function involving effects or coeffects takes the context as an ordinary argument or a receiver, as in Section 5.1. Where the language supplies the context implicitly, functions no longer need to take it. (2) Every context carries its own lifecycle state and committed view ( Section 4.1). A library realization passes a context as an ordinary variable, so a component may reach another component’s context by mistake, through a closure or a global variable. An effect it installs there then leaks out of its own lifecycle, and a coeffect it reads there escapes its dependency specification. Making the context implicit closes both.
这样的语言可以让上下文重新变为隐式的,同时保留第 3.3 节的上下文语义。命令式语言已经在让每条语句作用于一个隐式上下文,而那唯一的上下文既不追踪效应,也不解析余效应。上下文范式则区分出多个上下文,其中一个操作要么修改它所作用于的上下文,要么从它派生出另一个上下文(定义 23)。原地进行的实现修改环境上下文,正如命令式语言所做的那样。派生式的实现则另行引入一个独立的上下文,为此语言必须提供相应的构造。把上下文变为隐式同时带来人体工学与安全性两方面的好处。(1)在库实现中,每个涉及效应或余效应的函数都把上下文当作普通参数或接收者,如第 5.1 节所示。当语言隐式地供给上下文时,函数就不再需要接受它。(2)每个上下文都携带自己的生命周期状态与已提交视图(第 4.1 节)。库实现把上下文当作普通变量传递,因此一个组件可能通过闭包或全局变量误触另一个组件的上下文。它在那里安装的效应于是泄漏出自身的生命周期,而它在那里读取的余效应则逃逸出自己的依赖规约。把上下文变为隐式可以同时堵住这两处。
Such a language can also make effects and coeffects known to its compiler. (1) For effects, an effect iterator ( Definition 17) allocates a closure at every step to hold the inverse together with the state it restores. With syntax for performing an effect, a compiler can emit a single state machine for the whole iteration and hold those inverses in its frame. (2) For coeffects, the coeffect specification can be admitted into the type system, with two benefits. First, a dependency cycle is reported at compile time instead of being left to the runtime (Section 6.5). Second, a dependency can be compared by the structure of its type rather than by key identity alone, as row types do [28], which is type-level support for the structural compatibility of Section 6.6.
这样的语言还可以让效应与余效应为其编译器所知。(1)对于效应,一个效应迭代器(定义 17)在每一步都分配一个闭包,用以保存逆操作以及它所恢复的状态。有了执行效应的语法,编译器可以为整个迭代过程生成单一的状态机,并把那些逆操作保存在它的栈帧中。(2)对于余效应,余效应规约可以被接纳进类型系统,带来两点好处。其一,依赖环在编译期就被报告,而不必留给运行时(第 6.5 节)。其二,一个依赖可以按其类型的结构来比较,而不只是按键的同一性,正如行类型(row types)所做的那样 [28],这为第 6.6 节的结构兼容性提供了类型层面的支持。
Co-design with operating systems. Section 1.2.3 observes a coarse-grained substitute for dynamic composability, where the operating system supplies temporal composability at the granularity of a process, and the container orchestrator above it supplies spatial composability at the granularity of a service. An operating system co-designed with the paradigm would support fine-grained composition, by making the coeffect specification a component declares the whole of what it can reach, and by providing its own resources as coeffects.
与操作系统协同设计。 第 1.2.3 节观察到动态可组合性的一种粗粒度替代方案:操作系统以进程为粒度提供时间可组合性,而其上的容器编排器以服务为粒度提供空间可组合性。一种与该范式协同设计的操作系统则会支持细粒度组合,办法是让组件所声明的余效应规约成为它能够触达的全部内容,并把自身的资源作为余效应提供出来。
Such an operating system can supply the sandbox that Section 6.3 defers to a mechanism outside the language. It does so by bounding a component to the dependencies it declares, supplying them when the component is loaded and leaving nothing else reachable from within it, as a WebAssembly module receives its imports from its embedder at instantiation [78]. It 76 can also provide the coeffect isolation and interception of Section 3.2.3 as abilities of its own, binding a key differently for each component and mediating the accesses it supplies.
这样的操作系统可以提供第 6.3 节交由语言外部机制去完成的沙箱。它做到这一点的方式是把一个组件限制在它所声明的依赖之内:在组件被加载时供给这些依赖,并使组件内部无法触达其他任何东西,正如一个 WebAssembly 模块在实例化时从它的嵌入方接收自己的导入 [78] 一样。它还可以把第 3.2.3 节的余效应隔离与拦截作为自身的能力来提供,为每个组件以不同方式绑定键,并中介它所供给的访问。
Such an operating system can also provide its own resources as coeffects. A resource lying outside the boundary is made revertible where the runtime records each acquisition against the component that made it ( Section 6.1), and every runtime keeps a record of its own. An operating system that provides the resource as a coeffect keeps that record once, since it is the party that hands the resource out and can attribute it to the component that asked. Memory and file descriptors are the immediate candidates, and tracking them for the sake of recovery has been done at the kernel interface [79, 80]. Furthermore, an operating system can make revertible some of the operations Section 6.1 can only withhold or compensate for. A system that performs a write to persistent storage transactionally can roll it back [81], and one built on copy-on-write or immutable storage reaches an earlier state by moving a pointer [82, 83].
这样的操作系统还可以把自身的资源作为余效应提供出来。位于边界之外的一项资源在运行时把每次获取记录到做出该获取的组件名下时成为可撤销的(第 6.1 节),而每个运行时都各自保留一份记录。一个把资源作为余效应提供的操作系统只保留这一份记录,因为它正是发放资源的一方,并且能够把它归属于提出请求的那个组件。内存与文件描述符是最直接的候选者,而为恢复之目的追踪它们的工作已经在内核接口处做过 [79, 80]。此外,操作系统还可以让第 6.1 节只能扣留或补偿的某些操作变为可撤销。一个以事务方式执行对持久化存储写入的系统可以将该写入回滚 [81],而一个构建于写时复制或不可变存储之上的系统则可以通过移动一个指针来回到较早的状态 [82, 83]。
7. 相关工作
7. Related Work
Dynamic composability intersects several established research areas. We survey the most relevant lines of work and distinguish our contribution from each of them.
动态可组合性与若干已有研究领域相交。我们考察其中最相关的几条工作线索,并把本文的贡献与它们逐一区分开来。
7.1. 效应与余效应系统
7.1. Effect and Coeffect Systems
Section 2 reviewed effects and coeffects as the theoretical pillars underlying our work. We first situate the monadic effect systems now common in industrial practice, then survey three research lines that extend effects and coeffects in directions relevant to Cordis: recasting algebraic effects as capabilities, giving effects a reversible semantics, and unifying effects and coeffects under a single graded discipline.
第 2 节已经把效应与余效应作为本文工作的理论支柱加以回顾。我们首先定位当前工业实践中常见的单子式效应系统,然后考察三条在相关方向上扩展效应与余效应的研究线索:把代数效应改写为能力、赋予效应一种可逆语义,以及把效应与余效应统一在单一的分级纪律之下。
Monadic effect systems. One family of libraries encodes effects in the type systems of existing general-purpose languages, representing them as monadic values that a runtime executes. ZIO in Scala [84] models a computation as ZIO[R,E,A] and Effect-TS in TypeScript [85] as Effect<A,E,R>, a generic type whose parameters describe its result, its typed errors, and the services its context must supply; the fp-ts library [86] encodes the same error and requirement channels through Reader-based monad transformers. Two traits separate these systems from Cordis. First, the tracking costs a monadic embedding: a program obtains it only by being written inside the effect type, whereas Cordis tracks effects as an overlay over ordinary host code. Second, a requirement is discharged by interpretation, an installed service that supplies its operations, and when that service is withdrawn what its operations performed remains in place; Cordis instead pairs each effect with an inverse and re-resolves requirements as providers come and go (Section 3.1, Section 3.2).
单子式效应系统。 有一族库把效应编码进现有通用语言的类型系统,把它们表示为由运行时执行的单子值。Scala 中的 ZIO [84] 把一个计算建模为 ZIO[R,E,A],TypeScript 中的 Effect-TS [85] 建模为 Effect,其泛型参数分别描述它的结果、它的带类型错误,以及它的上下文必须供给的服务;fp-ts 库 [86] 则通过基于 Reader 的单子变换器编码同样的错误通道与需求通道。有两个特征把这些系统与 Cordis 区分开来。其一,这种追踪需要单子式嵌入为代价:程序只有写在效应类型内部才能获得它,而 Cordis 则是把效应作为覆盖在普通宿主代码之上的一层来追踪。其二,一项需求是通过解释来解除的,即由一个已安装的服务来供给它的操作,而当该服务被撤销时,它的操作所造成的结果依然留在原地;Cordis 则把每个效应与一个逆操作配对,并随着提供者的来去重新解析需求(第 3.1 节、第 3.2 节)。
Algebraic effects as capabilities. Algebraic effects (Section 2.1) make effect operations visi- ble to the type system. The extension closest to our work is Brachthäuser et al.‘s Effekt language, which reinterprets effect types as capabilities [87, 88]: an effect type expresses what a computation requires from its context rather than what side effects it may produce. This perspective, like ours, treats the context as a mediator of capabilities. Cordis and Effekt differ in two respects. (1) In purpose, algebraic effects make effects visible to enable modular interpretation, giving one operation many handler semantics, whereas Cordis makes them visible to enable tracking and reversion, pairing every context transformation with an inverse. (2) In setting, Effekt disciplines effects statically at the type level, defaulting to scope-based reasoning in which capabilities are 77 second-class and confined to their lexical scope, and recovering first-class use through boxing, which lifts that restriction by tracking captured capabilities in types; Cordis instead disciplines effects at runtime, aiming at complete resource recovery on component removal; Section 6.7 takes up what a language that made the context second class in this sense would offer.
代数效应作为能力。 代数效应(第 2.1 节)使效应操作对类型系统可见。与本文工作最接近的扩展是 Brachthäuser 等人的 Effekt 语言,它把效应类型重新解释为能力 [87, 88]:一个效应类型表达的是一个计算从其上下文中所需求的东西,而不是它可能产生的副作用。这一视角与本文一样,把上下文视为能力的中介者。Cordis 与 Effekt 在两方面不同。(1)在目的上,代数效应让效应可见是为了支持模块化解释,使同一个操作可以有许多种处理器语义;而 Cordis 让效应可见是为了支持追踪与回退,把每一次上下文变换都与一个逆操作配对。(2)在场景上,Effekt 在类型层面静态地规训效应,默认采用基于作用域的推理,其中能力是二等的、被限定在其词法作用域内,并通过装箱(boxing)来恢复一等使用——装箱通过在类型中追踪被捕获的能力而解除该限制;Cordis 则是在运行时规训效应,目标是在组件移除时实现完整的资源回收;第 6.7 节讨论了在这种情况下把上下文变为二等的语言会提供什么。
Reversible effect semantics. A parallel line gives effects a reversible semantics rather than an interpretive one. Heunen et al. [89] model side effects in a reversible setting by adapting Hughes’ arrows to dagger arrows and inverse arrows, capturing effects such as serialization and mutable store whose operations admit inverses. This is the formal account closest to our revertible effects: both pair each effect with the means to undo it rather than discharging it through a handler. The two differ in where reversibility resides, and in how much of it they demand. Heunen et al. work in a denotational, categorical setting where reversibility is a global property, guaranteed by construction since every computation is invertible, and the inverse is two-sided and recovered from the categorical structure. Cordis tracks inverses at runtime and requires less of them: not that the whole computation be reversible, but that each atomic effect admit a one-sided inverse, supplied by the caller at the point of application rather than derived, from which the inverse of any composite follows by composition (Section 3.1).
可逆效应语义。 并行的另一条线索赋予效应一种可逆语义,而不是解释性语义。Heunen 等人 [89] 通过把 Hughes 的箭头改造为匕首箭头(dagger arrows)与逆箭头,在可逆设定下对副作用建模,捕捉诸如序列化与可变存储这类其操作承认逆操作的效应。这是与本文可撤销效应最接近的形式刻画:二者都把每个效应与撤销它的手段配对,而不是通过处理器把它解除。两者的区别在于可逆性所处的位置,以及对可逆性的要求程度。Heunen 等人工作在指称式的、范畴论的设定中,其中可逆性是一种全局性质,由构造保证,因为每个计算都是可逆的,而且逆操作是双侧的,从范畴结构中恢复出来。Cordis 则在运行时追踪逆操作,并且对它们要求更少:并不要求整个计算可逆,而只要求每个原子效应承认一个单侧逆操作,由调用方在应用点提供而非推导出来,任何复合的逆操作则由复合得出(第 3.1 节)。
Graded types as unified effects and coeffects. Orchard et al. [90] proposed graded modal types as an umbrella notion encompassing both effect reasoning (via graded monads) and coeffect reasoning (via graded comonads), realized in the Granule language, demonstrating that a single type system can track both what a computation does and what it needs; more recent work extends coeffects to imperative Java-like languages [91, 92] and to call-by-push-value [93]. All of these operate at the type level: effects and coeffects are static annotations checked at compile time over lexically fixed scopes. Our contribution is orthogonal to this analysis: we lift the same two notions to runtime mechanisms, which lets Cordis handle dynamic composition. Temporal retraction and spatial dependency are re-resolved as the set of loaded components evolves, instead of being settled once over a fixed program text.
分级类型作为统一的效应与余效应。 Orchard 等人 [90] 提出把分级模态类型作为一个涵盖效应推理(经由分级单子)与余效应推理(经由分级余单子)的总括性概念,并在 Granule 语言中实现,证明单一类型系统可以同时追踪一个计算做什么以及它需要什么;更近期的工作把余效应扩展到命令式的类 Java 语言 [91, 92] 以及 call-by-push-value [93]。这些工作全部运作在类型层面:效应与余效应是编译期在词法上固定的作用域上检查的静态标注。本文的贡献与这一分析正交:我们把同样的两个概念提升为运行时机制,这使得 Cordis 能够处理动态组合。时间撤回与空间依赖随着已加载组件集合的演进而被重新解析,而不是在固定的程序文本上一次确定。
7.2. 编程范式
7.2. Programming Paradigms
The context paradigm ( Section 3.3) mediates every effect and coeffect through an explicit context. This section first compares it with the functional and imperative treatments of side effects, and then with two established paradigms, one sharing our terminology and the other our treatment of crosscutting concerns.
上下文范式(第 3.3 节)通过一个显式的上下文中介每一个效应与余效应。本节首先把它与函数式和命令式对副作用的处理方式相比较,然后与两种既有范式相比较,一种与我们共享术语,另一种与我们共享对横切关注点的处理方式。
Explicit threading and implicit mutation. Purely functional languages make effects explicit in types: the State monad 𝑆 → (𝐴, 𝑆) [23] threads the environment through every computation, securing equational reasoning at the cost of the threading itself, every function on the call path accepting and returning the state whether or not it touches it; the monadic effect systems of Section 7.1 are this pole in industrial form. Imperative languages leave effects and dependencies implicit at the call site, so reading what a call does to the system means reading its implementation transitively, and moving or removing a call may silently break distant invari- ants. The context paradigm takes the traceability of the first treatment and the ergonomics of the second: effects and coeffects pass through a context the component holds, so each operation is attributable to the context it was invoked on and hence to the component, and everything else stays ordinary host code. The paradigm is in this sense an overlay, realizable atop a language of either style: it fixes each operation’s denotation and leaves its realization to the host language, in place where the host mutates and derived where it stays pure (Definition 23). 78
显式穿参与隐式变更。 纯函数式语言在类型中把效应显式化:State 单子 𝑆 → (𝐴, 𝑆) [23] 把环境贯穿于每一个计算,以穿参本身的代价换取等式推理的保障——调用路径上的每个函数无论是否触及状态都要接受并返回它;第 7.1 节的那些单子式效应系统就是工业形态下的这一极。命令式语言则在调用点把效应与依赖留作隐式的,因而要读出一个调用对系统做了什么,就必须传递地阅读它的实现,而移动或删除一个调用可能静默地破坏远处的不变式。上下文范式取第一种处理的可追踪性与第二种处理的人体工学:效应与余效应经由组件所持有的上下文传递,因此每个操作都可归属于它被调用时所针对的那个上下文,进而归属于该组件,而其余一切仍保持为普通的宿主代码。在这一意义上,该范式是一层覆盖物,可在任一风格的语言之上实现:它固定每个操作的指称,而把其实现留给宿主语言——宿主采用变更时就原地进行,宿主保持纯粹时就派生地进行(定义 23)。
Context-oriented programming. COP [94, 95] equips a language with layers—partial method and class definitions that are activated and deactivated at runtime according to the execution context, so that behavior adapts without the base code naming its context dependencies [96]. COP and Cordis coincide in treating context as a first-class, runtime-mutable entity and in activating and deactivating behavior dynamically, but the resemblance is nominal. In COP , “context” denotes the ambient execution situation (e.g., location, user, mode), and activation changes method dispatch within a dynamically scoped extent; a layer neither tracks the side effects it induces nor reverts them, and activation is not governed by dependency satisfaction. In Cordis, the context is the Γ∞ entity mediating effects and coeffects: activation runs a component’s revertible effects and is driven by reactive coeffect satisfaction (Section 3.2), and deactivation reverts them in full. COP varies what behavior runs; Cordis composes and reverts what effects and dependencies a component installs. Their difference is one of trade-off. COP folds activation into the host language’s method dispatch, gaining dynamically-scoped layer extents at the cost of language specificity, whereas Cordis, as a language-agnostic overlay, resolves activation reactively over a shared context. Cordis can thus express as a coeffect only COP’s global, value-driven fragment: context-dependent selection among implementations, but not dynamically-scoped activation.
面向上下文的编程。 COP [94, 95] 为一门语言配备层(layer)——即根据执行上下文在运行时被激活与停用的部分方法定义与部分类定义——使得行为得以适配,而基础代码不必指名其上下文依赖 [96]。COP 与 Cordis 的共同之处在于:都把上下文视为一等、运行时可变的实体,并动态地激活与停用行为,但这种相似只是名义上的。在 COP 中,“上下文”指周围的执行情境(例如位置、用户、模式),激活则在一个动态作用域的区段内改变方法分派;层既不追踪它所引发的副作用,也不撤销它们,激活也不受依赖是否得到满足的支配。在 Cordis 中,上下文是中介效应与余效应的 Γ∞ 实体:激活会运行一个组件的可撤销效应,并由响应式余效应的满足情况所驱动(第 3.2 节),停用则将其完整撤销。COP 改变的是运行何种行为;Cordis 组合与撤销的是组件所安装的效应与依赖。二者的差异是一种取舍。COP 把激活折叠进宿主语言的方法分派之中,以语言特异性为代价换来了动态作用域的层区段;而 Cordis 作为与语言无关的覆盖层,在一个共享上下文之上以响应式方式解析激活。因此,Cordis 只能把 COP 中全局的、由值驱动的那一片段表达为余效应:即诸实现之间依上下文的选择,而非动态作用域的激活。
Aspect-oriented programming. AOP [97, 98] modularizes a crosscutting concern into an aspect: a pointcut that quantifies over join points selected in the base program, and advice woven in at each. Cordis addresses the same problem of contextual behavior that would otherwise scatter across components, but its analogue of an aspect is a coeffect: a shared point of mediation many components declare a dependence on, so that crosscutting behavior can be reshaped there without editing any of them. The two paradigms then differ on two axes. (1) Declaration versus obliviousness: an AOP pointcut is oblivious and quantified, matching arbitrary join points whose code is unaware it is advised, whereas Cordis confines crosscutting to the coeffects each component declares, so its reach is exactly that declared surface. This yields determinacy and traceability: an application orchestrator can inspect and govern what cross-cuts a component at the configuration layer, without reading or analyzing its source, whereas an AOP concern is legible only through the aspects that quantify over it. (2) Lifecycle integration: a crosscutting change in Cordis is carried by a component’s effects, reverted when the component unloads and propagated reactively to its dependents, so it is one move within the dynamic composition model; dynamic-AOP systems [99, 100] can also weave and unweave at runtime, but as a standalone operation, neither bound to a component’s lifecycle nor triggering re-resolution among the advised code.
面向切面编程。 AOP [97, 98] 把一个横切关注点模块化为切面(aspect):一个对基础程序中选定的连接点做量化的切入点(pointcut),以及在每一处织入的通知(advice)。Cordis 处理的是同一个问题——否则会散布到各组件之中的上下文行为——但它的切面对应物是余效应:一个众多组件都声明依赖的共享中介点,从而可以在那里重塑横切行为,而无须编辑其中任何一个组件。两种范式随后在两个维度上分道扬镳。(1)声明 versus 无感知(obliviousness):AOP 的切入点是无感知且量化的,匹配任意连接点,而这些连接点的代码并不知道自己正被通知增强;Cordis 则把横切限定在每个组件所声明的余效应之内,因此其影响范围恰好就是这一被声明的界面。这带来了确定性与可追溯性:应用编排器可以在配置层检视并治理横切某个组件的因素,无须阅读或分析其源码;而一个 AOP 关注点只有通过量化覆盖它的那些切面才可辨识。(2)生命周期集成:Cordis 中的横切变更由组件的效应承载,在组件卸载时被撤销,并响应式地传播给它的依赖方,因此它是动态组合模型内部的一步动作;动态 AOP 系统 [99, 100] 同样可以在运行时织入与解织入,但那是作为一项独立的操作,既不绑定组件的生命周期,也不在被通知的代码之间触发重新解析。
7.3. 时间可组合性
7.3. Temporal Composability
Temporal composability concerns replacing or removing a component in a running program while recovering the effects it installed. Prior approaches divide by how they treat a departing component’s state and effects: carrying state forward to a successor version, recovering effects through developer-authored cleanup, reversing effects automatically within a scope fixed in advance, or reclaiming resources from a record the runtime accumulates by interposing on an interface.
时间可组合性关注的是:在一个运行中的程序里替换或移除某个组件,同时回收它所安装的效应。既有方案按如何处理一个即将离场的组件的状态与效应而分野:把状态前向携带到后继版本、通过开发者手写的清理来回收效应、在一个事先固定的作用域内自动逆转效应,或者从运行时通过在接口上插桩而累积起来的记录中回收资源。
Stateful forward migration. A broad family of systems replaces components in a running program without downtime by carrying their state forward across versions. All observe the same timing discipline: a component may be swapped only once it reaches a safe, interaction- free point. Kramer and Magee established this criterion as quiescence [53], which Vandewoude et al. later relaxed to the less disruptive tranquility [54]; our rolling-update pattern (Section 6.2) 79 enforces it by draining in-flight requests before unloading a provider. Dynamic software updating (DSU) then migrates state forward through hand-written transformation functions: Hicks et al.‘s general-purpose DSU for C [101], Stoyle et al.’s type-safe update points via con-freeness analysis [102], and Hayden et al.‘s Kitsune [103] all map old-version data to new- version representations, inheriting heap objects, open files, and connections in place while re-initializing whatever is left unmigrated. The same discipline extends to persistent state: Overeem et al. [104] convert a running event store’s data between schema versions through hand-written upgrade operations while keeping the system available. Erlang/OTP [15] takes the same stance at the process level, migrating state through code_change/3 and recovering from faults by restarting supervised processes rather than reverting their effects; JavaScript’s Hot Module Replacement (e.g., webpack [48], Vite [49]) does the same at the module level, handing state forward through the module.hot or import.meta.hot API across a reload. Compared with Cordis’s module replacement (Section 5.2), these approaches migrate in-memory state more gracefully: Cordis reverts the old component’s tracked effects and reapplies the new component’s from a clean slate, so a component’s own in-memory state does not survive a reload unless placed in a longer-lived dependency, and layering DSU-style forward migration atop revertible effects is future work. Cordis’s approach is nonetheless more general in two respects: it needs no hand-written migration functions of the kind DSU and HMR require, and it supports unloading a component entirely and recovering its resources, not merely updating one in place.
有状态的前向迁移。 一大类系统通过跨版本前向携带状态,在运行中的程序里无停机地替换组件。它们都遵循同一条时序纪律:只有当组件到达一个安全的、无交互的点时才可被换出。Kramer 与 Magee 把这一准则确立为静止(quiescence)[53],Vandewoude 等人后来将其放宽为破坏性更小的宁静(tranquility)[54];我们的滚动更新模式(第 6.2 节)则通过在卸载一个提供者之前排空在途请求来强制这一点。动态软件更新(DSU)进而通过手写的变换函数前向迁移状态:Hicks 等人面向 C 的通用 DSU [101]、Stoyle 等人借助 con-freeness 分析得到的类型安全更新点 [102],以及 Hayden 等人的 Kitsune [103],都把旧版本的数据映射到新版本的表示,就地继承堆对象、打开的文件与连接,同时对凡未迁移的部分重新初始化。同一条纪律延伸到持久化状态:Overeem 等人 [104] 通过手写的升级操作,在运行中的事件存储的各模式版本之间转换数据,同时保持系统可用。Erlang/OTP [15] 在进程层面持同样的立场,通过 code_change/3 迁移状态,并通过重启受监督的进程而非撤销其效应来从故障中恢复;JavaScript 的模块热替换(如 webpack [48]、Vite [49])在模块层面做同样的事,通过 module.hot 或 import.meta.hot API 在一次重载之间把状态移交下去。与 Cordis 的模块替换(第 5.2 节)相比,这些方法对内存中状态的迁移更为优雅:Cordis 撤销旧组件被追踪的效应,并从一张干净的白板重新施加新组件的效应,因此组件自身的内存状态无法在一次重载后存活,除非被安放到一个生命周期更长的依赖之中;而在可撤销效应之上叠加 DSU 式的前向迁移,则是未来的工作。尽管如此,Cordis 的方法在两个方面更为通用:它不需要 DSU 与 HMR 所要求的那类手写迁移函数,并且它支持完整地卸载一个组件并回收其资源,而不仅仅是就地更新一个组件。
Developer-authored recovery. A second family recovers a component’s effects through cleanup or compensation logic that the developer writes by hand. Plugin lifecycle conventions (e.g., OSGi [52], Eclipse’s extension points, IntelliJ and VSCode) delegate cleanup to developer- written unload callbacks; the Command pattern [105] encapsulates an operation together with an undo method for undo/redo stacks; the saga model [51] structures a long-lived transaction as steps each paired with a compensating action; algebraic effect handlers can attach finalizers that run on teardown [106]; and event sourcing [107] retracts state by appending compensating events rather than executing an inverse at all. In all of them the inverse is an unenforced duty, decoupled from the operation, so that a forgotten one leaks resources silently (as documented empirically in Section 1.2.1). React’s useEffect hook [108] comes closest to pairing an effect with its inverse structurally, returning a cleanup the runtime invokes before each re-execution and on unmount. Its shortfall is composability: a hook may be called only at the top level of a component or another hook, never inside a conditional, loop, or nested function, and its effect body accepts neither an async function nor an iterator. Effects thus cannot be assembled from other effects or interleaved with control flow, leaving nothing from which a composite inverse could be derived. Cordis effects carry no such restriction: they are ordinary operations that compose freely and may run asynchronously, and require a hand-written inverse only for each atomic effect, from which the inverse of any composite is derived by composition, so that assembling existing effects requires writing no inverses at all. This structural pairing of every effect with its inverse makes complete recovery an invariant of the system rather than a matter of developer discipline.
开发者手写的恢复。 第二类方案通过开发者手工撰写的清理或补偿逻辑来回收组件的效应。插件生命周期惯例(如 OSGi [52]、Eclipse 的扩展点、IntelliJ 与 VSCode)把清理委托给开发者编写的卸载回调;命令模式 [105] 把一个操作连同用于撤销/重做栈的 undo 方法封装在一起;saga 模型 [51] 把一个长生命周期事务结构化为若干步骤,每一步都配一个补偿动作;代数效应处理器可以附着在拆卸时运行的 finalizer [106];而事件溯源 [107] 则通过追加补偿事件来撤销状态,根本不去执行逆操作。在所有这些方案中,逆操作都是一项未被强制的义务,与操作本身相解耦,因此一旦被遗漏便会静默地泄漏资源(正如第 1.2.1 节所作的经验性记载)。React 的 useEffect 钩子 [108] 最接近把效应与其逆操作在结构上配对:它返回一个清理函数,运行时在每次重新执行之前以及卸载时调用它。它的短板在于可组合性:钩子只能在组件或其他钩子的顶层被调用,绝不能出现在条件、循环或嵌套函数之中,并且其效应体既不接受 async 函数也不接受迭代器。因此效应无法由其他效应组装而成,也无法与控制流交错,从而没有任何东西可供导出复合的逆操作。Cordis 的效应不带此类限制:它们是可以自由组合的普通操作,并且可以异步运行;只需为每个原子效应手写一个逆操作,任意复合效应的逆操作便可由组合导出,因而在组装已有效应时完全不需要编写任何逆操作。这种把每一个效应与其逆操作在结构上配对的做法,使完整恢复成为系统的一个不变式,而不再是开发者自律的问题。
Statically scoped reversal. A third family reverses effects automatically, by construction, but confines reversal to a scope fixed in advance. Software transactional memory [109, 110], descended from hardware transactional memory [111], records a read/write log so that a group of memory operations either commits or aborts, rolling memory back to its pre-transaction state. Reversible computing, from Landauer and Bennett’s thermodynamic analyses [112, 113] to reversible languages such as Janus [114], goes further and makes every step of a whole computation globally invertible. Reversible process calculi build backtracking into the 80 semantics itself: RCCS [115] carries a memory alongside each process and admits a step to be taken back when the past it leads to is causally equivalent, and Phillips and Ulidowski [116] derive reversible operators for CCS, ACP , and CSP uniformly while preserving their forward operational semantics. Their causal-consistency criterion is the concurrent counterpart of the order Cordis’s recovery follows, an accumulator applying a component’s own inverses in last- in-first-out order and the guard of Section 4.2.2 deferring a provider’s withdrawal until its consumers have deactivated (Theorem 70). The reach, however, is fixed by the semantics, every action performed remaining undoable, whereas a Cordis component supplies an inverse for each atomic effect and its accumulator brings the context back to where its composition began. Linear types [117], RAII [4], and Rust’s ownership system [63] tie a resource’s release to a lexical region. Each fixes the scope and reach of reversal statically; Cordis, by contrast, fixes no such scope in advance: it reverts arbitrary context operations over a component’s lifecycle, and treats lexical resource management as complementary, appropriate for local resources within a single component. Verification supplies the same pairing at the granularity of a data structure. Kim and Rinard specify and verify an inverse for every state-changing operation of a collection of set and map implementations, together with the conditions under which two operations commute, reasoning on abstract state so that orders leaving equivalent rather than identical structures count as commuting [118]. Both of the ingredients Section 3.4 requires of a coeffect are therefore mechanically checkable at the interfaces most keys publish, and their reason for preferring an inverse to a saved copy of the state is the one Section 3.1 acts on. Their inverses do not compose, each being verified for one operation, whereas a Cordis accumulator carries the inverses of a whole lifecycle.
静态作用域的逆转。 第三类方案依构造自动逆转效应,但把逆转限定在一个事先固定的作用域之内。软件事务内存 [109, 110] 源自硬件事务内存 [111],它记录一份读写日志,使得一组内存操作要么提交要么中止,从而把内存回滚到事务前的状态。可逆计算——从 Landauer 与 Bennett 的热力学分析 [112, 113] 到 Janus [114] 这样的可逆语言——走得更远,使整个计算的每一步都全局可逆。可逆进程演算把回溯内建进语义本身:RCCS [115] 为每个进程携带一份记忆,并允许在其所导向的过去因果等价时把一步退回去;Phillips 与 Ulidowski [116] 为 CCS、ACP 与 CSP 统一地导出可逆算子,同时保持它们的前向操作语义。它们的因果一致性准则是 Cordis 恢复所遵循之顺序的并发对应物:一个累加器以后进先出的顺序施加组件自身的逆操作,而第 4.2.2 节的守卫把一个提供者的撤出推迟到它的消费者都已停用之后(定理 70)。然而,其可达范围由语义固定,所执行的每一个动作始终可撤销;而 Cordis 组件为每个原子效应提供一个逆操作,其累加器则把上下文带回到它的组合开始之处。线性类型 [117]、RAII [4] 以及 Rust 的所有权系统 [63] 把资源的释放绑定到一个词法区域。它们每一个都静态地固定了逆转的作用域与范围;相比之下,Cordis 不事先固定任何此类作用域:它在一个组件的生命周期上逆转任意的上下文操作,并把词法资源管理视为互补的手段,适用于单个组件内部的局部资源。验证则在数据结构的粒度上提供了同样的配对。Kim 与 Rinard 为一组 set 与 map 实现的每一个改变状态的操作指定并验证了一个逆操作,同时给出两个操作可交换的条件,在抽象状态上进行推理,从而使那些留下等价而非相同结构的顺序也算作可交换 [118]。因此,第 3.4 节对余效应所要求的两项要素,在大多数键所发布的接口上都是可以机械检验的;而他们偏好逆操作而非保存一份状态副本的理由,正是第 3.1 节所依凭的那一条。他们的逆操作并不复合——每一个只针对单个操作加以验证——而 Cordis 的累加器承载着整个生命周期的逆操作。
Interposed reclamation. A fourth family reclaims what a component acquired without the component itself supplying the inverses, by recording its acquisitions at an interface the runtime controls. Nooks [79] wraps every call crossing the boundary between the Linux kernel and its loadable extensions, so that the kernel objects an extension touches pass through an object tracker whose record tells the recovery manager what to release when the extension fails; shadow drivers [80] tap the same calls from the other side, recording the requests and configuration that determine a driver’s state so that a restarted instance can be restored to it. Akeso [119] obtains the record by compiler instrumentation instead, dividing kernel execution into nestable recovery domains that log their state changes and cross-thread dependencies, and rolling a faulting request back together with every domain that depends on it. Reclamation thus follows from a record the runtime maintains rather than from cleanup the developer remembers to write, which makes this family the closest systems-level precedent for revertible effects. It differs from Cordis in vocabulary and in reach. The platform fixes what can be recorded, whether as release code per kernel object type, one shadow per driver class, or an inverse per instrumented allocator, so a component may hold only resources the platform already knows how to release; a Cordis component instead introduces effects of its own and supplies an inverse for each atomic one (Section 3.1). Reclamation is likewise bounded by a request that commits or a restart of the same extension, whereas Cordis reverts over a component’s whole lifetime and propagates removal to its dependents, which release their own effects in turn (Section 3.2).
插桩式回收。 第四类方案在回收一个组件所获取之物时,并不要求组件自身提供逆操作,而是在一个由运行时掌控的接口上记录其获取行为。Nooks [79] 包装每一次穿越 Linux 内核与其可加载扩展之间边界的调用,使得扩展所触及的内核对象都经过一个对象追踪器,其记录告诉恢复管理器在该扩展失效时应当释放什么;影子驱动(shadow drivers)[80] 从另一侧窃听同样的调用,记录决定一个驱动状态的请求与配置,以便重启后的实例能被恢复到该状态。Akeso [119] 则改用编译器插桩来获得这份记录,把内核执行划分为可嵌套的恢复域,各自记录其状态变化与跨线程依赖,并把出错的请求连同所有依赖它的域一起回滚。因此,回收来自运行时所维护的一份记录,而非开发者记得写下的清理代码,这使得这一类成为可撤销效应在系统层面最接近的先例。它与 Cordis 在词汇与范围上都有所不同。平台固定了什么可被记录——无论是每种内核对象类型对应的释放代码、每一类驱动对应的一个影子,还是每个被插桩的分配器对应的一个逆操作——因此组件只能持有平台已然知道如何释放的资源;而 Cordis 组件则自行引入效应,并为每个原子效应提供一个逆操作(第 3.1 节)。回收同样受限于一个提交了的请求或同一扩展的一次重启,而 Cordis 则在一个组件的整个生命期内进行逆转,并把移除传播给它的依赖方,后者继而释放自身的效应(第 3.2 节)。
7.4. 空间可组合性
7.4. Spatial Composability
Spatial composability concerns how a component’s dependencies on others are declared and bound. Prior mechanisms divide by how binding responds to change: wiring dependencies once at initialization, reacting to the availability of whole components, or propagating change at the granularity of individual values. 81
空间可组合性关注的是:一个组件对其他组件的依赖如何被声明与绑定。既有机制按绑定如何响应变化而分野:在初始化时一次性地把依赖接线起来、对整体组件的可用性做出反应,或者以单个值的粒度传播变化。
Initialization-time dependency wiring. Two established mechanisms wire components to- gether at initialization time. Dependency injection frameworks [39] (e.g., Spring [120], Guice, Angular, Inversify) inject dependencies into components at initialization, and UI framework context (e.g., Vue.js’s provide/inject and React’s Context API) passes them along a component tree. Some support dynamic scoping (e.g., Spring’s prototype/request scopes, Angular’s hier- archical injectors), but neither re-resolves reactively: when a provider is replaced or removed at runtime, existing dependents are neither deactivated nor re-initialized, and none offers lifecycle management of the kind our component state machine provides. Cordis’s reactive coeffects (Section 3.2) supply this: the notification mechanism triggers lifecycle transitions whenever the satisfaction predicate changes.
初始化时的依赖接线。 两种既有机制在初始化时把组件彼此接线到一起。依赖注入框架 [39](如 Spring [120]、Guice、Angular、Inversify)在初始化时把依赖注入组件;UI 框架的 context(如 Vue.js 的 provide/inject 与 React 的 Context API)则沿组件树向下传递它们。其中有些支持动态作用域(如 Spring 的 prototype/request 作用域、Angular 的层次化注入器),但都不做响应式重解析:当一个提供者在运行时被替换或移除时,既有的依赖方既不停用也不重新初始化,并且没有哪一个提供我们组件状态机所提供的那类生命周期管理。Cordis 的响应式余效应(第 3.2 节)补足了这一点:通知机制在满足谓词发生变化时触发生命周期转换。
Availability-reactive component models. The closest precedent to our reactive coeffects reacts to service availability. OSGi’s Declarative Services and iPOJO [121, 122] let components declare provided and required services, with the runtime automatically activating and deacti- vating them as services appear and disappear; iPOJO’s Gravity project [122] explicitly targets autonomous runtime adaptation to changing service availability, and its provide/require model directly prefigures Cordis’s ctx.provide/ctx.get pattern. R-OSGi [55] extends the same abstraction transparently to distributed settings via RPC, mapping network failures to service- withdrawal events, a pattern Section 6.2 discusses as an extension of the Cordis model. All these systems recover through a deactivation callback, which is limited in two ways. First, the callback is hand-written, so resource safety rests on developer discipline and a forgotten one leaks silently. Second, the callback is synchronous: should teardown require an asynchronous exchange with the departing dependency, the frameworks offer no protocol to await it, forcing a blocking wait against a reference that may already be stale. Cordis’s reactive coeffects close both gaps: deactivation reverts the dependents’ accumulated effects, and its inertial 𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 state (Section 4.4) runs asynchronous teardown to completion before acting on further change.
对可用性作出反应的组件模型。 与我们的响应式余效应最接近的先例,对服务的可用性作出反应。OSGi 的 Declarative Services 与 iPOJO [121, 122] 允许组件声明其所提供与所需求的服务,运行时随着服务的出现与消失自动激活与停用它们;iPOJO 的 Gravity 项目 [122] 明确以“随服务可用性变化而自主地进行运行时适配”为目标,其 provide/require 模型直接预示了 Cordis 的 ctx.provide/ctx.get 模式。R-OSGi [55] 通过 RPC 把同一抽象透明地扩展到分布式场景,把网络故障映射为服务撤出事件——第 6.2 节把这一模式作为 Cordis 模型的一种扩展加以讨论。所有这些系统都通过一个停用回调来进行恢复,而该回调有两方面局限。第一,回调是手写的,因此资源安全系于开发者的自律,一旦遗漏便静默泄漏。第二,回调是同步的:倘若拆卸需要与正在离场的依赖进行异步交互,这些框架并不提供等待它的协议,只能对一个可能已经失效的引用做阻塞等待。Cordis 的响应式余效应弥合了这两处缺口:停用会撤销依赖方已累积的效应,而其惯性的(inertial)𝖴𝗇𝗅𝗈𝖺𝖽𝗂𝗇𝗀 状态(第 4.4 节)会在响应后续变化之前把异步拆卸运行到底。
Value-level reactivity. Functional reactive programming (FRP) [123] and its modern incar- nations (e.g., signals [124, 125] in SolidJS, Vue’s reactivity system, Angular Signals) propagate change at a value-level granularity: when a signal changes, derived computations are re-evalu- ated synchronously or under a scheduler [126]. Cordis’s reactive coeffects act at a component- level granularity, adding asynchronous lifecycle semantics that value-level propagation does not model. The same granularity difference runs the other way for consistency: propagating in a turn, in an order the dependency graph fixes, lets FRP require that no derived computation read a mixture of updated and stale inputs, which is glitch freedom [127], whereas Cordis has no counterpart of a turn, orchestration actions arriving one at a time, and guarantees only that no single transition straddles two resolutions of its coeffects ( Theorem 71). The two are complementary rather than competing: a Cordis coeffect can itself carry reactive values, and a component updates on only the parts it actually consumes, refining component-level reactivity into finer-grained reactive coeffects that span both levels.
值层面的响应式。 函数式响应式编程(FRP)[123] 及其现代化身(如 SolidJS 中的 signals [124, 125]、Vue 的响应式系统、Angular Signals)以值层面的粒度传播变化:当一个 signal 改变时,派生计算被同步地或在某个调度器 [126] 之下重新求值。Cordis 的响应式余效应作用在组件层面的粒度上,并附加了值层面传播所没有建模的异步生命周期语义。同样的粒度差异在一致性问题上则朝相反方向起作用:在一轮(turn)之内、按依赖图所固定的顺序传播,使 FRP 能够要求任何派生计算都不会读到已更新输入与陈旧输入的混合体,这就是无毛刺性(glitch freedom)[127];而 Cordis 没有“轮”的对应物,编排动作逐个到达,只保证没有任何单个转换横跨其余效应的两次解析(定理 71)。二者是互补而非竞争关系:一个 Cordis 余效应自身可以携带响应式的值,而组件只在它实际消费的那些部分上更新,从而把组件层面的响应式细化为跨越两个层级的更细粒度的响应式余效应。
8. 结论
8. Conclusion
We have presented a formal foundation for dynamic composability by lifting the classical concepts of effects and coeffects to runtime mechanisms. Revertible effects address local temporal composability: every context transformation carries an inverse that the runtime holds, and both tracking and recovery preserve composition, so the context is recovered upon component removal. Reactive coeffects address local spatial composability: every context change is classified against a component’s coeffect specification as activating, deactivating, or neutral, and the 82 classification drives its activation and deactivation. We then unify the effect context and the coeffect context into a single context type and mediate every effect and coeffect through it, yielding a discipline we call the context paradigm ; the mediation induces an observational equivalence up to which the effects of distinct components attain independence. Combining these mechanisms into the notion of a component, we give a calculus of dynamic composition whose metatheory carries spatiotemporal composability from a single component to a whole system of interleaved components. We realize this paradigm as the Cordis meta-framework, with a core library providing effect tracking and coeffect resolution, as well as a declarative component loader with configuration reconciliation and hot module replacement. The Koishi case study validates the design of Cordis in a production system with over 4000 community plugins.
我们给出了动态可组合性的一个形式化基础,其途径是把效应与余效应这对经典概念提升为运行时机制。可撤销效应处理局部的时间可组合性:每个上下文转换都携带一个由运行时持有的逆操作,追踪与恢复二者都保持组合性,因此在组件被移除时上下文得以恢复。响应式余效应处理局部的空间可组合性:每个上下文变化都对照组件的余效应规约被分类为激活、停用或中性,而该分类驱动它的激活与停用。我们随后把效应上下文与余效应上下文统一为单一的上下文类型,并通过它中介每一个效应与余效应,从而得到一种我们称之为上下文范式的纪律;这种中介诱导出一个观测等价,在该等价之下不同组件的效应获得独立性。把这些机制组合成组件这一概念,我们给出了一个动态组合演算,其元理论把时空可组合性从单个组件带到由相互交错的组件构成的整个系统。我们把这一范式实现为 Cordis 元框架,它带有一个提供效应追踪与余效应解析的核心库,以及一个具备配置调和与模块热替换的声明式组件加载器。Koishi 案例研究在一个拥有超过 4000 个社区插件的生产系统中验证了 Cordis 的设计。
Beyond human-curated plugin ecosystems, a compelling direction for future validation is self-evolving agent harnesses ( Section 1.2.2), where an AI agent generates and replaces its own harness components continuously and with little human oversight. Applying Cordis in such a setting would validate the temporal guarantees of complete recovery under rapid component replacement, as well as the spatial guarantees of dependency coordination under frequent topological change. Such validation would demonstrate the paradigm’s applicability as a foundation for recoverable, coordinated, and continuous self-evolution in agent harnesses and other autonomous systems. References [1] D. L. Parnas, “On the criteria to be used in decomposing systems into modules,” Com- munications of the ACM, vol. 15, no. 12, pp. 1053–1058, 1972, doi: 10.1145/361598.361623. [2] D. Birsan, “On Plug-ins and Extensible Architectures,” ACM Queue, vol. 3, no. 2, pp. 40– 46, 2005, doi: 10.1145/1053331.1053345. [3] B. Burns, B. Grant, D. Oppenheimer, E. Brewer, and J. Wilkes, “Borg, Omega, and Kuber- netes,” Communications of the ACM, vol. 59, no. 5, pp. 50–57, 2016, doi: 10.1145/2890784. [4] B. Stroustrup, The Design and Evolution of C++. Addison-Wesley, 1994. [5] S. Marlow, S. Peyton Jones, A. Moran, and J. Reppy, “ Asynchronous Exceptions in Haskell,” in Proceedings of the ACM SIGPLAN 2001 Conference on Programming Language Design and Implementation, in PLDI '01. New York, NY, USA: Association for Computing Machinery, 2001, pp. 274–285. doi: 10.1145/378795.378858. [6] L. Cardelli, “Program Fragments, Linking, and Modularization,” in Proceedings of the 24th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL 1997), ACM Press, 1997, pp. 266–277. doi: 10.1145/263699.263735. [7] C. Szyperski, Component Software: Beyond Object-Oriented Programming, 2nd ed. Addison- Wesley, 2002. [8] R. Lopopolo, “Harness Engineering: Leveraging Codex in an Agent-First World.” [On- line]. Available: https://openai.com/index/harness-engineering/ [9] Anthropic, “Harness Design for Long-Running Application Development.” [Online]. Available: https://www.anthropic.com/engineering/harness-design-long- running-apps 83
在人工策划的插件生态之外,未来验证的一个引人瞩目的方向是自演化的智能体运行时(第 1.2.2 节):在那里,一个 AI 智能体持续地生成并替换它自己的运行时组件,几乎不需要人工监督。在此类场景中应用 Cordis,将验证在组件快速替换之下完整恢复这一时间保证,以及在频繁拓扑变化之下依赖协调这一空间保证。此类验证将表明:该范式可以作为智能体运行时以及其他自治系统中可恢复、可协调、可连续的自演化的基础而适用。
参考文献(保留原文)
参考文献(保留英文原文)
参考文献(保留原文)
- [10] L. Wang et al., “ A Survey on Large Language Model Based Autonomous Agents,” Frontiers of Computer Science, vol. 18, no. 6, p. 186345, 2024, doi: 10.1007/s11704-024-40231-1.
- [11] Y. Qin et al., “Tool Learning with Foundation Models,” ACM Computing Surveys, 2025, doi: 10.1145/3704435.
- [12] C. Packer, V. Fang, S. G. Patil, K. Lin, S. Wooders, and J. E. Gonzalez, “MemGPT: Towards LLMs as Operating Systems,” CoRR, vol. abs/2310.08560, 2023.
- [13] T. Guo et al., “Large Language Model Based Multi-Agents: A Survey of Progress and Challenges,” in Proceedings of the Thirty-Third International Joint Conference on Artificial Intelligence, in IJCAI 2024. 2024, pp. 8048–8057. doi: 10.24963/ijcai.2024/890.
- [14] T. Cai, X. Wang, T. Ma, X. Chen, and D. Zhou, “Large Language Models as Tool Makers,” in Proceedings of the Twelfth International Conference on Learning Representations , in ICLR 2024. 2024. [Online]. Available: https://openreview.net/forum?id=qV83K9d5WB
- [15] J. Armstrong, “Making Reliable Distributed Systems in the Presence of Software Errors,” Doctoral dissertation, 2003. [Online]. Available: https://erlang.org/download/armstrong_thesis_2003.pdf
- [16] E. Moggi, “Notions of computation and monads,” Information and Computation, vol. 93, no. 1, pp. 55–92, 1991, doi: 10.1016/0890-5401(91)90052-4.
- [17] G. Plotkin and J. Power, “ Adequacy for Algebraic Effects,” in Foundations of Software Science and Computation Structures, F. Honsell and M. Miculan, Eds., Berlin, Heidelberg: Springer Berlin Heidelberg, 2001, pp. 1–24.
- [18] T. Petricek, D. Orchard, and A. Mycroft, “Coeffects: unified static analysis of context-dependence,” in Proceedings of the 40th International Conference on Automata, Languages, and Programming - Volume Part II, in ICALP'13. Riga, Latvia: Springer-Verlag, 2013, pp. 385–397. doi: 10.1007/978-3-642-39212-2_35.
- [19] M. Gaboardi, S.-ya Katsumata, D. Orchard, F. Breuvart, and T. Uustalu, “Combining effects and coeffects via grading,” in Proceedings of the 21st ACM SIGPLAN International Conference on Functional Programming, in ICFP 2016. Nara, Japan: Association for Computing Machinery, 2016, pp. 476–489. doi: 10.1145/2951913.2951939.
- [20] A. Church, “ A Formulation of the Simple Theory of Types,” The Journal of Symbolic Logic, vol. 5, no. 2, pp. 56–68, 1940, doi: 10.2307/2266170.
- [21] B. C. Pierce, Types and Programming Languages. MIT Press, 2002.
- [22] J. M. Lucassen and D. K. Gifford, “Polymorphic Effect Systems,” in Proceedings of the 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '88. San Diego, California, USA: Association for Computing Machinery, 1988, pp. 47–57. doi: 10.1145/73560.73564.
- [23] P. Wadler, “Monads for functional programming,” in Program Design Calculi , M. Broy, Ed., Berlin, Heidelberg: Springer Berlin Heidelberg, 1993, pp. 233–264.
- [24] G. Plotkin and J. Power, “Notions of Computation Determine Monads,” in Foundations of Software Science and Computation Structures , Berlin, Heidelberg: Springer Berlin Heidelberg, 2002, pp. 342–356. doi: 10.1007/3-540-45931-6_24.
- [25] G. Plotkin and M. Pretnar, “Handlers of Algebraic Effects,” in Programming Languages and Systems (ESOP) , Berlin, Heidelberg: Springer Berlin Heidelberg, 2009, pp. 80–94. doi: 10.1007/978-3-642-00590-9_7.
- [26] M. Pretnar, “ An Introduction to Algebraic Effects and Handlers. Invited tutorial paper,” Electron. Notes Theor. Comput. Sci. , vol. 319, no. C, pp. 19–35, Dec. 2015, doi: 10.1016/j.entcs.2015.12.003.
- [27] D. Leijen, “Koka: Programming with Row Polymorphic Effect Types,” Electronic Proceedings in Theoretical Computer Science , vol. 153, pp. 100–126, Jun. 2014, doi: 10.4204/eptcs.153.8.
- [28] D. Leijen, “Type directed compilation of row-typed algebraic effects,” in Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages , in POPL '17. Paris, France: Association for Computing Machinery, 2017, pp. 486–499. doi: 10.1145/3009837.3009872.
- [29] A. Bauer and M. Pretnar, “Programming with algebraic effects and handlers,” Journal of Logical and Algebraic Methods in Programming, vol. 84, no. 1, pp. 108–123, Jan. 2015, doi: 10.1016/j.jlamp.2014.02.001.
- [30] K. Sivaramakrishnan et al., “Retrofitting parallelism onto OCaml,” Proc. ACM Program. Lang., vol. 4, no. ICFP , Aug. 2020, doi: 10.1145/3408995.
- [31] T. Petricek, D. Orchard, and A. Mycroft, “Coeffects: a calculus of context-dependent computation,” in Proceedings of the 19th ACM SIGPLAN International Conference on Functional Programming, in ICFP '14. Gothenburg, Sweden: Association for Computing Machinery, 2014, pp. 123–135. doi: 10.1145/2628136.2628160.
- [32] T. Uustalu and V. Vene, “Comonadic Notions of Computation,” Electronic Notes in Theoretical Computer Science, vol. 203, no. 5, pp. 263–284, 2008, doi: 10.1016/j.entcs.2008.05.029.
- [33] A. Brunel, M. Gaboardi, D. Mazza, and S. Zdancewic, “ A Core Quantitative Coeffect Calculus,” in Proceedings of the 23rd European Symposium on Programming Languages and Systems - Volume 8410 , Berlin, Heidelberg: Springer-Verlag, 2014, pp. 351–370. doi: 10.1007/978-3-642-54833-8_19.
- [34] J. Reed and B. C. Pierce, “Distance makes the types grow stronger: a calculus for differential privacy,” SIGPLAN Not. , vol. 45, no. 9, pp. 157–168, Sep. 2010, doi: 10.1145/1932681.1863568.
- [35] M. Abadi, A. Banerjee, N. Heintze, and J. G. Riecke, “ A core calculus of dependency,” in Proceedings of the 26th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '99. San Antonio, Texas, USA: Association for Computing Machinery, 1999, pp. 147–160. doi: 10.1145/292540.292555.
- [36] D. E. Denning, “ A lattice model of secure information flow,” Commun. ACM, vol. 19, no. 5, pp. 236–243, May 1976, doi: 10.1145/360051.360056.
- [37] U. Dal Lago and F. Gavazzo, “ A relational theory of effects and coeffects,” Proc. ACM Program. Lang., vol. 6, no. POPL, Jan. 2022, doi: 10.1145/3498692.
- [38] R. P. James and A. Sabry, “Yield: Mainstream Delimited Continuations,” in First International Workshop on the Theory and Practice of Delimited Continuations (TPDC 2011) , 2011, pp. 20–32. [Online]. Available: https://homes.luddy.indiana.edu/sabry/files/yield.pdf
- [39] M. Fowler, “Inversion of Control Containers and the Dependency Injection pattern.” [Online]. Available: https://martinfowler.com/articles/injection.html
- [40] A. M. Pitts and I. D. B. Stark, “Observable Properties of Higher Order Functions that Dynamically Create Local Names, or What's New?,” in Mathematical Foundations of Computer Science 1993 (MFCS 1993) , in Lecture Notes in Computer Science, vol. 711. Springer, 1993, pp. 122–141. doi: 10.1007/3-540-57182-5_8.
- [41] G. D. Plotkin, “LCF Considered as a Programming Language,” Theoretical Computer Science, vol. 5, no. 3, pp. 223–255, 1977, doi: 10.1016/0304-3975(77)90044-5.
- [42] D. R. Ghica, K. Muroya, and T. Waugh Ambridge, “ A Robust Graph-Based Approach to Observational Equivalence,” Logical Methods in Computer Science , vol. 21, no. 2, p. 8:1–8:95, 2025, doi: 10.46298/LMCS-21(2:8)2025.
- [43] M. Shapiro, N. Preguiça, C. Baquero, and M. Zawirski, “ A Comprehensive Study of Convergent and Commutative Replicated Data Types,” technical report RR-7506, 2011.
- [44] X. Leroy and S. Blazy, “Formal Verification of a C-like Memory Model and Its Uses for Verifying Program Transformations,” Journal of Automated Reasoning, vol. 41, no. 1, pp. 1–31, 2008, doi: 10.1007/s10817-008-9099-0.
- [45] A. T. Clements, M. F. Kaashoek, N. Zeldovich, R. T. Morris, and E. Kohler, “The Scalable Commutativity Rule: Designing Scalable Software for Multicore Processors,” in Proceedings of the 24th ACM Symposium on Operating Systems Principles, 2013, pp. 1–17. doi: 10.1145/2517349.2522712.
- [46] A. W. Mazurkiewicz, “Trace Theory,” in Petri Nets: Applications and Relationships to Other Models of Concurrency, Advances in Petri Nets 1986, Part II, in Lecture Notes in Computer Science, vol. 255. Springer, 1987, pp. 279–324. doi: 10.1007/3-540-17906-2_30.
- [47] U. A. Acar, G. E. Blelloch, and R. Harper, “ Adaptive functional programming,” ACM Transactions on Programming Languages and Systems , vol. 28, no. 6, pp. 990–1034, 2006, doi: 10.1145/1186632.1186634.
- [48] webpack, “Hot Module Replacement.” [Online]. Available: https://webpack.js.org/api/hot-module-replacement/
- [49] Vite, “HMR API.” [Online]. Available: https://vite.dev/guide/api-hmr
- [50] E. N. (M. Elnozahy, L. Alvisi, Y.-M. Wang, and D. B. Johnson, “ A Survey of Rollback-Recovery Protocols in Message-Passing Systems,” ACM Computing Surveys, vol. 34, no. 3, pp. 375–408, 2002, doi: 10.1145/568522.568525.
- [51] H. Garcia-Molina and K. Salem, “Sagas,” in Proceedings of the 1987 ACM SIGMOD International Conference on Management of Data, in SIGMOD '87. 1987, pp. 249–259. doi: 10.1145/38713.38742.
- [52] OSGi Alliance, OSGi Core Release 8. OSGi Alliance, 2020. [Online]. Available: https://docs.osgi.org/specification/osgi.core/8.0.0/
- [53] J. Kramer and J. Magee, “The Evolving Philosophers Problem: Dynamic Change Management,” IEEE Transactions on Software Engineering, vol. 16, no. 11, pp. 1293–1306, 1990, doi: 10.1109/32.60317.
- [54] Y. Vandewoude, P. Ebraert, Y. Berbers, and T. D'Hondt, “Tranquility: A Low Disruptive Alternative to Quiescence for Ensuring Safe Dynamic Updates,” IEEE Transactions on Software Engineering, vol. 33, no. 12, pp. 856–868, 2007, doi: 10.1109/tse.2007.70733.
- [55] J. S. Rellermeyer, G. Alonso, and T. Roscoe, “R-OSGi: Distributed Applications Through Software Modularization,” in Proceedings of the ACM/IFIP/USENIX 8th International Middleware Conference , in Middleware '07. 2007, pp. 1–20. doi: 10.1007/978-3-540-76778-7_1.
- [56] J. B. Dennis and E. C. Van Horn, “Programming Semantics for Multiprogrammed Computations,” Communications of the ACM , vol. 9, no. 3, pp. 143–155, 1966, doi: 10.1145/365230.365252.
- [57] M. S. Miller, K.-P. Yee, and J. Shapiro, “Capability Myths Demolished,” technical report SRL2003–2, 2003. [Online]. Available: http://zesty.ca/capmyths/usenix.pdf
- [58] R. N. M. Watson, J. Anderson, B. Laurie, and K. Kennaway, “Capsicum: Practical Capabilities for UNIX,” in Proceedings of the 19th USENIX Security Symposium, 2010, pp. 29–46. [Online]. Available: https://www.usenix.org/legacy/events/sec10/tech/full_papers/Watson.pdf
- [59] R. Wahbe, S. Lucco, T. E. Anderson, and S. L. Graham, “Efficient Software-Based Fault Isolation,” in Proceedings of the 14th ACM Symposium on Operating Systems Principles , in SOSP '93. 1993, pp. 203–216. doi: 10.1145/168619.168635.
- [60] A. Barth, A. P. Felt, P. Saxena, and A. Boodman, “Protecting Browsers from Extension Vulnerabilities,” in Proceedings of the 17th Annual Network and Distributed System Security Symposium, in NDSS '10. 2010. [Online]. Available: https://www.ndss-symposium.org/ndss2010/protecting-browsers-extension-vulnerabilities/
- [61] W. W. Ho and R. A. Olsson, “ An Approach to Genuine Dynamic Linking,” Software: Practice and Experience, vol. 21, no. 4, pp. 375–390, 1991, doi: 10.1002/SPE.4380210404.
- [62] P. Wadler and S. Blott, “How to Make Ad-hoc Polymorphism Less Ad Hoc,” in Proceedings of the 16th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '89. 1989, pp. 60–76. doi: 10.1145/75277.75283.
- [63] F. Klock and N. Matsakis, “The Rust Language and Type System,” in ACM SIGPLAN ML Family Workshop, Gothenburg, Sweden, Sep. 2014.
- [64] D. Dreyer, R. Harper, M. M. T. Chakravarty, and G. Keller, “Modular Type Classes,” in Proceedings of the 34th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '07. 2007, pp. 63–70. doi: 10.1145/1190216.1190229.
- [65] Microsoft, “Declaration Merging.” [Online]. Available: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
- [66] T. Van Cutsem and M. S. Miller, “Proxies: Design Principles for Robust Object-oriented Intercession APIs,” in Proceedings of the 6th Symposium on Dynamic Languages, in DLS '10. 2010, pp. 59–72. doi: 10.1145/1869631.1869638.
- [67] R. Hettinger, “Descriptor HowTo Guide.” [Online]. Available: https://docs.python.org/3/howto/descriptor.html
- [68] P. Maes, “Concepts and Experiments in Computational Reflection,” in Conference on Object-Oriented Programming Systems, Languages, and Applications (OOPSLA) , 1987, pp. 147–155. doi: 10.1145/38765.38821.
- [69] G. Bracha and D. M. Ungar, “Mirrors: design principles for meta-level facilities of object-oriented programming languages,” in Proceedings of the 19th Annual ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA), 2004, pp. 331–344. doi: 10.1145/1028976.1029004.
- [70] R. Rouvoy and P. Merle, “Leveraging component-based software engineering with Fraclet,” Annals of Telecommunications , vol. 64, no. 1–2, pp. 65–79, 2009, doi: 10.1007/s12243-008-0072-z.
- [71] E. Burmako, “Scala Macros: Let Our Powers Combine!,” in Proceedings of the 4th Workshop on Scala, in SCALA@ECOOP '13. 2013, p. 3:1–3:10. doi: 10.1145/2489837.2489840.
- [72] S. Raemaekers, A. van Deursen, and J. Visser, “Semantic Versioning and Impact of Breaking Changes in the Maven Repository,” Journal of Systems and Software, vol. 129, pp. 140–158, 2017, doi: 10.1016/j.jss.2016.04.008.
- [73] P. Lam, J. Dietrich, and D. J. Pearce, “Putting the Semantics into Semantic Versioning,” in Proceedings of the 2020 ACM SIGPLAN International Symposium on New Ideas, New Paradigms, and Reflections on Programming and Software , in Onward! '20. 2020, pp. 157–179. doi: 10.1145/3426428.3426922.
- [74] P. Abate, R. Di Cosmo, R. Treinen, and S. Zacchiroli, “Dependency Solving: A Separate Concern in Component Evolution Management,” Journal of Systems and Software, vol. 85, no. 10, pp. 2228–2240, 2012, doi: 10.1016/j.jss.2012.02.018.
- [75] L. Cardelli, “Structural Subtyping and the Notion of Power Type,” in Proceedings of the 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '88. 1988, pp. 70–79. doi: 10.1145/73560.73566.
- [76] B. Meyer, “ Applying "Design by Contract",” Computer, vol. 25, no. 10, pp. 40–51, 1992, doi: 10.1109/2.161279.
- [77] B. C. Pierce, “Bounded Quantification is Undecidable,” Information and Computation, vol. 112, no. 1, pp. 131–165, 1994, doi: 10.1006/inco.1994.1055.
- [78] A. Haas et al. , “Bringing the web up to speed with WebAssembly,” in Proceedings of the 38th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), ACM, 2017, pp. 185–200. doi: 10.1145/3062341.3062363.
- [79] M. M. Swift, B. N. Bershad, and H. M. Levy, “Improving the reliability of commodity operating systems,” in Proceedings of the 19th ACM Symposium on Operating Systems Principles (SOSP), ACM, 2003, pp. 207–222. doi: 10.1145/945445.945466.
- [80] M. M. Swift, M. Annamalai, B. N. Bershad, and H. M. Levy, “Recovering device drivers,” ACM Transactions on Computer Systems, vol. 24, no. 4, pp. 333–360, 2006, doi: 10.1145/1189256.1189257.
- [81] D. E. Porter, O. S. Hofmann, C. J. Rossbach, A. Benn, and E. Witchel, “Operating System Transactions,” in Proceedings of the 22nd ACM Symposium on Operating Systems Principles (SOSP), ACM, 2009, pp. 161–176. doi: 10.1145/1629575.1629591.
- [82] O. Kiselyov and C.-chieh Shan, “Delimited Continuations in Operating Systems,” in Modeling and Using Context (CONTEXT 2007) , in Lecture Notes in Computer Science, vol. 4635. Springer, 2007, pp. 291–302. doi: 10.1007/978-3-540-74255-5_22.
- [83] E. Dolstra and A. Löh, “NixOS: a purely functional Linux distribution,” in Proceedings of the 13th ACM SIGPLAN International Conference on Functional Programming (ICFP), ACM, 2008, pp. 367–378. doi: 10.1145/1411204.1411255.
- [84] ZIO, “ZIO: Type-safe, composable asynchronous and concurrent programming for Scala.” [Online]. Available: https://zio.dev/
- [85] Effect, “Effect: A TypeScript library for building robust applications.” [Online]. Available: https://effect.website/
- [86] G. Canti, “fp-ts: Functional programming in TypeScript.” [Online]. Available: https://github.com/gcanti/fp-ts
- [87] J. I. Brachthäuser, P. Schuster, and K. Ostermann, “Effects as capabilities: effect handlers and lightweight effect polymorphism,” Proc. ACM Program. Lang., vol. 4, no. OOPSLA, 2020, doi: 10.1145/3428194.
- [88] J. I. Brachthäuser, P. Schuster, E. Lee, and A. Boruch-Gruszecki, “Effects, capabilities, and boxes: from scope-based reasoning to type-based reasoning and back,” Proc. ACM Program. Lang., vol. 6, no. OOPSLA1, 2022, doi: 10.1145/3527320.
- [89] C. Heunen, R. Kaarsgaard, and M. Karvonen, “Reversible Effects as Inverse Arrows,” in Proceedings of the Thirty-Fourth Conference on the Mathematical Foundations of Programming Semantics (MFPS XXXIV), in Electronic Notes in Theoretical Computer Science, vol. 341. 2018, pp. 179–199. doi: 10.1016/j.entcs.2018.11.009.
- [90] D. Orchard, V.-B. Liepelt, and H. Eades III, “Quantitative program reasoning with graded modal types,” Proc. ACM Program. Lang. , vol. 3, no. ICFP , 2019, doi: 10.1145/3341714.
- [91] R. Bianchini, F. Dagnino, P. Giannini, E. Zucca, and M. Servetto, “Coeffects for sharing and mutation,” Proc. ACM Program. Lang. , vol. 6, no. OOPSLA2, Oct. 2022, doi: 10.1145/3563319.
- [92] R. Bianchini, F. Dagnino, P. Giannini, and E. Zucca, “ A Java-like calculus with heterogeneous coeffects,” Theoretical Computer Science, vol. 971, p. 114063, 2023, doi: 10.1016/j.tcs.2023.114063.
- [93] C. Torczon, E. Suárez Acevedo, S. Agrawal, J. Velez-Ginorio, and S. Weirich, “Effects and Coeffects in Call-by-Push-Value,” Proc. ACM Program. Lang., vol. 8, no. OOPSLA2, Oct. 2024, doi: 10.1145/3689750.
- [94] R. Hirschfeld, P. Costanza, and O. Nierstrasz, “Context-oriented Programming,” Journal of Object Technology, vol. 7, no. 3, pp. 125–151, 2008, doi: 10.5381/jot.2008.7.3.a4.
- [95] P. Costanza and R. Hirschfeld, “Language constructs for context-oriented programming: an overview of ContextL,” in Proceedings of the 2005 Symposium on Dynamic Languages (DLS '05), ACM, 2005, pp. 1–10. doi: 10.1145/1146841.1146842.
- [96] G. Salvaneschi, C. Ghezzi, and M. Pradella, “Context-oriented programming: A software engineering perspective,” Journal of Systems and Software , vol. 85, no. 8, pp. 1801–1817, 2012, doi: 10.1016/j.jss.2012.03.024.
- [97] G. Kiczales et al. , “ Aspect-Oriented Programming,” in ECOOP'97 — Object-Oriented Programming, 11th European Conference, in Lecture Notes in Computer Science, vol. 1241. Springer, 1997, pp. 220–242. doi: 10.1007/BFb0053381.
- [98] G. Kiczales, E. Hilsdale, J. Hugunin, M. Kersten, J. Palm, and W. G. Griswold, “ An Overview of AspectJ,” in ECOOP 2001 — Object-Oriented Programming, 15th European Conference, in Lecture Notes in Computer Science, vol. 2072. Springer, 2001, pp. 327–353. doi: 10.1007/3-540-45337-7_18.
- [99] A. Popovici, T. Gross, and G. Alonso, “Dynamic Weaving for Aspect-Oriented Programming,” in Proceedings of the 1st International Conference on Aspect-Oriented Software Development (AOSD 2002), ACM, 2002, pp. 141–147. doi: 10.1145/508386.508404.
- [100] J. Bonér, “What Are the Key Issues for Commercial AOP Use: How Does AspectWerkz Address Them?,” in Proceedings of the 3rd International Conference on Aspect-Oriented Software Development (AOSD 2004), ACM, 2004, pp. 5–6. doi: 10.1145/976270.976273.
- [101] M. Hicks, J. T. Moore, and S. Nettles, “Dynamic Software Updating,” in Proceedings of the ACM SIGPLAN 2001 Conference on Programming Language Design and Implementation, in PLDI '01. 2001, pp. 13–23. doi: 10.1145/378795.378798.
- [102] G. Stoyle, M. Hicks, G. Bierman, P. Sewell, and I. Neamtiu, “Mutatis Mutandis: Safe and Predictable Dynamic Software Updating,” in Proceedings of the 32nd ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, in POPL '05. 2005, pp. 183–194. doi: 10.1145/1040305.1040321.
- [103] C. M. Hayden, K. Saur, E. K. Smith, and M. Hicks, “Kitsune: Efficient, General-Purpose Dynamic Software Updating for C,” ACM Trans. Program. Lang. Syst., vol. 36, no. 4, 2014, doi: 10.1145/2629460.
- [104] M. Overeem, M. Spoor, and S. Jansen, “The Dark Side of Event Sourcing: Managing Data Conversion,” in IEEE 24th International Conference on Software Analysis, Evolution and Reengineering, in SANER '17. 2017, pp. 193–204. doi: 10.1109/SANER.2017.7884621.
- [105] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Boston, MA: Addison-Wesley, 1994.
- [106] D. Leijen, “ Algebraic Effect Handlers with Resources and Deep Finalization,” technical report MSR-TR-2018-10, Apr. 2018. [Online]. Available: https://www.microsoft.com/en-us/research/publication/algebraic-effect-handlers-resources-deep-finalization/
- [107] M. Fowler, “Event Sourcing.” 2005.
- [108] J. Lee, J. Ahn, and K. Yi, “React-tRace: A Semantics for Understanding React Hooks,” Proc. ACM Program. Lang., vol. 9, no. OOPSLA2, pp. 471–498, 2025, doi: 10.1145/3763067.
- [109] N. Shavit and D. Touitou, “Software Transactional Memory,” in Proceedings of the Fourteenth Annual ACM Symposium on Principles of Distributed Computing, in PODC '95. 1995, pp. 204–213. doi: 10.1145/224964.224987.
- [110] T. Harris, S. Marlow, S. Peyton Jones, and M. Herlihy, “Composable Memory Transactions,” in Proceedings of the Tenth ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, in PPoPP '05. 2005, pp. 48–60. doi: 10.1145/1065944.1065952.
- [111] M. Herlihy and J. E. B. Moss, “Transactional Memory: Architectural Support for Lock-Free Data Structures,” in Proceedings of the 20th Annual International Symposium on Computer Architecture, in ISCA '93. 1993, pp. 289–300. doi: 10.1145/165123.165164.
- [112] R. Landauer, “Irreversibility and Heat Generation in the Computing Process,” IBM Journal of Research and Development, vol. 5, no. 3, pp. 183–191, 1961, doi: 10.1147/rd.53.0183.
- [113] C. H. Bennett, “Logical Reversibility of Computation,” IBM Journal of Research and Development, vol. 17, no. 6, pp. 525–532, 1973, doi: 10.1147/rd.176.0525.
- [114] T. Yokoyama and R. Glück, “ A Reversible Programming Language and its Invertible Self-Interpreter,” in Proceedings of the 2007 ACM SIGPLAN Workshop on Partial Evaluation and Semantics-Based Program Manipulation , in PEPM '07. 2007, pp. 144–153. doi: 10.1145/1244381.1244404.
- [115] V. Danos and J. Krivine, “Reversible Communicating Systems,” in CONCUR 2004 — Concurrency Theory, 15th International Conference, in Lecture Notes in Computer Science, vol. 3170. Springer, 2004, pp. 292–307. doi: 10.1007/978-3-540-28644-8_19.
- [116] I. Phillips and I. Ulidowski, “Reversing Algebraic Process Calculi,” in Foundations of Software Science and Computation Structures, 9th International Conference (FOSSACS 2006), in Lecture Notes in Computer Science, vol. 3921. Springer, 2006, pp. 246–260. doi: 10.1007/11690634_17.
- [117] P. Wadler, “Linear Types Can Change the World!,” in Programming Concepts and Methods: Proceedings of the IFIP Working Group 2.2/2.3 Working Conference , North-Holland, 1990, pp. 561–581. [Online]. Available: https://homepages.inf.ed.ac.uk/wadler/papers/linear/linear.ps
- [118] D. Kim and M. C. Rinard, “Verification of Semantic Commutativity Conditions and Inverse Operations on Linked Data Structures,” in Proceedings of the 32nd ACM SIGPLAN Conference on Programming Language Design and Implementation, 2011, pp. 528–541. doi: 10.1145/1993498.1993561.
- [119] A. Lenharth, V. S. Adve, and S. T. King, “Recovery domains: an organizing principle for recoverable operating systems,” in Proceedings of the 14th International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), ACM, 2009, pp. 49–60. doi: 10.1145/1508244.1508251.
- [120] C. Walls, Spring in Action , 6th ed. Manning Publications, 2022. [Online]. Available: https://www.manning.com/books/spring-in-action-sixth-edition
- [121] C. Escoffier, R. S. Hall, and P. Lalanda, “iPOJO: an Extensible Service-Oriented Component Framework,” in IEEE International Conference on Services Computing, 2007, pp. 474–481. doi: 10.1109/SCC.2007.74.
- [122] H. Cervantes and R. S. Hall, “ Autonomous Adaptation to Dynamic Availability Using a Service-Oriented Component Model,” in Proceedings of the 26th International Conference on Software Engineering, in ICSE '04. 2004, pp. 614–623. doi: 10.1109/ICSE.2004.1317483.
- [123] C. Elliott and P. Hudak, “Functional Reactive Animation,” in Proceedings of the Second ACM SIGPLAN International Conference on Functional Programming, in ICFP '97. 1997, pp. 263–273. doi: 10.1145/258948.258973.
- [124] G. H. Cooper and S. Krishnamurthi, “Embedding Dynamic Dataflow in a Call-by-Value Language,” in Programming Languages and Systems (ESOP 2006) , in Lecture Notes in Computer Science, vol. 3924. Springer, 2006, pp. 294–308. doi: 10.1007/11693024_20.
- [125] I. Maier and M. Odersky, “Deprecating the Observer Pattern with Scala.React,” technical report EPFL-REPORT-176887, 2012. [Online]. Available: https://infoscience.epfl.ch/record/176887
- [126] E. Bainomugisha, A. L. Carreton, T. Van Cutsem, W. De Meuter, and others, “ A Survey on Reactive Programming,” ACM Comput. Surv. , vol. 45, no. 4, 2013, doi: 10.1145/2501654.2501666.
- [127] A. Margara and G. Salvaneschi, “On the Semantics of Distributed Reactive Programming: The Cost of Consistency,” IEEE Trans. Software Eng., vol. 44, no. 7, pp. 689–711, 2018, doi: 10.1109/TSE.2018.2833109.
更新日志
2026/9/27 16:07
查看所有更新日志
fcffa-feat(blog): 以 VuePress 页面形式集成《时空可组合性编程范式》中英对照论文于
版权所有
版权归属:huanghx02