简体   繁体   English

了解Haskell类型签名

[英]Understanding Haskell Type Signatures

I am in the process of teaching myself Haskell and I was wondering about the following type signatures: 我正在自学Haskell,我想知道以下类型的签名:

Prelude> :t ($)
($) :: (a -> b) -> a -> b
Prelude>

How should I interpret (no pun intended) that? 我应该如何解释(没有双关语意)?

A semi-similar result is also proving to be puzzling: 一个半相似的结果也证明是令人费解的:

Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude>

I'll start with map . 我将从map开始。 The map function applies an operation to every element in a list. map函数将操作应用于列表中的每个元素。 If I had 如果我有

add3 :: Int -> Int
add3 x = x + 3

Then I could apply this to a whole list of Int s using map : 然后我可以使用map将它应用于整个Int的列表:

> map add3 [1, 2, 3, 4]
[4, 5, 6, 7]

So if you look at the type signature 所以,如果你看一下类型签名

map :: (a -> b) -> [a] -> [b]

You'll see that the first argument is (a -> b) , which is just a function that takes an a and returns a b . 你会看到第一个参数是(a -> b) ,它只是一个带a并返回b的函数。 The second argument is [a] , which is a list of values of type a , and the return type [b] , a list of values of type b . 第二个参数是[a] ,它是类型a的值列表,返回类型[b]是类型b的值列表。 So in plain english, the map function applies a function to each element in a list of values, then returns the those values as a list. 因此,简单地说, map函数将函数应用于值列表中的每个元素,然后将这些值作为列表返回。

This is what makes map a higher order function , it takes a function as an argument and does stuff with it. 这就是使map成为一个更高阶函数的原因 ,它将一个函数作为一个参数并对其进行处理。 Another way to look at map is to add some parentheses to the type signature to make it 查看map另一种方法是在类型签名中添加一些括号来制作它

map :: (a -> b) -> ([a] -> [b])

So you can also think of it as a function that transforms a function from a to b into a function from [a] to [b] . 因此,您也可以将其视为将函数从a转换为b[a][b]的函数的函数。


The function ($) has the type 函数($)具有类型

($) :: (a -> b) -> a -> b

And is used like 并使用像

> add3 $ 1 + 1
5

All it does is take what's to the right , in this case 1 + 1 , and passes it to the function on the left , here add3 . 它所做的只是取右边的东西 ,在这种情况下是1 + 1 ,并将它传递给左边的函数,这里是add3 Why is this important? 为什么这很重要? It has a handy fixity , or operator precedence, that makes it equivalent to 它具有方便的固定性或运算符优先级,使其等效于

> add3 (1 + 1)

So whatever to the right gets essentially wrapped in parentheses before being passed to the left. 因此,无论如何,在传递给左边之前,基本上将其包裹在括号中。 This just makes it useful for chaining several functions together: 这使得将多个函数链接在一起非常有用:

> add3 $ add3 $ add3 $ add3 $ 1 + 1

is nicer than 比...更好

> add3 (add3 (add3 (add3 (1 + 1))))

because you don't have to close parentheses. 因为你不必关闭括号。

Well, as said already, $ can be easily understood if you just forget about currying and see it like, say, in C++ 好吧,正如已经说过的那样,如果你只是忘记currying并且在C ++中看到它就可以很容易地理解$

template<typename A, typename B>
B dollar(std::function<B(A)> f, A x) {
  return f(x);
}

But actually, there is more to this than just applying a function to a value! 但实际上,除了将函数应用于值之外,还有更多内容! The apparent similarity between the signatures of $ and map has in fact a pretty deep category-theory meaning: both are examples of the morphism-action of a functor! $map的签名之间的明显相似性实际上是一个非常深刻的类别理论意义:两者都是仿函数的一个例子 - 仿函数!

In the category Hask that we work with all the time, objects are types. 在我们一直使用的Hask类别中,对象是类型。 ( That is a bit confusionsome , but don't worry). 这有点混乱 ,但不要担心)。 The morphisms are functions. 态射是函数。

The most well-known (endo-) functors are those which have an instance of the eponymous type class . 最着名的(endo-) 仿函数是那些具有同名类型实例的仿函数 But actually, mathematically, a functor is only something that maps both objects to objects and morphisms to morphisms 1 . 但实际上,在数学上,仿函数只是将对象映射到对象和态射到态射1的东西 map (pun intended, I suppose!) is an example: it takes an object (ie type) A and maps it to a type [A] . map (双关语,我想!)就是一个例子:它取一个对象(即类型) A并将其映射到一个类型[A] And, for any two types A and B , it takes a morphism (ie function) A -> B , and maps it to the corresponding list-function of type [A] -> [B] . 并且,对于任何两种类型AB ,它采用态射(即函数) A -> B ,并将其映射到类型[A] -> [B]的相应列表函数。

This is just a special case of the functor class signature operation: 这只是仿函数类签名操作的一个特例:

fmap :: Functor f   =>   (a->b) -> (f a->f b)

Mathematics doesn't require this fmap to have a name though. 数学并不要求这个fmap有一个名字。 And so there can be also the identity functor , which simply assigns any type to itself. 所以也可以有身份函子 ,它只是为自己分配任何类型。 And, every morphism to itself: 并且,每个态度对自己:

($) :: (a->b) -> (a->b)

"Identity" exists obviously more generally, you can also map values of any type to themselves. “身份”显然更为普遍存在,您也可以将任何类型的值映射到自己。

id :: a -> a
id x = x

And sure enough, a possible implementation is then 当然,可能的实施就是这样

($) = id

1 Mind, not anything that maps objects and morphisms is a functor... it does need to satisfy the functor laws . 1心灵,不是映射物体和态射的任何东西都是一个算子......它确实需要满足算子定律

($) is just function application. ($)只是功能应用程序。 It gets a function of type a->b , an argument of type a , applies the function and returns a value of type b . 它得到类型的函数a->b ,类型的参数a ,应用功能,并返回类型的值b

map is a wonderful example for how reading a function type signature helps understanding it. map是阅读函数类型签名有助于理解它的一个很好的例子。 map 's first argument is a function that takes a and returns b , and its second argument is a list of type [a] . map的第一个参数是一个带a和返回b的函数,它的第二个参数是一个类型为[a]的列表。 So map applies a function of type a->b to a list of a values. 所以map应用类型的函数a->b到的列表a值。 And the result type is indeed of type [b] - a list of b values! 结果类型确实是[b]类型 - b值列表!

(a->b)->[a]->[b] can be interpreted as "Accepts a function and a list and returns another list", and also as "Accepts a function of type a->b and returns another function of type [a]->[b] ". (a->b)->[a]->[b]可以解释为“接受函数和列表并返回另一个列表”,也可以解释为“接受类型a->b函数并返回另一个函数类型[a]->[b] “。 When you look at it this way, map "upgrade" f (the term "lift" is often used in this context) to work on lists: if double is a function that doubles an integer, then map double is a function that double every integer in a list. 当你以这种方式看待它时, map “upgrade”f(在这个上下文中通常使用术语“lift”)来处理列表:如果double是一个加倍整数的函数,那么map double是一个map double的函数列表中的整数。

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

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