You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell Traversal使用to报错:无法推导Contravariant f及Optic选型疑问

问题解答

一、类型错误的原因与解决

你的代码报错是因为startedAt的实际类型是Fold,但你给它标注了Traversal',两者的类型约束不兼容:

  • to构造的是Getter,而Getter要求f满足Contravariant约束。
  • Traversal'的类型约束仅要求f是Applicative,不包含Contravariant,因此编译器无法推导所需的约束。

而当你把代码内联到doSomething函数中时,实际是在使用Fold的能力(First类型同时满足Applicative和Contravariant),所以能正常运行。

解决方法有两种:

1. 调整类型签名为Fold'

如果你只需要读取值(不需要修改),直接把类型改成Fold' Object Int即可:

startedAt :: Fold' Object Int
startedAt = ix "at" 
          . (_Integral 
            `failing`  _String . to timeComponent . _Right 
            `failing` _String . _Integral)

2. 改为Traversal(支持修改)

如果你需要支持修改这个值(符合Traversal的双向能力),需要把to timeComponent . _Right替换为能双向转换的Prism。比如先构造一个Prism,既可以从Text解析出Int,也能把Int转回Text:

_TimeComponent :: Prism' Text Int
_TimeComponent = prism showText (\t -> timeComponent t)
-- 假设timeComponent是Text -> Either String Int,showText是Int -> Text

startedAt :: Traversal' Object Int
startedAt = ix "at" 
          . (_Integral 
            `failing`  _String . _TimeComponent
            `failing` _String . _Integral)

Prism和Traversal组合后仍然是Traversal,满足类型约束。

二、Traversal vs Prism的使用场景

用Prism的场景:

  • 处理0个或1个元素的部分同构:比如sum类型的分支(_Left/_Right)、字符串解析为特定类型(同时能反向序列化)。
  • 需要双向操作:既能从父类型提取子类型(preview),也能把子类型包装回父类型(review)。

用Traversal的场景:

  • 处理0个、1个或多个元素:比如遍历列表中的所有偶数、访问Map中的某个键(可能不存在)、嵌套结构的多个字段。
  • 不需要完整的双向同构:只需要读取或修改元素,不需要从子类型构造整个父类型。

简单来说:Prism是“可选的同构”,Traversal是“可遍历的容器访问”。Prism可以看作是特殊的Traversal,但Traversal的适用范围更广,支持多元素操作。

内容的提问来源于stack exchange,提问作者Saravanan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 18:52:58