# 可组合性与插件系统 ## 组合 编程的本质就是组合。将小的构建块拼装为更大的系统,再将大系统作为块继续拼装——这是从函数到模块到微服务一脉相承的思想。 组合可以分为两种: - **静态组合**:编译期确定的组合,例如函数调用、模块导入。 - **动态组合**:运行时确定的组合,例如热更新、插件加载/卸载。 静态组合是逻辑的组合;动态组合为可组合性引入了时间和空间两个新维度。 ## 三种可组合性 | 维度 | 定义 | 对应问题 | |------|------|----------| | **逻辑可组合性** (Logical) | 功能能否被任意拆分和组装 | 接口设计是否正交 | | **时间可组合性** (Temporal) | 能否灵活、安全地控制组合的运行时序 | 能否热加载/卸载而不泄漏 | | **空间可组合性** (Spatial) | 能否灵活、安全地管理组合的依赖关系 | 依赖缺失时行为是否确定 | 一门编程语言或应用框架越多地使用组合范式,就称它的可组合性越好。 ## 传统插件系统的问题 插件系统是动态组合的典型形式。浏览器扩展、IDE 插件、操作系统驱动,都是其实例。然而大多数插件系统并不可靠。 ### 不可逆的插件化 以 VSCode 为例: - 卸载或更新插件时需要重启整个系统。 - 无法在运行时追踪和回收副作用,导致内存泄漏和非预期的资源占用。 - 即便提供了 `deactivate` 钩子,也无法强制开发者正确实现清理逻辑。 **根本原因**:未做到时间可组合——系统不知道某个插件产生了哪些副作用、占用了哪些资源。 ### 不完全的插件化 - 无法表达插件间的依赖关系,扩展能力受限。 - 只有外围功能被下放给插件,核心功能依然通过修改主体代码来实现。 **根本原因**:未做到空间可组合——系统缺乏对依赖关系的建模和管理。 ## Cordis 的解法 Cordis 同时解决了上述两个问题: 1. **可逆作用** (Revertible Effects) 实现时间可组合性——所有注册自动追踪、自动回收。 2. **响应式余作用** (Reactive Coeffects) 实现空间可组合性——依赖声明驱动加载顺序。 两者通过**上下文模型** (Context Model) 统一为单一的编程范式:开发者只需通过 `ctx` 调用框架 API,可逆性和依赖管理由框架保证。 ## 在 Harness 中的体现 DeepSeek Harness 将 Cordis 的可组合性应用到 Agent 开发领域: ```ts import type { Context } from 'cordis' import { defineTool } from '@deepseek-ai/dsh-tools' import type {} from '@deepseek-ai/dsh-llm' // 一个 Harness 插件天然是可逆的 export const inject = ['tools', 'llm'] // 空间可组合:声明依赖 export function apply(ctx: Context) { // 时间可组合:注册会被自动追踪和回收 ctx.tools.register(defineTool({ name: 'my-tool', description: '...', parameters: { /* ... */ }, async execute(args) { return [] }, })) } ``` 插件卸载时,tool 自动注销、事件监听自动移除——无需手动清理。依赖的服务(如 `llm`)消失时,插件自动挂起;恢复时自动重新加载。