我玩了一下zipWith
,遇到以下情况:
Prelude Control.Applicative> :t zipWith id
zipWith id :: [b -> c] -> [b] -> [c]
为什么编译器期望下一个参数是函数列表?
我试图分析,但无法得出结论,为什么下一个参数必须是函数列表。
id
当我传递给时,签名是如何应用的zipWith
?
的类型zipWith
是:
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
的类型id
是:
id :: d -> d
因此,如果我们现在想要导出 的类型zipWith id
,我们将 的类型推id :: d -> d
入 的第一个参数的类型zipWith
:
d -> d
~ a -> (b -> c)
所以这意味着:a ~ d
和a ~ b -> c
。所以这意味着现在的类型zipWith id
是:
zipWith id :: [a] -> [b] -> [c]
-> zipWith id :: [b -> c] -> [b] -> [c]
这是如何工作的:第一个列表必须包含函数f :: b -> c
列表,第二个列表必须包含元素列表,x :: b
因此它会计算元素列表f x :: c
。
例如:
Prelude> zipWith id [(+1),(5/),(3*),(3-)] [1,4,2,5]
[2.0,1.25,6.0,-2.0]
因为1+1
是2.0
,5/4
是1.25
,3*2
是6.0
,3-5
是-2.0
。
所以zipWith id
将采用两个元素f
and x
,并应用于id f x
这些,或者更详细(id f) x
的 。既然id f
是f
,就会这样计算f x
。
因此,我们可以得出结论,这zipWith
是一个元素映射。
谢谢您,Willem Van Onsem 的出色回答。
让我们
zipWith id
从ghc的类型推断系统的角度来理解。
首先,考虑类型zipWith
Prelude> :info zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
-- Defined in ‘GHC.List’
的第一个参数zipWith
是一个函数,它接受一个带有两个参数的函数。
(a -> b -> c)
也可以重写为a -> (b -> c)
现在考虑zipWith id
。类型id
来自a -> a
我们已经放置id
了一个两个参数函数必须去的地方。
因此,类型推断(a -> b -> c)
看起来像a -> (b -> c)
(注意a -> (b -> c)
接受一个参数 a 并给出b -> c
一个参数函数。)
但是,只有当is (b -> c)a -> (b -> c)
时才有可能制作恒等函数。a
当a
是 (b -> c) 时,函数a -> b -> c
变为 ((b -> c) -> (b -> c))
因此,类型推断系统会推断a
为 as(b -> c)
并且结果输出将[a] -> [b] -> [c]
替换a
为b -> c
.
替换
a
为 (b -> c)。使 (a -> b -> c) 看起来像
id
. (a -> b -> c) 可以id
通过上述替换看起来像。((b -> c) -> b -> c) 也可以写成 ((b -> c) -> (b -> c)) 这
id :: x -> x
就是x
(b -> c)
zipWith :: ((b -> c) -> b -> c) -> [b -> c] -> [b] -> [c]
所以最后我们得到输出[b -> c] -> [b] -> [c]