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

定义Data.Profunctor.Choice实例失败:编译器提示left未识别

搞定Choice实例的两个小坑

嘿,我来帮你拆解这两个问题——都是定义Profunctor的Choice实例时容易踩的低级坑,我之前折腾Profunctor的时候也栽过!

一、为什么left会被提示未识别?

你想用right调用left来实现的思路本身没问题,但编译器找不到left大概率是这几个原因:

  1. 导入出问题了:你有没有正确导入Data.Profunctor.Choice?如果导入的时候加了hiding (left),或者用了qualified但没加别名引用,那编译器肯定找不到它。比如要是写了import qualified Data.Profunctor.Choice as C,那你得写成C.left才行。

  2. 忘记先定义Profunctor实例:Choice是Profunctor的子类,你得先给你的类型写好Profunctor实例,才能继续定义Choice实例——不然编译器根本不知道你的类型是个Profunctor,自然找不到属于Choice类的left方法。

  3. 实例定义的语法小错误:比如你在right的实现里拼写错了left,或者把left写在了实例块外面?给你个正确的实例结构参考:

import Data.Profunctor
import Data.Profunctor.Choice
import Data.Either (swapEither)

-- 先定义Profunctor实例
instance Profunctor MyProfunctor where
  dimap f g p = ... -- 你的dimap实现

-- 再定义Choice实例,用left实现right的标准写法
instance Choice MyProfunctor where
  left f = ... -- 你的left逻辑
  right f = dimap swapEither swapEither $ left f

这里用swapEither把Either的左右换过来,让left能处理原本right要处理的分支,再换回去,这是用left实现right的常规操作。

二、为什么left的Right分支必须拆包重包?

这个问题其实是类型对齐的硬性要求,或者和你的Profunctor结构有关:

  • 如果是简单的函数包装类Profunctor,比如newtype FuncProfunctor a b = FuncProfunctor (a -> b),那left的Right分支直接返回Right b就可以,完全不需要拆包重包:
left (FuncProfunctor f) = FuncProfunctor $ \case
  Left a -> Left (f a)
  Right b -> Right b
  • 但如果你的Profunctor带上下文(比如Kleisli monadic函数),那Right分支就必须适配上下文结构。比如Kleisli m a b = Kleisli (a -> m b)的left实现里,Right分支得写成return (Right b)——这看起来像拆包重包,但其实是Kleisli的结构要求返回值必须在monad上下文里,不是类型标注能绕过的。

如果你用普通Profunctor却遇到这个问题,大概率是类型推断没跟上。试试给Right分支的表达式加个显式类型标注:

Right b :: Either (f a) b

强制编译器对齐类型,说不定就能解决。

最后给你几个排查小技巧

  • 打开ghci输入:i left,看看它在不在作用域里,要是提示Not in scope,那肯定是导入的问题
  • 检查你的Profunctor实例是不是符合Profunctor定律,有时候结构写错了也会导致类型对齐出问题
  • 要是还是卡壳,把你的Profunctor类型定义贴出来,一眼就能看出问题在哪!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:54:09