简体   繁体   English

如何在Haskell中读取类型签名

[英]How to Read Type Signatures in Haskell

I would like understand how the type signature works, in other words: I am baffled by the output I get from :type . 我想了解类型签名的工作原理,换句话说:我对:type的输出感到困惑。

 :type (+)
(+) :: Num a => a -> a -> a

Why is it not 为什么不是

 :type (+)
(+) :: (Number, Number) -> Number

instead? 代替?

I understand that 我明白那个

(+) :: Num a => a -> a -> a

can be rewritten as 可以改写成

(+) :: Num a => a -> (a -> a)

which means that (+) takes an argument a and outputs a function with type a ->a . 这意味着(+)接受参数a并输出类型a ->a的函数。 Why does it output a function and not a number? 为什么输出函数而不是数字?

The (+) function is used as a binary operator . (+)函数用作二进制运算符 Indeed: if you write: 确实:如果您写:

5 + 2

you have, behind the curtains, written: 您在幕后写道:

(+) 5 2

So you call the function with 5 and 2 as arguments. 因此,您以52作为参数调用该函数。 Now in Haskell every function takes exactly one argument as input. 现在在Haskell中,每个函数正好将一个参数作为输入。 So (+) 5 2 is actually a compact notation of: 因此, (+) 5 2实际上是一个紧凑的表示法:

((+) 5) 2

Indeed, we first takes 5 as argument for the (+) function, and this will produce a new function that takes as input a number and returns a number such that the outcome is five plus that number. 确实,我们首先将5作为(+)函数的参数,这将产生一个函数,该函数将一个数字作为输入并返回一个数字,结果是5加该数字。

We can thus define a function: 因此,我们可以定义一个函数:

f5 = (+) 5

and then apply f5 to for example 3 and 7 , and we will get f5 3 = 8 and f5 7 = 12 . 然后将f5应用于例如37 ,我们将得到f5 3 = 8f5 7 = 12 So if we analyze the types, we will see that: 因此,如果我们分析类型,我们将看到:

(+) :: Num a => a -> (a -> a)
(+) 5 :: Num a => a -> a
((+) 5) 2 :: Num a => a

So now we can answer your question: 现在,我们可以回答您的问题:

Why does it output a function and not a number? 为什么输出函数而不是数字?

If it would immediately output a number, then we could only give it a single number, so in that case we would have written (+) 5 and immediately obtain a number. 如果它将立即输出一个数字,那么我们只能给它一个数字,因此在这种情况下,我们将写入(+) 5并立即获得一个数字。 The only logical way to do that would be to return 5 and thus let (+) act as a unary operator (an operator that takes one parameter into account). 唯一的逻辑方法是返回5 ,然后让(+)充当一元运算符 (将一个参数考虑在内的运算符 )。 But in Haskell, the (+) is a binary operator. 但是在Haskell中, (+)是二进制运算符。

Why is it not 为什么不是

 :type (+) (+) :: (Number, Number) -> Number 

Haskell has a notion of tuples. Haskell有一个元组的概念。 But again that tuple is a single argument. 但同样是元组是一个参数。 In case we would construct such function, we would call it with: 如果我们要构造这样的函数,可以用以下代码调用它:

f (2, 3)

