我一直在努力理解这段代码,但我无法清楚地说明这一点:
ghci > :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
ghci > :t ($)
($) :: (a -> b) -> a -> b
ghci > let c = zipWith ($)
ghci > :t c
c :: [b -> c] -> [b] -> [c]
[b -> c]
如何在上述类型签名中产生?
答案 0 :(得分:6)
为了zipWith ($)
进行类型检查,我们必须统一类型为zipWith
的{{1}}第一个参数的类型。我会把它们一起写出来并用独特的名字来表达它们。
($)
因此,zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
($) :: ((x -> y) -> x -> y)
类型检查当且仅当我们可以假设zipWith
,a ~ (x -> y)
和b ~ x
时。没有什么可以阻止这种统一成功,所以我们可以将这些名称替换回c ~ y
的类型。
zipWith
然后继续申请,因为现在一切都很好了
zipWith :: ((x -> y) -> x -> y) -> [x -> y] -> [x] -> [y]
($) :: ((x -> y) -> x -> y)
这相当于具有您所见类型的类型变量名称的特定选择。
答案 1 :(得分:2)
这只是上下文替换,没有魔法。看:
ghci > :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
ghci > :t ($)
($) :: (a' -> b') -> a' -> b'
现在考虑zipWith ($)
。它的类型为(a -> b -> c) -> [a] -> [b] -> [c]
,其中第一个参数是固定的,因此我们应该将(a -> b -> c)
(第一个arg的类型)与(a' -> b') -> a' -> b'
(类型$
)进行模式匹配。因此,我们有a = (a' -> b')
,b = a'
,c = b'
。替换回到zipWith
:[a' -> b'] -> [a'] -> [b']
(第一个参数是固定的,所以他从类型中消失了),这正是你用不同命名的类型变量得到的。
此外,人们可能会考虑zipWith
语义:拉链(第一个参数),然后将两个列表压缩在一起。如果您的拉链是函数应用程序($
是函数应用程序,是的!)那么当压缩两个列表时,您只需使用第二个列表的相应元素调用第一个列表的元素。功能类型反映了这一点。
答案 2 :(得分:0)
类型签名中指定的实际字母是任意的,可以是任何字母。您可以轻松地将($)的类型写为
(x -> y) -> x -> y
它需要两个参数,一个函数接受一个参数,一个值传递给函数。
zipWith
的第一个参数是一个带有两个参数(a -> b -> c)
的函数。根据{{1}}的定义,您选择($)
为a
,(x -> y)
为b
,然后x
为c
,所以你得到y
的类型为
zipWith ($)
答案 3 :(得分:0)
让我们重写一下我们的签名:
($) :: (a -> b) -> a -> b
($) :: a' -> b' -> c'
where -- pseudo-Haskell
a' = a -> b
b' = a
c' = b
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
找到,那
zipWith ($) :: [a'] -> [b'] -> [c']
zipWith ($) :: [a -> b] -> [a] -> [b]