了解Haskell类型签名

Cai*_*inG 25 haskell types type-signature

我正在自学Haskell,我想知道以下类型的签名:

Prelude> :t ($)
($) :: (a -> b) -> a -> b
Prelude>
Run Code Online (Sandbox Code Playgroud)

我应该如何解释(没有双关语意)?

一个半相似的结果也证明是令人费解的:

Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude>
Run Code Online (Sandbox Code Playgroud)

bhe*_*ilr 31

我会先说map.该map函数将操作应用于列表中的每个元素.如果我有

add3 :: Int -> Int
add3 x = x + 3
Run Code Online (Sandbox Code Playgroud)

然后我可以使用以下方法将其应用于整个Ints 列表map:

> map add3 [1, 2, 3, 4]
[4, 5, 6, 7]
Run Code Online (Sandbox Code Playgroud)

所以,如果你看一下类型签名

map :: (a -> b) -> [a] -> [b]
Run Code Online (Sandbox Code Playgroud)

你会看到第一个参数是(a -> b),它只是一个函数,它接受一个a并返回一个b.第二个参数是[a]类型值的列表,a返回类型[b]是类型值的列表b.因此,在简单的英语中,该map函数将函数应用于值列表中的每个元素,然后将这些值作为列表返回.

这是什么使map一个高阶函数,它接受一个函数作为参数,并做东西吧.另一种方法map是在类型签名中添加一些括号来实现它

map :: (a -> b) -> ([a] -> [b])
Run Code Online (Sandbox Code Playgroud)

所以,你也可以把它看成是从转换功能的功能ab为从功能[a][b].


该函数($)具有类型

($) :: (a -> b) -> a -> b
Run Code Online (Sandbox Code Playgroud)

并使用像

> add3 $ 1 + 1
5
Run Code Online (Sandbox Code Playgroud)

它所做的是采取什么是对正确的,在这种情况下1 + 1,并把它传递给函数在左边,在这里add3.为什么这很重要?它具有方便的固定性或运算符优先级,使其等效于

> add3 (1 + 1)
Run Code Online (Sandbox Code Playgroud)

因此,无论如何,在传递给左边之前,基本上将其包裹在括号中.这使得将多个函数链接在一起非常有用:

> add3 $ add3 $ add3 $ add3 $ 1 + 1
Run Code Online (Sandbox Code Playgroud)

比...更好

> add3 (add3 (add3 (add3 (1 + 1))))
Run Code Online (Sandbox Code Playgroud)

因为你不必关闭括号.

  • `add3 $ add3 $ add3 $ add3 $ 1 + 1` 和 `add3 之间有什么实际区别吗?添加3。添加3。add3 $ 1 + 1`? (2认同)

lef*_*out 7

好吧,正如已经说过的那样,$如果你只是忘记了currying并且在C++中看到它就可以很容易理解

template<typename A, typename B>
B dollar(std::function<B(A)> f, A x) {
  return f(x);
}
Run Code Online (Sandbox Code Playgroud)

但实际上,除了将函数应用于值之外,还有更多内容!的签名之间的明显的相似性$,并map具有实际上是一个相当深类别的理论意义:两者都是函子的同态作用的例子!

在我们一直使用的Hask类别中,对象是类型.(这有点混乱,但不要担心).态射是函数.

最着名的(endo-)仿函数是那些具有同名类型实例的仿函数.但实际上,在数学上,仿函数只是将对象映射到对象和态射到态射1的东西.map(双关语,我想!)是一个例子:它接受一个对象(即类型)A并将其映射到一个类型[A].而且,对于任何两种类型的AB,它需要一个态射(即功能)A -> B,并将其映射到类型的对应列表的功能[A] -> [B].

这只是仿函数类签名操作的一个特例:

fmap :: Functor f   =>   (a->b) -> (f a->f b)
Run Code Online (Sandbox Code Playgroud)

数学并不要求这个fmap有名字.所以也可以有身份函子,它只是为自己分配任何类型.并且,每个态度对自己:

($) :: (a->b) -> (a->b)
Run Code Online (Sandbox Code Playgroud)

"身份"显然更为普遍存在,您也可以将任何类型的值映射到自己.

id :: a -> a
id x = x
Run Code Online (Sandbox Code Playgroud)

当然,可能的实施就是这样

($) = id
Run Code Online (Sandbox Code Playgroud)

1心灵,不是映射物体和态射的任何东西都是一个算子......它确实需要满足算子定律.


Ben*_*esh 6

($)只是功能应用.它获取类型的函数,类型a->b的参数a,应用函数并返回类型的值b.

map是阅读函数类型签名有助于理解它的一个很好的例子.map第一个参数是一个获取a和返回的函数,b它的第二个参数是一个类型列表[a].因此map将类型函数应用于值a->b列表a.结果类型确实是类型[b]- b值列表!

(a->b)->[a]->[b]可以解释为"接受一个函数和一个列表并返回另一个列表",还可以解释为"接受一个类型的函数a->b并返回另一个类型的函数[a]->[b]".当你这样看时,map"升级"f(在这种情况下通常使用术语"提升")来处理列表:if double是一个加倍整数map double的函数,那么是一个加倍列表中每个整数的函数.

  • 你是对的!箭头是右关联的,所以a-> b-> c等于a - >(b-> c) - 这是一个接受a类型参数并返回b-> c类型的另一个函数的函数.如果你想要一个函数作为参数,你需要括号. (2认同)