我是Haskell的新手,我试图理解为什么需要编写类型声明。由于Haskell有类型推断,我什么时候需要第一行? GHCI似乎使用':t'
生成正确的输出我到目前为止发现的唯一一个似乎需要声明的例子如下。
maximum' :: (Ord a) => [a] -> a
maximum' = foldr1 max
但是,如果我添加“-XNoMonomorphismRestriction”,则不再需要标志声明。是否存在类型推断不起作用且需要指定类型的特定情况?
因为我可能在类型声明中有一个错误并且没有直接的好处,所以我宁愿不写它。我刚刚开始学习Haskell,所以如果我错了请纠正我,因为我想养成良好的习惯。
编辑:事实证明,Type inference is a double-edged sword一书的Real World Haskell部分对此主题进行了很好的讨论。
答案 0 :(得分:28)
undefined
。一切都编译我知道我的想法似乎不太糟糕。然后我继续用真实代码替换undefined
答案 1 :(得分:15)
考虑read "5"
。 Haskell如何知道read "5"
的类型?它不能,因为没有办法解决操作的结果,因为read
被定义为(Read a) => String -> a
。 a
不依赖于字符串,因此必须使用上下文。
但通常上下文类似Ord
或Num
,因此无法确定。这不是单形态限制,而是另一种永远无法正确处理的情况。
示例:
不起作用
read "0.5"
putStrLn . show . read $ "0.5"
有效吗
read "0.5" :: Float
putStrLn . show . (read :: String -> Float) $ "0.5"
这些是必要的,因为如果我没记错的话,默认的Show
实例是Int
。
答案 2 :(得分:5)
安心。有时确保编译器同意您对函数类型应该是什么的看法是有益的。如果推断的类型没有与您给定的类型统一,那么编译器会对您大喊大叫。熟悉类型系统后,您会发现可选类型签名对您的编码信心非常有利。
答案 3 :(得分:4)
这通常是因为它使阅读更容易,有时更容易编写。在像Haskell这样的强类型语言中,通常你会发现自己创建了一些函数来获取某些类型并输出另一种类型,并发现自己依赖于这些类型而不是它们的名称。在你习惯了类型系统的工作方式之后,它可以让你更清楚你想要做什么,如果你做错了什么,编译器可以抓住你。
但这是偏好的事情。如果您习惯使用动态类型语言,则可能会发现指定任何类型都不比指定类型容易。这只是使用Haskell提供的强类型系统的两种不同方式。
然后有时类型推断不起作用,例如另一个答案给出的“读取”示例。但这些是内联类型定义,而不是函数的类型定义。
答案 4 :(得分:3)
我在任何答案中都没有看到的一个重要事项是,在写下任何实际代码之前,您通常会实际编写类型定义并键入签名。一旦你完成了“规范”,你的实现就会在编写时对其进行检查,这样可以在编译器检查你的类型匹配时更容易捕获错误。例如,如果您知道某些内容应该具有签名Int -> Int -> [a] -> [a]
,但在编写时,而不是实例化两个参数x
和y
,则只实例化一个参数{{1}偶然使用它两次,编译器将在您定义函数的位置捕获错误,而不是您尝试使用它时应该如何使用它。