西蒙·马洛(Simon Marlow)读的书“ Parallel and Concurrent Programming in Haskell ”我遇到了一个不确定的事情,为什么它以GHC的方式工作。即:
evalPair :: Strategy a -> Strategy b -> Strategy (a,b)
evalPair sa sb (a,b) = do
a' <- sa a
b' <- sb b
return (a',b')
在类型级别具有两个输入参数(我看到Strategy a
和Strategy b
的方式)和一个输出-Strategy (a,b)
。
但是下面的行有三个参数:sa
,sb
和(a,b)
。令人困惑。
但是由于Strategy
是类型同义词:
type Strategy a = a -> Eval a
我认为,如果我将策略展开为(a->评估a),可能会更清楚。所以:
evalPair :: (a -> Eval a) -> (b -> Eval b) -> (a,b) -> Eval (a,b)
和(末尾添加了括号)
evalPair :: (a -> Eval a) -> (b -> Eval b) -> ((a,b) -> Eval (a,b))
都编译。
然后我以这种方式编写它,并且借助Stack工具提示,发现函数evalPair返回的内容(故意返回到Strategy以使其更加混乱)不是 Strategy (a,b)
,而是 Eval (a,b)
。
evalPair :: Strategy a -> Strategy b -> Strategy (a,b)
evalPair sa sb (a,b) =
let res = do
a' <- sa a
b' <- sb b
return (a', b')
in res
因此很明显,编译器从类型同义词中解开了最后一个参数,但仅解开了最后一个参数-因为我们不需要为Strategy a
提供值
和Strategy b
这是我的问题:
在哪里可以获得有关此编译器行为的更多信息?为什么函数返回Eval a
,即使它说它返回Strategy a
如果发生拆包,那为什么我不需要(实际上也不能)像这样提供Strategy a
和Strategy b
的值:
evalPair :: Strategy a -> Strategy b -> Strategy (a,b)
evalPair a sa a sb (a,b) = do
a' <- sa a
b' <- sb b
return (a',b')
答案 0 :(得分:4)
给出类型同义词
type Strategy a = a -> Eval a
和类型
Strategy a -> Strategy b -> Strategy (a,b)
我们可以通过将同义词的每次使用替换为其定义来对类型进行“解糖”:
(a -> Eval a) -> (b -> Eval b) -> ((a,b) -> Eval (a,b))
请注意,在此处必须使用括号以阐明正在发生的情况。函数evalPair
仍带有两个参数。它的两个参数是两个 function 。如果我在视觉上将类型与其相应的参数对齐,则可能会更清楚:
evalPair :: (a -> Eval a) -> (b -> Eval b) -> (a,b) -> Eval (a,b)
evalPair sa sb (a,b) = ...
因此,sa
的类型为a -> Eval a
,而sb
的类型为b -> Eval b
。
请注意,Haskell报告指出:
类型同义词是一种方便但严格的句法机制,可使类型签名更具可读性。同义词及其定义是完全可以互换的
因此,编译器可以自由地“包装”或“解开”类型的同义词,因为它们是“完全可互换的”。
您可以在Haskell报告的第4.2.2节中了解有关类型同义词的信息: https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-730004.2.2
答案 1 :(得分:4)
您误解了Haskell函数的工作原理。
所有Haskell函数只有个参数。如果函数f
的类型为A
,并且返回类型为B
,则我们将写为f :: A -> B
。
但是,当然,我们可以编写带有多个参数的函数,但是以一种聪明的方式称为currying。如果我们想要一个函数g
接受一个A
和一个B
并产生一个C
,我们实际上编写了一个接受A
的函数,并且然后返回一个接受B
并返回C
的函数。
我们将其写为g :: A -> (B -> C)
。但是,由于这是我们通常要对函数执行的操作,因此Haskell编译器将读取签名g :: A -> B -> C
相同。
这意味着当我们写A -> B -> C -> ... -> Z
时,我们实际上是指A -> (B -> (C -> (... -> Z...)))
。
但是,如果我们写g :: (A -> B) -> C
,这不一样!括号很重要! g
现在是一个函数,它接受一个函数并产生一个C
。
现在,让我们检查这个具体示例:
evalPair :: (a -> Eval a) -> (b -> Eval b) -> (a,b) -> Eval (a,b)
我读为:
函数
evalPair
具有以下功能:
a -> Eval a
类型的事物b -> Eval b
类型的事物(a,b)
类型的事物并产生
Eval (a,b)
类型的东西。
所以声明自然看起来像
evalPair first_arg second_arg third_arg = -- ...
写evalPair a sa a sb (a,b) = -- ...
是荒谬的,因为它需要5个参数,因此evalPair
的类型为a -> b -> c -> (d, e) -> f
,与我们的期望完全不同。