Haskell刚性类型变量编译错误修复求助:Step工厂函数问题
问题与解决方案
问题描述
定义了Step类型类,基于它实现了Step1、Step2等具体类型,用Lens实现类型类方法。编写工厂函数mkStep通过case逻辑根据参数返回对应Step实例时,出现两个编译错误:
- 无法将实际类型
Step2匹配为刚性类型变量q Step1与Step2类型不匹配
怀疑和Lens仅在运行时可用有关,寻求解决方案。
相关代码
Step类型类与实例定义
class (Eq q, User a, User b, User c) => Step q a b c where {-# MINIMAL performerA, performerB, performerC, completionDate #-} performerA :: Lens' q a performerB :: Lens' q b performerC :: Lens' q (Maybe c) data Step1 makeLenses ''Step1 instance Step Step1 UserType1 UserType2 UserType3 where ... data Step2 makeLenses ''Step2 instance Step Step2 UserType1 UserType2 UserType4 where ...
工厂函数mkStep
mkStep :: (User a, User b, User c, Step q b a c) => Package a b -> b -> Maybe c -> Either ValidationError q mkStep package performer' nextPerformer' = do return step where step = case (package ^. currentPerformer . memberType, performer' ^. memberType, nextPerformer' ^. memberType) of (MemberType3, MemberType2, MemberType4) -> Step1 performer' (package ^. currentPerformer) nextPerformer' (MemberType3, MemberType2, MemberType1) -> Step2 performer' (package ^. currentPerformer) nextPerformer'
关联类型定义
data MemberType = MemberType1 | MemberType2 | MemberType3 | MemberType4 deriving (Show, Eq) class (Eq a, Show a) => User a where {-# MINIMAL memberType, userId #-} memberType :: Lens' a MemberType userId :: Lens' a MemberId data UserType1 makeLenses ''UserType1 instance User UserType1 where ... data UserType2 makeLenses ''UserType2 instance User UserType2 where ... data UserType3 makeLenses ''UserType3 instance User UserType3 where ... data UserType4 makeLenses ''UserType4 instance User UserType4 where ... data Package a b = Package { _currentPerformer :: a , _nextPerformer :: Maybe b } deriving (Show, Eq) makeLenses ''Package
编译错误信息
/home/aoaddeola/ss-model-ddd/src/Steps.hs:103:10: error: • Couldn't match expected type ‘q’ with actual type ‘Step2’ ‘q’ is a rigid type variable bound by the type signature for: mkStep :: forall a b c q. (User a, User b, User c, Step q b a c) => Package a b -> b -> Maybe c -> Either ValidationError q at src/Steps.hs:(96,1)-(100,34) • In the first argument of ‘return’, namely ‘step’ In a stmt of a 'do' block: return step In the expression: do return step • Relevant bindings include mkStep :: Package a b -> b -> Maybe c -> Either ValidationError q (bound at src/Steps.hs:101:1) | 103 | return step | ^^^^ /home/aoaddeola/ss-model-ddd/src/Steps.hs:109:18: error: • Couldn't match type ‘Step1’ with ‘Step2’ Expected: Step2 Actual: Step1 • In the expression: Step1 performer' (package ^. currentPerformer) nextPerformer' In a case alternative: (MemberType3, MemberType2, MemberType4) -> Step1 performer' (package ^. currentPerformer) nextPerformer' In the expression: case (package ^. currentPerformer . memberType, performer' ^. memberType, nextPerformer' ^. memberType) of (MemberType3, MemberType2, MemberType1) -> Step2 performer' (package ^. currentPerformer) nextPerformer' (MemberType3, MemberType2, MemberType4) -> Step1 performer' (package ^. currentPerformer) nextPerformer' | 109 | -> Step1 performer' (package ^. currentPerformer) nextPerformer' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
解决方案
核心原因
和Lens无关,这是Haskell静态类型系统的基本约束:
- case表达式的所有分支必须返回同一类型,但当前分支返回的
Step1和Step2是完全不同的类型 - 类型签名中的
q是刚性类型变量,由调用方指定,函数内部不能动态返回不同的q类型
方案1:用代数数据类型(ADT)统一返回类型
定义一个包装所有Step类型的ADT,让case分支返回同一ADT的不同构造函数:
-- 定义统一的ADT data AnyStep = AnyStep1 Step1 | AnyStep2 Step2 -- 修改mkStep的返回类型 mkStep :: (User a, User b, User c) => Package a b -> b -> Maybe c -> Either ValidationError AnyStep mkStep package performer' nextPerformer' = do return $ case (package ^. currentPerformer . memberType, performer' ^. memberType, nextPerformer' ^. memberType) of (MemberType3, MemberType2, MemberType4) -> AnyStep1 $ Step1 performer' (package ^. currentPerformer) nextPerformer' (MemberType3, MemberType2, MemberType1) -> AnyStep2 $ Step2 performer' (package ^. currentPerformer) nextPerformer'
之后可以直接针对AnyStep编写操作逻辑,或者为它实现Step类型类(需要调整类型参数适配不同的User类型)。
方案2:用存在类型隐藏具体类型
如果想保留Step类型类的多态性,用存在类型包装所有实现Step的实例:
{-# LANGUAGE ExistentialQuantification #-} -- 定义存在类型,包装所有符合Step约束的类型 data SomeStep = forall q a b c. Step q a b c => SomeStep q -- 修改mkStep的类型签名和返回值 mkStep :: (User a, User b, User c) => Package a b -> b -> Maybe c -> Either ValidationError SomeStep mkStep package performer' nextPerformer' = do return $ case (package ^. currentPerformer . memberType, performer' ^. memberType, nextPerformer' ^. memberType) of (MemberType3, MemberType2, MemberType4) -> SomeStep $ Step1 performer' (package ^. currentPerformer) nextPerformer' (MemberType3, MemberType2, MemberType1) -> SomeStep $ Step2 performer' (package ^. currentPerformer) nextPerformer'
调用方可以通过Step类型类的方法操作SomeStep内部的值,无需关心具体是Step1还是Step2。
额外注意:检查类型参数顺序
你的mkStep类型签名中Step q b a c的参数顺序可能和实例不匹配:
Step1的实例是Step Step1 UserType1 UserType2 UserType3(对应q=Step1, a=UserType1, b=UserType2, c=UserType3)- 但函数中
performer'是b类型,却传给Step1的第一个参数,而Step1的performerA是Lens' Step1 UserType1,这里可能参数顺序搞反了,需要调整类型签名或实例的参数顺序,确保类型匹配。
内容的提问来源于stack exchange,提问作者Fiftywebs
相关产品推荐
相关产品推荐

