在书中"Get programming with Haskell",我偶然发现了相当混乱的练习。其中一章解释了如何使用闭包创建简单对象。所以,例如,我们有一个元组,它摧毁了一个原始的机器人:
(name, attack, hp)
。使用这个元组,我们可以构建一个这样的机器人:
robot (name,attack,hp) = \message -> message (name,attack,hp)
例如:
killerRobot = robot ("Kill3r", 25, 200)
依此类推,作者解释了如何使用这种结构制作访问器功能:
hp (_,_,hp) = hp
attack (_,a,_) = a
getHP aRobot = aRobot hp
getAttack aRobot = aRobot attack
所以我们可以检查某个机器人有多少生命值:
getHP killerRobot
到目前为止一直很好,我不打算重写整个章节,但我不能再抓到另一件事了。接下来我们有一个功能:
damage aRobot attackDamage = aRobot (\(n,a,h) ->
robot (n,a,h-attackDamage))
和另一个功能
fight aRobot defender = damage defender attack
where attack = if getHP aRobot > 10
then getAttack aRobot
else 0
模仿两个机器人之间的战斗。 所以我们可以这样写:
gentleGiant = robot ("Mr. Friendly", 10, 300)
gentleGiantRound1 = fight killerRobot gentleGiant
killerRobotRound1 = fight gentleGiant killerRobot
gentleGiantRound2 = fight killerRobotRound1 gentleGiantRound1
killerRobotRound2 = fight gentleGiantRound1 killerRobotRound1
gentleGiantRound3 = fight killerRobotRound2 gentleGiantRound2
killerRobotRound3 = fight gentleGiantRound2 killerRobotRound2
它工作得很好。但是当我尝试将它放入一个函数时,它将这些步骤封装并返回最后一步的结果(实际上任务略有不同)我得到了一大堆与类型系统相关的错误。我将展示一个引发错误的简化版本:
roundFights rb1 rb2 =
let rb2' = fight rb1 rb2
in fight rb2' rb1
第二次战斗使编译器爆炸出错。所有这些功能都没有故意的类型签名 - 书中的内容相同 - 因为它只是介绍性章节之一,类型签名尚未解释。
有人可以提出错误吗?
以下是源代码:
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 atacker defender = damage defender power where
power = if getHP atacker > 10
then getAttack atacker
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)
和我得到的erorrs表:
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.
答案 0 :(得分:4)
据我所知,您的代码在无类型设置(例如,Scheme或JavaScript)中运行良好。
在类型设置中,它可以工作,但前提是它涉及相当复杂的类型,即rank-2类型。类型推理引擎无法推断那些必须手动注释的那些。
为了强调这一点,让我们尝试仅使用rank-1类型,添加所有注释。这部分编译得很好。
type Robot a = ((String, Int, Int) -> a) -> a
robot :: (String, Int, Int) -> Robot a
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 -> String
getName r = r name
getAttack :: Robot Int -> Int
getAttack r = r attack
getHP :: Robot Int -> Int
getHP r = r hp
setName :: Robot (Robot a) -> String -> Robot a
setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp)
setAttack :: Robot (Robot a) -> Int -> Robot a
setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp)
setHP :: Robot (Robot a) -> Int -> Robot a
setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp)
printRobot :: Robot String -> String
printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp
damage :: Robot (Robot a) -> Int -> Robot a
damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad)
fight :: Robot Int -> Robot (Robot a) -> Robot a
fight atacker defender = damage defender power where
power = if getHP atacker > 10
then getAttack atacker
else 0
上面,Robot a
表示机器人值,该值只能用于计算a
类型的值。例如。从Robot Int
您可以提取攻击和HP,但不能提取名称。
查看代码......出现了许多奇怪的类型! fight
的类型非常令人费解:
fight :: Robot Int -> Robot (Robot a) -> Robot a
第一个机器人必须产生它的攻击,所以它是Robot Int
,而第二个机器人必须战斗并产生Robot a
,因此奇怪的类型Robot (Robot a)
。
由此我们得出结论,我们无法同时输入fight r1 r2
和fight r2 r1
:这需要Int = Robot a
,这是不可能的。
• Couldn't match type ‘Int’ with ‘((String, Int, Int) -> a) -> a’
Expected type: Robot (Robot a)
Actual type: Robot Int
可能是什么解决方案?使用rank-2机器人:
newtype Robot = Robot (forall a. ((String, Int, Int) -> a) -> a)
此处forall a
表示排名2机器人可以生成我们选择的任何结果,而不仅仅是单个结果。因此,从rank-2机器人中我们可以提取名称和HP。
我们需要使用构造函数来包装/解包所有内容,这可能有点烦人:
robot :: (String, Int, Int) -> Robot
robot (name, attack, hp) = Robot (\message -> message (name, attack, hp))
getName :: Robot -> String
getName (Robot r) = r name
-- etc.
现在,fight
应该有效。我将剩下的剩余时间让OP尝试。
请注意,理论结果(Yoneda的引理)表明我们使用的多态类型forall a. ((String, Int, Int) -> a) -> a
与(String, Int, Int)
是同构的,所以我们确实以更复杂的方式重新构造了元组。
总结:我对Haskell的一本书提出了这种方法感到有些惊讶。对我来说这似乎是非常先进的材料。我想知道目标解决方案是什么。