如何为Lens的MyDataType类型添加Applicative上下文解决GHC报错?
一、为MyDataType添加Applicative上下文
要给自定义类型MyDataType添加Applicative实例,首先得确保它有合法的Functor实例(因为Applicative是Functor的扩展)。假设你的MyDataType结构类似MyDataType1(包含多个Maybe字段的ADT),下面是具体实现步骤:
1. 准备工作:开启扩展与导入模块
先开启必要的语言扩展,并导入核心模块:
{-# LANGUAGE TemplateHaskell #-} -- 用于自动生成Lens import Control.Applicative import Control.Lens
2. 定义MyDataType并生成Lens
假设MyDataType的结构如下(你可以根据实际结构调整):
data MyDataType a = MyDataType { _mdValue :: Maybe a , _mdCounter :: Maybe Int } deriving (Show) -- 自动生成对应字段的Lens makeLenses ''MyDataType
3. 实现Functor实例
Applicative依赖Functor,先为MyDataType实现Functor实例:
instance Functor MyDataType where fmap f (MyDataType val counter) = MyDataType (fmap f val) counter
这里我们只对包含泛型a的_mdValue字段应用映射,其他字段保持不变,符合Functor的定律。
4. 实现Applicative实例
Applicative需要实现两个核心函数:pure(将值包装进类型)和<*>(应用包装后的函数到包装后的值):
instance Applicative MyDataType where -- pure: 把值放入泛型字段,其他字段用合理默认值(这里用Just 0作为计数器默认) pure x = MyDataType (Just x) (Just 0) -- <*>: 对每个字段分别应用对应类型的Applicative操作(复用Maybe的合法实例) (MyDataType funcVal funcCount) <*> (MyDataType valVal valCount) = MyDataType (funcVal <*> valVal) (funcCount <*> valCount)
这个实现符合Applicative的四大定律,因为我们复用了Maybe已经验证过的合法Applicative实例。如果你的MyDataType结构不同,核心思路是:
pure为每个字段提供对应类型的默认值(调用该字段类型的pure)<*>对每个字段分别执行对应类型的<*>操作
二、解决setThis函数的Traversal报错问题
问题根源
你遇到的报错是因为dmsTypes . _Just . _j不是Lens',而是Traversal':
dmsTypes是Lens' MyDataType1 (Maybe DMS),聚焦Maybe DMS类型_Just是Prism' (Maybe a) a,用来从Maybe a中提取a,但它属于Traversal(因为可能匹配失败,比如遇到Nothing)- 组合后得到的
dmsTypes . _Just . _j是Traversal' MyDataType1 (Maybe Float),而你的setThis函数参数要求的是Lens'——Lens'只要求Functor上下文,而Traversal'需要Applicative上下文,因此GHC无法推导所需约束。
解决方法
把setThis的参数类型从Lens'改为Traversal',因为Traversal'兼容Lens'(所有Lens'都是Traversal'的特例),同时支持需要Applicative的组合(比如_Just)。另外,考虑到你的字段都是Maybe类型,可以直接固定泛型为Maybe,让类型更清晰:
setThis :: Traversal' MyDataType1 (Maybe Float) -> Float -> MyDataType1 -> MyDataType1 -> MyDataType1 setThis func toThis dataType1 dataType2 = dataType2 & func %~ (\a -> (+) <$> a <*> delta) where baseStat = dataType1 ^. func delta = (* toThis) <$> baseStat -- 简化lambda写法,更简洁
为什么这样有效?
Traversal'的类型签名是forall f. Applicative f => (a -> f a) -> s -> f s,明确要求Applicative上下文,刚好满足_Just的需求- 原来的
Lens'只要求Functor,无法兼容需要Applicative的Traversal组合 - 固定泛型为
Maybe贴合你的实际使用场景,避免不必要的多态复杂度
现在你调用setThis (dmsTypes . _Just . _j) 0.3 someY someY就不会再报错了。
内容的提问来源于stack exchange,提问作者atis

