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

如何为Lens的MyDataType类型添加Applicative上下文解决GHC报错?

你的两个Haskell问题解答

一、为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:06:55