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

《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.

错误原因分析

  1. 无限类型错误(Occurs check):
    你的roundFights函数当前逻辑是让rb1攻击rb2得到rb2',再让rb2'攻击rb1并返回最终的rb1状态。但Haskell的类型推导在这里陷入循环:闭包机器人的类型是多态的((String, t, t) -> a) -> a,当你试图让rb2'作为攻击者攻击rb1时,类型推导无法统一两种不同的多态实例,导致出现无限类型矛盾。

  2. 类型歧义错误:
    所有涉及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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:25:49