If f = (+) , then f (2, 3) would result in 5 . 如果f = (+) ,则f (2, 3)将得出5 But it is not very flexible. 但这不是很灵活。 A frequent pattern in functional programming is taking a function, and apply a limited number of arguments on it (like we did with f5 = (+) 5 and then pass that function as an argument again. By using tuples, we lose that flexibility. 函数式编程中的一种常见模式是采用一个函数,并在函数上应用有限数量的参数(就像我们对f5 = (+) 5所做的那样,然后再次将该函数作为参数传递。)通过使用元组,我们失去了灵活性。

Since some functions work with tuples, there are two popular functions called curry :: ((a, b) -> c) -> a -> b -> c and uncurry :: (a -> b -> c) -> (a, b) -> c . 由于某些函数可用于元组,因此有两个流行的函数,分别称为curry :: ((a, b) -> c) -> a -> b -> c uncurry :: (a -> b -> c) -> (a, b) -> c curry :: ((a, b) -> c) -> a -> b -> cuncurry :: (a -> b -> c) -> (a, b) -> c These functions can transform a given function that takes a tuple as input into a function with two arguments, and vice versa. 这些函数可以将以元组作为输入的给定函数转换为具有两个参数的函数,反之亦然。

For example: 例如:

Prelude> :t uncurry (+)
uncurry (+) :: Num c => (c, c) -> c

So by constructing uncurry (+) we have constructed a function that takes as input a 2-tuple. 因此,通过构造uncurry (+)我们构造了一个将2元组作为输入的函数。

Note that curry and uncurry work only with 2-tuples (and thus two arguments). 请注意, curryuncurry仅适用于2元组(因此有两个参数)。 There are variants that work with more arguments, but these are not that popular. 有一些变体可以使用更多的参数,但是并不是很流行。

Usually using the approach of functions constructing more specialized functions allows more flexibility and thus are more useful. 通常,使用功能的方法来构造更多的专用功能可以提供更大的灵活性,因此更加有用。

Say for instance I had an uncurry version of (+) : 假设我有(+)uncurry版本:

uplus :: Num c => (c, c) -> c
uplus = uncurry (+)

Then how would I construct a partially applied function f5 :: Num c => c -> c with that? 那我该如何构造一个部分应用的函数f5 :: Num c => c -> c I could write it like: 我可以这样写:

f5 :: Num c => c -> c
f5 x = uplus (5, x)

but this requires a lot more syntax. 但这需要更多语法。 Furthermore if I extend uplus to work with 3-tuples. 此外,如果我扩展uplus以使用3元组。 I have to introduce more variables ( y , z , etc.) to write the partially applied function. 我必须引入更多变量( yz等)来编写部分应用的函数。

Why is it not 为什么不是

 :type (+) (+) :: (Number, Number) -> Number 

I am assuming that with Number you mean any type in class Num . 我假设使用Number表示Num类中的任何类型。

This would make ti possible to add an Integer (numeric type) and a Complex Double (another numeric type) and get as a result a Rational . 这样就可以添加一个Integer (数字类型)和Complex Double (另一个数字类型)并得到一个Rational We can't really do that. 我们真的不能那样做。

We can only sum two values of the same numeric type, and obtain a result of the same type. 我们只能将两个相同数字类型的值求和,并获得相同类型的结果。 We need to express that all these three numeric types are equal. 我们需要表示这三个数值类型都是相等的。 This is done by the standard signature: 这是通过标准签名完成的:

(+) :: Num a => a -> a -> a

If you instead consider Number as a single, specific, numeric type, then we could not add Int s, since that is a different type. 如果改为将Number视为单个特定的数字类型,则我们不能添加Int ,因为那是另一种类型。 We could remove all numeric types and use only Number , extending it to encompass complexes, rationals, integers, and all other numeric types. 我们可以删除所有数值类型,而仅使用Number ,将其扩展为包含复数,有理数,整数和所有其他数值类型。 The programmer would then lose control of the underlying representation (making it hard to predict performance) and types would be much less precise: we can't guarantee that length :: [a] -> Number does not return a complex number, for instance... 然后,程序员将失去对基本表示形式的控制(难以预测性能),并且类型的精度将大大降低:例如,我们不能保证length :: [a] -> Number不返回复数...

The two types for (+) you suggested are actually isomorphic (the same). 您建议的(+)的两种类型实际上是同构的(相同的)。 To prove two things are isomorphic, we must show that there is a pair of invertible functions which go back and forth between the representations. 为了证明两件事是同构的,我们必须证明在表示之间来回有一对可逆函数。 These functions already exist in the standard library as uncurry and curry 这些函数已经以uncurrycurry形式存在于标准库中

> :t uncurry (+)
uncurry (+) :: Num a => (a, a) -> a

> let add (a, b) = a + b
> :t curry add
curry add :: Num a => a -> a -> a

Proving that curry/uncurry are actually inverses of each other is an exercise for the reader. 对读者来说,证明curry/uncurry实际上是彼此curry/uncurry的,是一种练习。

To your second question "why does (+) take one argument and return a function rather than a number", this is called partial application. 关于第二个问题“为什么(+)为什么要带一个参数并返回一个函数而不是一个数字”,这称为部分应用程序。 Calling (+) with a single argument returns a partially applied function that captures or "closes over" that argument. 使用单个参数调用(+)返回部分应用的函数,该函数捕获或“关闭”该参数。 For example, 例如,

addOne :: Num a => a -> a
addOne = (1+)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM