Haskell中Setter为何需要Traversable约束?
我研究了lens-family-core包中的setting函数,它的类型签名是:
setting :: Identical f => ((a -> b) -> s -> t) -> LensLike f s t a b
其中Identical类的定义是:
class (Traversable f, Applicative f) => Identical f
从setting的实现代码来看:
setting sec f = pure . sec (extract . f)
这里只用到了Applicative的pure和Identical的extract,所以我能理解这两个约束的必要性,但完全搞不懂为什么要加上Traversable约束。另外我发现lens包中的Setter也通过Settable约束间接包含了Traversable,看来这是Setter的普遍设计,想知道背后的原因。
核心原因:保障Setter行为合法性与体系兼容性
Traversable约束并不是直接为setting的当前实现服务,而是为了从类型层面保证Setter能遵循透镜体系的规则,同时和其他组件兼容:
- 强制执行Setter定律
Setter必须满足两个关键定律才能保证行为可预测:
- 恒等律:用
id作为修改函数时,setter id必须等于id(修改后结构不变) - 组合律:
setter (g . f)必须等于setter g . setter f(修改函数的组合对应Setter的组合)
Traversable配合Applicative能约束f这类函子的行为,避免有人定义出破坏这些定律的Identical/Settable实例。比如如果没有Traversable,可能会出现一个函子实例,它在“设置”过程中偷偷改变了原结构的形状,违反Setter的设计初衷。
与Traversal的体系兼容
在透镜生态中,Setter是Traversal的特例——所有Traversal都可以用来设置值,而Setter是只专注于设置的简化版。Traversable是Traversal的核心约束之一,让Setter继承这个约束后,就能自然地和Traversal的操作(比如traverse)无缝配合,保持整个体系的一致性,不会出现接口割裂的情况。设计层面的统一性
lens系列库的设计围绕“遍历”这个核心概念展开,Setter作为其中的一部分,复用Traversable约束可以减少额外的类型类定义,同时让整个接口更统一。Settable(lens包)和Identical(lens-family-core)都继承Traversable,本质是让这类“设置专用”的函子符合遍历的基本要求,确保它们在整个透镜生态中能正确协作。
内容的提问来源于stack exchange,提问作者snak

