定义Data.Profunctor.Choice实例失败:编译器提示left未识别
嘿,我来帮你拆解这两个问题——都是定义Profunctor的Choice实例时容易踩的低级坑,我之前折腾Profunctor的时候也栽过!
一、为什么left会被提示未识别?
你想用right调用left来实现的思路本身没问题,但编译器找不到left大概率是这几个原因:
导入出问题了:你有没有正确导入
Data.Profunctor.Choice?如果导入的时候加了hiding (left),或者用了qualified但没加别名引用,那编译器肯定找不到它。比如要是写了import qualified Data.Profunctor.Choice as C,那你得写成C.left才行。忘记先定义Profunctor实例:Choice是Profunctor的子类,你得先给你的类型写好
Profunctor实例,才能继续定义Choice实例——不然编译器根本不知道你的类型是个Profunctor,自然找不到属于Choice类的left方法。实例定义的语法小错误:比如你在
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

