为什么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是一个函数,它接受一个值并返回它不变。因为它非常简单,它可以为各种不同的值做到这一点—— Ints 和Bools 和Strings 以及列表和树......

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

而且,事实证明,我为这两种类型给出的两种行为描述也证明是相同的。回想一下他们是:

  1. 将函数作为参数并原样返回的函数。
  2. 一个函数,它接受一个函数作为参数,以及该函数的一个参数,并将一个应用于另一个。

我会让你炖一会儿为什么这些结果是一样的!

无论如何,此时应该更清楚为什么会这样。而且,对于机械版的答案,我们可以采用这两种类型:

id :: a -> a
f :: (a -> b -> c) -> String

并通过a ~ b -> c为两者进行选择使它们组合在一起。(这只是巧合, 的两个实例化为a同一类型——在进行统一时通常不会出现这种情况!)在两个类型签名中实例化ato后b -> c,我们得到:

id :: (b -> c) -> b -> c
f :: ((b -> c) -> b -> c) -> String


以上是为什么a->b->c函数参数可以接受a->a类型?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>