- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我开始学习 haskell,并对 haskell 类型推断的结果感到困惑(参见下面的示例)。不幸的是,我对 haskell 的掌握不够流利,无法提出真正的问题,所以我必须通过示例来工作。
[*]一旦我知道真正的问题,我就会更新标题..
我正在关注Get programming with 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 ( :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的精彩回答)我可以理解这个问题。
对于所有其他陷入同样陷阱的初学者,这是我对问题的简化版本的推理:
s1
, s2
和一个 int i1
通过buildSSIclosure
。通过“发送消息”(传递函数)到闭包中,我可以访问闭包的“状态”。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
不受约束。当使用任何访问器时,t
ssiClosure
的实例绑定(bind)到一个类型:
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 函数调用的两个参数这有效并将绑定(bind) t
至[Char]
:
getS1I1_direct ssiClosure = ssiClosure (\ s1 _ i1 -> s1 ++ ", " ++ show i1)
我可以访问 S1
和S2
通过访问器。这是有效的,因为 getS1
,和getS2
绑定(bind)t
来自ssiClosure
至[Char]
:
getS1S2_indirect ssiClosure = show (getS1 ssiClosure) ++ ", " ++ show(getS2 ssiClosure)
下一步是访问 int 和 string 属性。那甚至无法编译!
这是我的理解:
getS1
需求t
从关闭到绑定(bind)到[Char]
getI1
需求t
从关闭到绑定(bind)到Int
它不能同时绑定(bind)到两者,因此编译器告诉我:
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)’
我仍然没有能力通过查看错误来识别问题。但还是有希望的;-)
最佳答案
Why is the signature of
isAlive
not(t1 -> a -> t -> Bool) -> Bool
?
isAlive r = r (\_ health _ -> health > 0)
让我们从 lambda 开始。我想你可以看到这一点
(\_ health _ -> health > 0) :: a -> b -> c -> Bool
哪里b
必须都是 Ord
类(对于 >
)和 Num
(对于0
)
自从争论r
以 lambda 作为输入,它必须是一个以 lambda 作为输入的函数:
r :: (a -> b -> c -> Bool) -> result
最后,isAlive
需要r
作为参数,并返回与 r
相同的结果。因此:
isAlive :: ((a -> b -> c -> Bool) -> result) -> result
添加约束并稍微重命名类型变量,我们得到 GHCi 的类型:
isAlive :: (Ord a, Num a) => ((t1 -> a -> t -> Bool) -> t2) -> t2
请注意,这种类型比以下类型更通用:
isAlive :: (Ord a, Num a) => ((t1 -> a -> t -> Bool) -> Bool) -> Bool
大致意思是“给我一个Bool
生成机器人,我会给你一个Bool
”。
What is wrong with my
fight
function?
fight attacker defender = if isAlive attacker then
attacker
else
defender
这很棘手。上面的代码调用isAlive attacker
这迫使 attacker
输入 (a -> b -> c -> Bool) -> result
。然后,result
必须是Bool
因为它用在if
中。此外,这使得defender
具有与 attacker
相同的类型因为 if then else
的两个分支必须返回相同类型的值。
因此,输出 fight
必须是“Bool
-生成机器人”,即不再能够生成其他任何东西的机器人。
这可以使用 2 级类型来修复,但如果您是初学者,我不建议您现在尝试此操作。对于初学者来说,这个练习看起来相当高级,因为传递了很多 lambda。
从技术上讲,您在各处传递 Church 编码的元组,而这仅适用于 2 级多态性。传递一阶元组会简单得多。
无论如何,这里有一个可能的解决方案。这打印 Klaus
作为获胜者。
{-# LANGUAGE Rank2Types #-}
isAlive :: (Ord h, Num h) => ((n -> h -> a -> Bool) -> Bool) -> Bool
isAlive r = r (\_ health _ -> health > 0)
-- A rank-2 polymorphic robot, isomorphic to (n, h, a)
type Robot n h a = forall result . (n -> h -> a -> result) -> result
fight :: (Ord h, Num h) => Robot n h a -> Robot n h a -> Robot n h a
fight attacker defender = if isAlive attacker
then attacker
else defender
robot :: n -> h -> a -> Robot n h a
robot name health attack = \message -> message name health attack
printRobot :: (Show n, Show h, Show a) => ((n -> h -> a -> String) -> String) -> String
printRobot r = r (\name health attack ->
"Name: " ++ show name ++
", health: " ++ show health ++
", attack: " ++ show attack)
klaus, peter :: Robot String Int Int
klaus = robot "Klaus" 50 5
peter = robot "Peter" 60 7
main :: IO ()
main = do
let victor = fight klaus peter
putStrLn (printRobot victor)
我建议您为每个顶级函数添加类型。虽然 Haskell 可以推断这些,但对于程序员来说,手头有类型是非常方便的。此外,如果您编写了您想要的类型,GHC 会检查它。经常发生 GHC 推断出程序员不希望的类型的情况,使代码看起来很好,但实际上并非如此。当推断的类型与代码的其余部分不匹配时,这通常会导致程序稍后出现令人费解的类型错误。
关于haskell - 为什么高阶函数会返回这种(意外的)类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57123449/
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许在 Stack Overflow 上提出有关通用计算硬件和软件的问题。您可以编辑问题,使其成为
当我尝试在 db2 中创建表时,它抛出以下错误 $ db2 CREATE TABLE employee(emp_id INT NOT NULL, emp_name VARCHAR(100)) sh:
我有: while (i < l) { if (one === two) { continue; } i++; } 但是 JSLint 说: Problem at line 1 chara
所以我有这个代码: char inputs[10] = ""; int numInputs = 0; while (numInputs < 10){ char c; printf("E
var ninja = { name: 'Ninja', say: function () { return 'I am a ' + this.name; }
我收到一个我不明白的错误,请注意,我是编码新手,所以这可能是一个简单的错误。 #include using namespace std; int main() { //Initialise Fahr
我正在使用 javascript 和 react,由于某种原因,我收到了一个奇怪的 token 错误。 这是发生错误的代码: renderNavBar() { if (!this.us
Closed. This question is off-topic。它当前不接受答案。
由于某种我无法解释的原因,编译器正在输出一个错误,指出它发现了一个意外的#else 标记。 这发生在文件的开头: #if defined( _USING_MFC ) #include "stda
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
这个问题在这里已经有了答案: Difference between sh and Bash (11 个答案) 关闭 2 年前。 我正在编写一个简单的 bash 脚本,我在 XX `(' unexpe
关闭。这个问题是not reproducible or was caused by typos .它目前不接受答案。 此问题是由拼写错误或无法再重现的问题引起的。虽然类似的问题可能是 on-topic
我在 Windows 7 上编写了一个脚本,它不断给我一个错误“(此时出乎意料。”对于以下代码 if %vardns%=="NODNS" ( netsh interface ipv4 set ad
我正在尝试使用xmlstarlet(使用xpath)解析XML文件,但是出现语法错误,并且我不知道如何更正我的代码。 这是我的脚本: #!/bin/bash if [ $1=="author" ];
以下脚本旨在在目录中的所有文件上运行程序“senna”,并将每个文件的输出(保留输入文件名)写入另一个目录 for file in ./Data/in/*; do ./senna -iobta
我从 challengers.coffee 运行此代码,并收到错误 ActionView::Template::Error (SyntaxError: [stdin]:3:31:unexpected
我在 config.db.database; 行中有语法错误(意外的标记“.”)。这是我在文件中的代码 const config = require('../config/config') const
这一定很明显,但是我无法使它正常工作。我正在尝试传输应该用于构建$ classKey的对象,这反过来又导致删除所需的软件(amd64或i386)。好吧,这里的代码: $name = @("softwa
我正在使用 1.3.7 版学习 Grails,但我一直无缘无故地遇到以下语法错误: unexpected token: mapping @ line x, column y. 有一次,我通过运行“gr
我正在尝试找出这段Pascal代码的问题 function Factorial(n: integer): integer; begin if n = 0 then Result := 1
我是一名优秀的程序员,十分优秀!