Почему этот код Haskell компилируется?
Учитывая:
uncurry :: (a-> b -> c) -> (a,b) -> c
id :: a -> a
Вызов uncurry id
приводит к функции типа: (b -> c, b) -> c
Как мы получаем этот результат?
Как вы можете использовать id (a → a) в качестве первого параметра для нечеткой, для чего требуется функция (a → b → c)?
Ответы
Ответ 1
Это проще понять, если мы попытаемся посмотреть на него с точки зрения создания типов: выясним, что нам нужно сделать для типа id
, чтобы он соответствовал форме, требуемой uncurry
. Поскольку мы имеем:
id :: a -> a
мы также имеем:
id :: (b -> c) -> (b -> c)
Это можно увидеть, заменив b -> c
на a
в исходном типе id
, так же, как вы можете заменить Int
при определении типа id 42
. Затем мы можем оставить круглые скобки в правой части, так как (->)
является право-ассоциативным:
id :: (b -> c) -> b -> c
показывающий, что тип id
соответствует форме a -> b -> c
, где a
- b -> c
. Другими словами, мы можем изменить тип id
, чтобы он соответствовал требуемой форме, просто специализировав общий тип, который он уже имеет.
Еще один способ понять это - увидеть, что uncurry ($)
также имеет тип (b -> c, b) -> c
. Сравнивая определения id
и ($)
:
id :: a -> a
id a = a
($) :: (a -> b) -> a -> b
($) f x = f x
мы можем сделать последнее определение более бессмысленным:
($) f = f
в этот момент становится понятным тот факт, что ($)
является просто специализацией id
более конкретному типу.
Ответ 2
Как вы можете использовать id (a → a) в качестве первого параметра для нечеткой, для чего требуется функция (a → b → c)?
На самом деле uncurry
требуется функция (a -> (b -> c))
. Вы можете заметить разницу?:)
Опуская круглые скобки - это зло (ну, иногда). Это делает невозможным для начинающего расшифровать Haskell. Конечно, после того, как вы собрали некоторый опыт работы с языком, вы чувствуете, что больше не нуждаетесь в них.
Здесь все становится ясно, как только мы выпишем все опущенные круглые скобки явно:
uncurry :: (a -> (b -> c)) -> ((a,b) -> c)
id :: a -> a
Теперь запись uncurry id
вызывает объединение типов a1 -> a1
с a2 -> (b -> c)
. Это просто, a1 ~ a2
и a1 ~ (b -> c)
. Просто механический материал, нет творческого мышления. Таким образом, id
имеет тип a -> a where a ~ (b -> c)
, поэтому uncurry id
имеет тип (b -> c,b) -> c
, путем простой подстановки a ~ (b -> c)
в (a,b) -> c
. То есть, он ожидает пару функции b -> c
и значение b
и должен иметь значение c
.
Поскольку типы являются наиболее общими (т.е. о них ничего не известно, и поэтому нет особых функций для вызова, которые могли бы сделать трюк каким-то особым образом), единственный способ получить значение c
здесь - вызвать b -> c
с значением b
в качестве аргумента. Естественно, что делает ($)
. Итак, uncurry id == uncurry ($)
, хотя id
, безусловно, не ($)
.