线性约束的是使用纪律

线性类型中的“线性”指程序如何使用资源,并非线性代数中的向量或线性函数。普通类型通常约束值能参与哪些操作,线性规则还要求某个值恰好按规定使用一次,不允许无条件把它复制成两个可独立使用的资源,也不允许悄悄丢下不管。这里的使用并不必然是销毁,它也可以是返回值或把责任交给另一个线性操作。

次数依据语言的求值规则和控制路径判断,不是简单统计变量名在源码里出现几次。两个互斥分支分别使用同一个参数,不意味着运行时必定使用两次。在 Linear Haskell 的具体定义中,线性函数的保证是:如果结果恰好被消费一次,参数也恰好被消费一次;这一条件尤其需要与惰性求值一起理解。

参考:[1] [2]

文件句柄怎样接力

文件接口可以用这种纪律表达“打开、读取、关闭”的流程。2018 年《Linear Haskell》论文给出的设计中,打开文件返回线性的 File;读取一行消费当前 File,同时返回新的 File 和可不受限制使用的字节串;关闭操作则消费最后的 File,不再返回可继续读取的句柄。

这样,连续读取多行并不违反“使用一次”:每次都把当前持有的资源交给下一步,再取得后续使用所需的资源。程序若一面把旧句柄传给读取操作,一面又拿同一个旧句柄去关闭,就无法满足该接口的线性要求。忘记处理最终句柄,也不能仅靠把它放出作用域来满足严格的线性纪律。

这里的新旧句柄是类型层面的责任传递,不表示每读一行都要在操作系统中重新打开文件。接口内部可以复用同一个物理资源;类型检查所追踪的是哪些使用方式获得许可。

参考:[1]

恰好一次、至多一次与显式放宽

仿射类型通常允许至多使用一次:禁止任意复制,却允许完全不用。线性则进一步要求不能无故丢弃。因而,一个仅阻止复制的资源类型,不足以证明程序一定执行了关闭等收尾操作。是否允许自动释放、显式取消或其他退出路径,必须由具体系统规则交代。

实际语言无需让所有值都受到同样限制。GHC 用 One 和 Many 区分线性与不受限制的使用,数据构造器也可把某些字段标成不受限制;论文则用 Unrestricted 包装读取出的普通数据。可复制或可忽略的能力因此由类型和接口明确暴露,而不是偷偷撤销所有约束。对于必须收尾的资源,显式释放操作本身可以消费它;这与直接遗忘不同。

参考:[1] [2]

与所有权、引用计数的关系

所有权系统通常强调谁负责资源、哪些别名和借用合法;线性纪律关注相应值的消费次数。两者可以协同设计,却不是同一个概念。在不同语言中,把线性标在函数箭头、变量绑定或资源类型上,也会得到不同的具体规则,不能只看术语就推断整个语言的行为。

引用计数则是一种运行时管理机制。Rust 的 Rc 允许多个指针共同拥有同一堆分配,克隆 Rc 增加的是指向同一分配的共享指针,最后一个拥有者销毁时再释放内部值。这与静态禁止任意重复消费并不是同一种检查,二者也并非绝对不能共存。

线性类型能排除哪些资源错误,取决于可靠的类型规则和忠实实现这些规则的接口。底层不安全代码若虚报使用纪律,或接口根本没有编码某项条件,就不能凭“禁止复制”推出所有内存错误都已消失;它也不保证程序一定终止或外部设备不会失败。

参考:[1] [3]