为含函数属性的Function记录实现Functor实例的问题
你的Function类型中,类型参数a同时出现在逆变位置(函数输入的[Exp a])和协变位置(返回值的Either (RawException a) (Exp a)),而Functor的核心要求是类型参数仅出现在协变位置——这就是编译器报错的根本原因。
你当前的fmap实现只处理了返回值的协变映射,但完全没处理输入参数的逆变转换:新的funcApply'仍然接受[Exp a]作为输入,但Function b要求它接受[Exp b],类型自然不匹配。
两种可行解决方案
1. 改用Profunctor处理双向映射
因为a同时涉及逆变和协变位置,Profunctor的dimap函数正好能处理这种场景——它可以同时对输入做反向映射,对输出做正向映射。首先需要导入profunctors包中的Profunctor类型类:
import Data.Profunctor -- 调整类型定义,分离输入和输出的类型参数 data Function a b = Function { funcApply :: FData -> [Exp a] -> Either (RawException b) (Exp b) } instance Profunctor Function where dimap g f (Function apply) = Function $ \fData exps -> case apply fData (fmap g exps) of Left e -> Left (fmap f e) Right x -> Right (fmap f x)
这里dimap g f会把输入的[Exp b]通过g :: b -> a转成[Exp a]传给原函数,再把返回值通过f :: a -> b映射成目标类型,完美匹配需求。
2. 调整Function定义,让a仅出现在协变位置
如果坚持要实现Functor,需要修改Function的结构,让a只出现在返回值这类协变位置,把输入参数的类型和a解耦:
-- 拆分类型参数,c对应输入类型,a对应输出类型 data Function c a = Function { funcApply :: FData -> [Exp c] -> Either (RawException a) (Exp a) } instance Functor (Function c) where fmap f (Function apply) = Function $ \fData exps -> case apply fData exps of Left e -> Left (fmap f e) Right x -> Right (fmap f x)
此时Function c作为Functor,仅对协变位置的a进行映射,输入参数的c不受影响,编译器就能顺利通过。
关于“funcApply本身已是多态”的补充
如果你的funcApply确实是多态的(比如能处理任意a,类型为forall a. FData -> [Exp a] -> Either (RawException a) (Exp a)),那应该改用RankNTypes扩展定义单态的Function:
{-# LANGUAGE RankNTypes #-} data Function = Function { funcApply :: forall a. FData -> [Exp a] -> Either (RawException a) (Exp a) }
这种情况下,Function不需要任何Functor实例,因为它本身就能处理任意类型的Exp。
内容的提问来源于stack exchange,提问作者Chris J Harris

