为什么a->b->c函数参数可以接受a->a类型?
这编译成功:
test = f id
f :: (a -> b -> c) -> String
f g = "test"
id
有 type a -> a
,但f
有一个 type 的第一个参数(a -> b -> c)
。
我认为它不应该编译。我不明白。
回答
因为你可以绑定未实例化的变量。f :: (a -> b -> c) -> String
表示对于任意 3 种类型 a、b 和 c,f 接受从 a 到 b 到 c 的函数并返回字符串。
重要的是要记住这f :: (a -> b -> c) -> String
相当于f :: (a -> (b -> c)) -> String
因为柯里化。
id
接受任何类型,并返回该类型。
所以如果我们交换它,id
正在返回b -> c
,并接受a
,所以如果a
可以b -> c
,它可以作为 a 可以是任何类型,那么这很好。
- @user754458 Why not? That is the whole point. In instancing f by passing it id, you can add any restrictions to the types you like, so you can restrict a to be b -> c
- @user754458 to match the types, the compiler solves the equation `a -> b -> c = x -> x` and finds that `x = a = (b -> c)`.
- Minor terminology note: there are no free (type) variables here. The binders are implicit, but the language specification makes it unambiguous how to insert binders when there are none. The explicit versions, `id :: forall a. a -> a` and `f :: forall a b c. (a -> b -> c) -> String`, for which the signatures in the question are shorthand, make it more obvious that none of the variables are free.
回答
简短版本:它可以工作,因为id
可以接受和返回函数——因此可以表现得好像它是一个双参数函数。现在是长版。
id
是一个函数,它接受一个值并返回它不变。因为它非常简单,它可以为各种不同的值做到这一点—— Int
s 和Bool
s 和String
s 以及列表和树......
id :: Int -> Int
id :: Bool -> Bool
id :: String -> String
id :: [(b, c)] -> [(b, c)]
id :: Tree b -> Tree b
id :: (b -> c) -> (b -> c)
...和功能,为什么不呢!
f
是一个接受两个参数的函数并忽略它们的函数。因为它对你提供的函数的处理非常简单,它可以为各种不同的双参数函数做到这一点——加法、连接、配对、忽略、打印......
f :: (Int -> Int -> Int) -> String
f :: ([b] -> [b] -> [b]) -> String
f :: (b -> c -> (b, c)) -> String
f :: (b -> c -> b) -> String
f :: (Handle -> String -> IO ()) -> String
f :: ((b -> c) -> b -> c) -> String
...以及接受一个函数和一个参数并将其中一个应用于另一个的操作,为什么不呢!
啊,但是两个参数函数实际上只是一个参数函数,它们本身返回一个函数。所以这两种类型实际上只是同一类型的不同拼写:
(b -> c) -> (b -> c)
(b -> c) -> b -> c
而且,事实证明,我为这两种类型给出的两种行为描述也证明是相同的。回想一下他们是:
- 将函数作为参数并原样返回的函数。
- 一个函数,它接受一个函数作为参数,以及该函数的一个参数,并将一个应用于另一个。
我会让你炖一会儿为什么这些结果是一样的!
无论如何,此时应该更清楚为什么会这样。而且,对于机械版的答案,我们可以采用这两种类型:
id :: a -> a
f :: (a -> b -> c) -> String
并通过a ~ b -> c
为两者进行选择使它们组合在一起。(这只是巧合, 的两个实例化为a
同一类型——在进行统一时通常不会出现这种情况!)在两个类型签名中实例化a
to后b -> c
,我们得到:
id :: (b -> c) -> b -> c
f :: ((b -> c) -> b -> c) -> String