混淆于Haskell类型 [*]
我正在开始学习Haskell,并对Haskell的类型推断结果感到困惑(如下所示的示例)。不幸的是,我的Haskell水平还不够熟练,无法用适当的方式表达真正的问题,只能通过示例进行工作。
[*] 一旦我知道真正的问题,我会更新说明..
我的目标
我正在跟随《使用Haskell进行编程》书的第10课中介绍的一种“保持状态”的方法:
-- The result of calling robot (`r`) is a function that takes
-- another function (`message` as argument). The parameters
-- passed to the initial call to `robot` are treated
-- as state and can be passed to `message`.
--
robot name health attack = \message -> message name health attack
getName r = r (\name _ _ -> name)
klaus = robot "Klaus" 50 5
*Main> getName klaus
"Klaus"
我理解了其要义并尝试创建一个小型机器人战斗。最终,我想要像这样的东西:
klaus = robot "Klaus" 50 5
peter = robot "Peter" 50 5
victor = fight klaus peter
getName victor
-- should be "Klaus"
我的机器人
以下是我编写的实现:
robot name health attack = \message -> message name health attack
isAlive r = r (\_ health _ -> health > 0)
fight attacker defender = if isAlive attacker then
attacker
else
defender
printRobot r = r (\name health attack -> "Name: " ++ (show name) ++", health: " ++ (show health) ++ ", attack: " ++ (show attack))
klaus = robot "Klaus" 50 5
peter = robot "Peter" 60 7
在ghci中进行实验
该代码可以在ghci中加载 (:l robots.hs
)。当我试图测试我的代码时,发现事情并不完全按照计划进行:类型系统和我似乎对结果类型的看法不太一样。
请指出我的推理错误之处,并帮助我理解我的错误:
--
-- in ghci
--
*Main> :t klaus
-- I understood:
-- klaus is a function. I have to pass a function that
-- takes name, health, and attack as parameters and
-- returns something of type "t".
--
-- A result of same type "t" is then returned by calling klaus
klaus :: ([Char] -> Integer -> Integer -> t) -> t
-- check the "isAlive" function:
-- As expected, it returns a Bool
*Main> :t isAlive klaus
isAlive klaus :: Bool
-- This is also expected as klaus has health > 0
*Main> isAlive klaus
True
-- Inspecting the type of `isAlive` confuses me:
--
-- I do understand:
--
-- The first parameter is my "robot". It has to accept a function
-- that returns a boolean (basically the isAlive logic):
--
-- (t1 -> a -> t -> Bool)
-- - t1: name, ignored
-- - a: health, needs to be a comparable number
-- - t: attack value, ignored
-- - returns boolean value if the health is >0
--
-- What I do NOT understand is, why doesn't it have the following type
-- isAlive :: (Ord a, Num a) => (t1 -> a -> t -> Bool) -> Bool
*Main> :t isAlive
isAlive :: (Ord a, Num a) => ((t1 -> a -> t -> Bool) -> t2) -> t2
-- The signature of `isAlive` bites me in my simplified
-- fight club:
-- If the attacker is alive, the attacker wins, else
-- the defender wins:
fight attacker defender = if isAlive attacker then
attacker
else
defender
-- I would expect the "fight" function to return a "robot".
-- But it does not:
*Main> victor = fight klaus peter
*Main> :t victor
victor :: ([Char] -> Integer -> Integer -> Bool) -> Bool
*Main> printRobot klaus
"Name: \"Klaus\", health: 50, attack: 5"
*Main> printRobot peter
"Name: \"Peter\", health: 60, attack: 7"
*Main> printRobot victor
<interactive>:25:12: error:
• Couldn't match type ‘[Char]’ with ‘Bool’
Expected type: ([Char] -> Integer -> Integer -> [Char]) -> Bool
Actual type: ([Char] -> Integer -> Integer -> Bool) -> Bool
• In the first argument of ‘printRobot’, namely ‘victor’
In the expression: printRobot victor
In an equation for ‘it’: it = printRobot victor
这些问题
isAlive
的签名为什么不是(t1 -> a -> t -> Bool) -> Bool
?fight
函数有什么问题?
我所学到的
根据我目前的理解,我无法修复问题,但现在(多亏了@chi的出色答案),我可以理解问题。
为了帮助其他遇到同样问题的初学者,这里有一个简化版本的问题及我的推理过程:
- 我通过
buildSSIclosure
构建一个闭包,其中包含两个字符串s1
、s2
和一个整数i1
。通过“发送消息”(传递函数)到闭包中,我可以访问闭包的“状态”。 - 我可以编写简单的访问器
getS1
、getS2
和getI1
- 我想编写一个函数,它接受一个
ssiClosure
并通过访问器获取Int
和[Char]
属性。
-- IMPORTANT: the return value `t` is not bound to a specific type
buildSSIclosure :: [Char] -> [Char] -> Int -> ([Char] -> [Char] -> Int -> t) -> t
buildSSIclosure s1 s2 i1 = (\message -> message s1 s2 i1)
buildSSIclosure
的定义中有t
未绑定。当使用访问器中的任何一个时,ssiClosure
实例的t
将会绑定到一个类型。getS1 :: (([Char] -> [Char] -> Int -> [Char]) -> [Char]) -> [Char]
getS2 :: (([Char] -> [Char] -> Int -> [Char]) -> [Char]) -> [Char]
getI1 :: (([Char] -> [Char] -> Int -> Int) -> Int) -> Int
-- `t` is bound to [Char]
getS1 ssiClosure = ssiClosure (\ s1 _ _ -> s1)
-- `t` is bound to [Char]
getS2 ssiClosure = ssiClosure (\ _ s2 _ -> s2)
-- `t` is bound to int
getI1 ssiClosure = ssiClosure (\ _ _ i1 -> i1)
我直接访问了lambda函数调用的两个参数。这样做可以绑定t
到[Char]
:
getS1I1_direct ssiClosure = ssiClosure (\ s1 _ i1 -> s1 ++ ", " ++ show i1)
调用两个字符串访问器
我可以通过这些访问器访问S1
和S2
。这是因为getS1
和 getS2
会将t
从ssiClosure
绑定到[Char]
:
getS1S2_indirect ssiClosure = show (getS1 ssiClosure) ++ ", " ++ show(getS2 ssiClosure)
访问Char和Int
接下来的步骤是访问int和string属性。但这样做是无法编译通过的!
这是我的理解:
- 调用
getS1
需要将闭包中的t
绑定到[Char]
- 调用
getI1
需要将闭包中的t
绑定到Int
它不能同时绑定到两者,因此编译器告诉我这个错误:
getS1I1_indirect ssiClosure = show(getS1 ssiClosure) ++ ", " ++ show(getI1 ssiClosure)
• Couldn't match type ‘[Char]’ with ‘Int’
Expected type: ([Char] -> [Char] -> Int -> Int) -> Int
Actual type: ([Char] -> [Char] -> Int -> [Char]) -> [Char]
• In the first argument of ‘getI1’, namely ‘ssiClosure’
In the first argument of ‘show’, namely ‘(getI1 ssiClosure)’
In the second argument of ‘(++)’, namely ‘show (getI1 ssiClosure)’
我仍然没有通过查看错误来识别问题的技能。但是还有希望 ;-)
RankNTypes
比Rank2Types
更受欢迎。 - bradrn