《Get Programming with Haskell》闭包实现机器人战斗的类型错误问题
Haskell闭包实现机器人战斗的类型错误排查与修复
问题背景
我在学习《Get Programming with Haskell》时,尝试用闭包实现基于(name, attack, hp)元组的简单机器人对象。单独调用fight函数模拟战斗可以正常运行,但把战斗步骤封装成roundFights函数时,触发了一系列类型系统相关错误。
原源码
robot (name, attack, hp) = \message -> message (name, attack, hp) name (nm, _, _) = nm attack (_, a, _) = a hp (_, _, p) = p getName r = r name getAttack r = r attack getHP r = r hp setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp) setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp) setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp) printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad) fight attacker defender = damage defender power where power = if getHP attacker > 10 then getAttack attacker else 0 lives = map getHP roundFights rb1 rb2 = let rb2' = fight rb1 rb2 in fight rb2' rb1 rb1 = robot("Killer", 25, 200) rb2 = robot("Slayer", 15, 200)
报错信息
D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:27:18: Occurs check: cannot construct the infinite type: t8 ~ ((t7, t8, t8) -> t0) -> t0 Expected type: ((t7, t8, t8) -> ((t7, t8, t8) -> t0) -> t0) -> t6 Actual type: ((t7, t8, t8) -> t8) -> t6 Relevant bindings include rb2' :: ((t4, t5, t5) -> t5) -> t8 (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:26:8) rb2 :: ((t2, t3, t6) -> ((t2, t3, t6) -> t) -> t) -> ((t4, t5, t5) -> t5) -> t8 (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:17) rb1 :: ((t7, t8, t8) -> t8) -> t6 (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:13) roundFights :: (((t7, t8, t8) -> t8) -> t6) -> (((t2, t3, t6) -> ((t2, t3, t6) -> t) -> t) -> ((t4, t5, t5) -> t5) -> t8) -> t6 (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:1) In the second argument of `fight', namely `rb1' In the expression: fight rb2' rb1 D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:30:27: No instance for (Num t1) arising from the literal `200' The type variable `t1' is ambiguous Relevant bindings include rb1 :: (([Char], t1, t1) -> t) -> t (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:30:1) Note: there are several potential instances: instance Num Double -- Defined in `GHC.Float' instance Num Float -- Defined in `GHC.Float' instance Integral a => Num (GHC.Real.Ratio a) -- Defined in `GHC.Real' ...plus three others In the expression: 200 In the first argument of `robot', namely `("Killer", 25, 200)' In the expression: robot ("Killer", 25, 200) D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:31:27: No instance for (Num t1) arising from the literal `200' The type variable `t1' is ambiguous Relevant bindings include rb2 :: (([Char], t1, t1) -> t) -> t (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:31:1) Note: there are several potential instances: instance Num Double -- Defined in `GHC.Float' instance Num Float -- Defined in `GHC.Float' instance Integral a => Num (GHC.Real.Ratio a) -- Defined in `GHC.Real' ...plus three others In the expression: 200 In the first argument of `robot', namely `("Slayer", 15, 200)' In the expression: robot ("Slayer", 15, 200) D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:33:8: No instance for (Ord t1) arising from a use of `fight' The type variable `t1' is ambiguous Relevant bindings include rb2' :: (([Char], t1, t1) -> t) -> t (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:33:1) Note: there are several potential instances: instance Integral a => Ord (GHC.Real.Ratio a) -- Defined in `GHC.Real' instance Ord () -- Defined in `GHC.Classes' instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes' ...plus 24 others In the expression: fight rb1 rb2 In an equation for rb2': rb2' = fight rb1 rb2 D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:34:8: No instance for (Ord t1) arising from a use of `fight' The type variable `t1' is ambiguous Relevant bindings include rb1' :: (([Char], t1, t1) -> t) -> t (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:34:1) Note: there are several potential instances: instance Integral a => Ord (GHC.Real.Ratio a) -- Defined in `GHC.Real' instance Ord () -- Defined in `GHC.Classes' instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes' ...plus 24 others In the expression: fight rb2' rb1 In an equation for rb1': rb1' = fight rb2' rb1 D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:35:9: No instance for (Ord t1) arising from a use of `fight' The type variable `t1' is ambiguous Relevant bindings include rb2'' :: (([Char], t1, t1) -> t) -> t (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:35:1) Note: there are several potential instances: instance Integral a => Ord (GHC.Real.Ratio a) -- Defined in `GHC.Real' instance Ord () -- Defined in `GHC.Classes' instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes' ...plus 24 others In the expression: fight rb1' rb2' In an equation for rb2'': rb2'' = fight rb1' rb2' Failed, modules loaded: none.
错误原因分析
无限类型错误(Occurs check):
你的roundFights函数当前逻辑是让rb1攻击rb2得到rb2',再让rb2'攻击rb1并返回最终的rb1状态。但Haskell的类型推导在这里陷入循环:闭包机器人的类型是多态的((String, t, t) -> a) -> a,当你试图让rb2'作为攻击者攻击rb1时,类型推导无法统一两种不同的多态实例,导致出现无限类型矛盾。类型歧义错误:
所有涉及Num和Ord的错误,都是因为没有给机器人相关函数添加明确的类型签名。Haskell无法确定attack和hp的具体类型(比如是Int还是Integer),导致类型变量歧义,无法找到对应的类型类实例。
修复方案
步骤1:添加明确的类型签名
先定义Robot类型别名,给所有函数添加类型签名,明确attack和hp为Int类型:
type Robot = ((String, Int, Int) -> a) -> a robot :: (String, Int, Int) -> Robot robot (name, attack, hp) = \message -> message (name, attack, hp) name :: (String, Int, Int) -> String name (nm, _, _) = nm attack :: (String, Int, Int) -> Int attack (_, a, _) = a hp :: (String, Int, Int) -> Int hp (_, _, p) = p getName :: Robot -> String getName r = r name getAttack :: Robot -> Int getAttack r = r attack getHP :: Robot -> Int getHP r = r hp setName :: Robot -> String -> Robot setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp) setAttack :: Robot -> Int -> Robot setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp) setHP :: Robot -> Int -> Robot setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp) printRobot :: Robot -> String printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp damage :: Robot -> Int -> Robot damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad) fight :: Robot -> Robot -> Robot fight attacker defender = damage defender power where power = if getHP attacker > 10 then getAttack attacker else 0 lives :: [Robot] -> [Int] lives = map getHP
步骤2:修正roundFights的逻辑
原来的roundFights只返回单个机器人的状态,不符合战斗一轮的逻辑(双方各攻击一次后,应该返回两个机器人的最新状态)。修改为返回状态元组:
roundFights :: Robot -> Robot -> (Robot, Robot) roundFights rb1 rb2 = let rb2' = fight rb1 rb2 rb1' = fight rb2' rb1 in (rb1', rb2')
步骤3:明确机器人实例的类型
给rb1和rb2添加类型签名,避免歧义:
rb1 :: Robot rb1 = robot ("Killer", 25, 200) rb2 :: Robot rb2 = robot ("Slayer", 15, 200)
完整可运行代码
type Robot = ((String, Int, Int) -> a) -> a robot :: (String, Int, Int) -> Robot robot (name, attack, hp) = \message -> message (name, attack, hp) name :: (String, Int, Int) -> String name (nm, _, _) = nm attack :: (String, Int, Int) -> Int attack (_, a, _) = a hp :: (String, Int, Int) -> Int hp (_, _, p) = p getName :: Robot -> String getName r = r name getAttack :: Robot -> Int getAttack r = r attack getHP :: Robot -> Int getHP r = r hp setName :: Robot -> String -> Robot setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp) setAttack :: Robot -> Int -> Robot setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp) setHP :: Robot -> Int -> Robot setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp) printRobot :: Robot -> String printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp damage :: Robot -> Int -> Robot damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad) fight :: Robot -> Robot -> Robot fight attacker defender = damage defender power where power = if getHP attacker > 10 then getAttack attacker else 0 lives :: [Robot] -> [Int] lives = map getHP roundFights :: Robot -> Robot -> (Robot, Robot) roundFights rb1 rb2 = let rb2' = fight rb1 rb2 rb1' = fight rb2' rb1 in (rb1', rb2') rb1 :: Robot rb1 = robot ("Killer", 25, 200) rb2 :: Robot rb2 = robot ("Slayer", 15, 200) -- 测试代码 main :: IO () main = do let (rb1', rb2') = roundFights rb1 rb2 putStrLn $ "After one round:" putStrLn $ printRobot rb1' putStrLn $ printRobot rb2'
总结
- 类型歧义问题通过添加明确的类型签名解决,让Haskell明确
attack和hp是Int类型,消除了Num和Ord实例的歧义。 - 无限类型错误是因为
roundFights的逻辑不符合闭包机器人的类型推导规则,改为返回双方状态的元组后,类型可以正确统一。 - 闭包风格的对象在Haskell中需要注意多态类型的边界,明确类型签名能避免很多推导问题。
内容的提问来源于stack exchange,提问作者BarbedWire
相关产品推荐
相关产品推荐

