2015-07-21 51 views
4

我正在使用“了解你的haskell教程”并已到达类型声明部分。我知道他们改变了GHCI给你一个错误信息的方式,但它们是否也会影响实际功能的工作方式?如果没有,它是否就像在“def someFunction(x):”下面用“”“”“”写成的python函数文档一样? - 只是举个例子haskell类型声明与Python类/函数文档使用相同的方式吗?

示例代码:

removeNonUppercase :: [Char] -> [Char] 
removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']] 

编辑:我问这个,因为教程解释说,Haskell是在编译时类型推断。

+0

不完全。我相信Haskell会根据你的代码检查你的类型声明。 Python是动态输入的,不会这样做。 – Kevin

+0

我明白了。因此,尽管在编译时确定了类型(而不必象在java/C++中那样声明类型),但显式使用类型声明还是可以避免编译错误的机会?我对haskell很陌生。 – Byte

+1

这是我的理解,Haskell代码可以很深奥,有时编译器将无法推断该函数的类型。即使可以,对于那些不熟悉类型推断所需的抽象推理的人类读者也会有所帮助。在这种情况下,它是一种文档形式,但编译器强制执行正确性,不像(说)评论,它可以做到这一点。 – Kevin

回答

8

签名不仅仅适用于文档(即使它们对于此也非常有用)。它们由编译器强制执行,这意味着通过添加签名可以使您的函数的类型比其他方式更具限制性。玩具例子:

add x y = x + y 

addInt :: Int -> Int -> Int 
addInt x y = x + y 
*Main> :t add 
add :: Num a => a -> a -> a 
*Main> add 2 3 
5 
*Main> add 2.1 3.1 
5.2 
*Main> :t addInt 
addInt :: Int -> Int -> Int 
*Main> addInt 2 3 
5 
*Main> addInt 2.1 3.1 -- addInt will not accept non-Ints. 

<interactive>:23:8: 
    No instance for (Fractional Int) arising from the literal ‘2.1’ 
    In the first argument of ‘addInt’, namely ‘2.1’ 
    In the expression: addInt 2.1 3.1 
    In an equation for ‘it’: it = addInt 2.1 3.1 

除此之外,加入类型签名意味着您将得到更好的(即更容易理解)在棘手的情况下错误,因为编译器会知道你想达到的,而不是什么必须自行猜测所有事情。

在某些情况下,如果没有某些签名或其他类型注释的帮助,编译器无法确定类型。也许最简单的例子是:

readAndShow s = show (read s) 

如果您尝试使用不指定任何类型的...

Foo.hs:6:17: 
    No instance for (Show a0) arising from a use of ‘show’ 
    The type variable ‘a0’ is ambiguous 
    Note: there are several potential instances: 
     instance (GHC.Arr.Ix a, Show a, Show b) => Show (GHC.Arr.Array a b) 
     -- Defined in ‘GHC.Arr’ 
     instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’ 
     instance (Integral a, Show a) => Show (GHC.Real.Ratio a) 
     -- Defined in ‘GHC.Real’ 
     ...plus 26 others 
    In the expression: show (read s) 
    In an equation for ‘readAndShow’: readAndShow s = show (read s) 

Foo.hs:6:23: 
    No instance for (Read a0) arising from a use of ‘read’ 
    The type variable ‘a0’ is ambiguous 
    Note: there are several potential instances: 
     instance (GHC.Arr.Ix a, Read a, Read b) => Read (GHC.Arr.Array a b) 
     -- Defined in ‘GHC.Read’ 
     instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’ 
     instance (Integral a, Read a) => Read (GHC.Real.Ratio a) 
     -- Defined in ‘GHC.Read’ 
     ...plus 25 others 
    In the first argument of ‘show’, namely ‘(read s)’ 
    In the expression: show (read s) 
    In an equation for ‘readAndShow’: readAndShow s = show (read s) 
Failed, modules loaded: none. 

...它不会工作。 readString转换为一些类型,而show则相反。但是,如果没有指定read s的类型,则编译器无法确定要将String读作哪种类型。所以,你要么需要指定中间型...

readAndShowAsInt s = show (read s :: Int) 
*Main> readAndShowAsInt "2" 
"2" 

...还是有别的东西挑类型为您提供:

readAndAdd :: String -> Int -> Int 
readAndAdd s y = read s + y 
*Main> readAndAdd "2" 3 
5 
2

类型声明是作为程序员用于理智检查代码的工具。 Haskell所做的类型推断将始终是正确的 - 但它可能不是您在编写代码时所期望的。你可能期望一个类型为Int - > Int - > Int的函数,但是你的代码可能会告诉推理算法该函数是类型的(Int,Int) - > Int,因为你忘记了括号。

它们也作为程序员的文档形式。有时在编写代码时看到函数的类型非常有帮助。通常(如果函数名字很好),你可以通过查看它的名字和类型来确定函数应该做什么。文档仍然非常有用,但是当您开始编写和阅读更多功能程序时,类型会成为您用来帮助您理解代码的东西。

+0

谢谢。如果类型声明与编译器类型推断不同,它会给我一个错误还是继续编译? – Byte

+0

它会出错。如果给定的类型与推断的类型不同,类型推断算法总是基于您提供的源并且会投诉。但就像我说的,有时候你的意思是不同的,只是输入了错误的代码。 –

+0

您可以创建一个与编译器推断的不同的正确类型定义。 duplode的答案中有一个例子。此外,它看起来像使用ML风格的语法而不是Haskell语法。第一种类型应该是'Int - > Int - > Int',第二种应该是'(Int,Int) - > Int'。 –

4

在简单的情况下,该类型声明仅仅是哈斯克尔会推断出相同类型的,并且仅仅是文档+有助于消除混乱的错误。但是也有例外:

(1)您可以给出更多受限制的类型声明。例如。

squareInt :: Int -> Int 
squareInt x = x*x 

如果没有声明,那可以推断为Num t => t -> t。 但是有了它,该功能只能用于Int s,不适用于任何数字类型。这对于将函数限制为预期用例或防止类型歧义错误有时很有用。 (2)许多语言扩展需要类型声明,因为它们超出了推理算法支持的类型系统部分。例如,等级n类型:

f :: (forall x. Show x => y) -> (Int, Bool) -> (y, y) 
f g (i, b) = (g i, g b) 

这也包括标准的Haskell特征“多态递归”的,这意味着调用一个函数递归与不同型比它最初调用。