Haskell: Перевертывание оператора доллара в долларах США
Скажем, я определяю эту функцию:
f = ($ 5)
Тогда я могу применить его:
> f (\x -> x ^ 2)
25
Его тип:
:t f
f :: (Integer -> b) -> b
Что имеет смысл, он получает функцию в качестве аргумента и возвращает эту функцию, примененную к Integer
5
.
Теперь я определяю эту функцию:
g = flip f
Я бы ожидал, что это не имеет смысла, потому что f
является функцией одного аргумента.
Но, проверяя его тип:
:t g
g :: b -> (Integer -> b -> c) -> c
Итак, теперь g
является функцией из двух аргументов!
Применяя его к некоторым значениям:
> g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
Что здесь происходит? Что означает flip ($ 5)
?
Ответы
Ответ 1
Выполните следующие типы:
($ 5) :: (Int -> a) -> a
flip :: (x -> y -> z) -> y -> x -> z
Но так как ->
является правильным ассоциативным, тип x -> y -> z
эквивалентен x -> (y -> z)
, поэтому
flip :: (x -> (y -> z)) -> y -> x -> z
($ 5) :: (Int -> a) -> a
So x ~ (Int -> a)
и (y -> z) ~ a
, поэтому подставляя обратно:
($ 5) :: (Int -> (y -> z)) -> (y -> z)
И упрощенная
($ 5) :: (Int -> y -> z) -> y -> z
Итак,
flip ($ 5) :: y -> (Int -> y -> z) -> z
Что эквивалентно типу, который вы видите (хотя я использовал Int
вместо Integer
для сохранения ввода).
Это говорит о том, что тип ($ 5)
становится специализированным при передаче в flip
, так что он принимает функцию из 2 аргументов. Совершенно верно иметь что-то вроде ($ 5) const
, где const :: a -> b -> a
и ($ 5) const :: b -> Int
. Все ($ 5)
делает применение 5
как аргумент функции, не обязательно аргумент для функции. Это пример частичного приложения, где не все аргументы передаются функции. Вот почему вы можете делать такие вещи, как map (subtract 1) [1, 2, 3]
.
Пример использования flip ($ 5)
:
> flip ($ 5) 1 (**)
25.0
> flip ($ 5) 1 (-)
4.0
> let f x y = (x, y)
> flip ($ 5) 1 f
(5, 1)
Ответ 2
Путаница возникает из-за расплывчатой концепции "числа аргументов" для полиморфных функций. Например, соблазнительно сказать, что
f :: (Integer -> b) -> b
имеет один аргумент (функция). Тем не менее, более точное утверждение заключалось бы в том, что f
является функцией с хотя бы одним аргументом. Это связано с тем, что переменная типа b
может быть заменена любым типом, благодаря полиморфизму, что дает, например,
f :: (Integer -> String) -> String
f :: (Integer -> Double) -> Double
...
которые действительно являются функциями с одним аргументом, но также, например,
f :: (Integer -> (String -> Double)) -> (String -> Double)
где b
был заменен функциональным типом String -> Double
. Эта подстановка делает второй аргумент "видимым" по-видимому магическим способом: f
может принимать первый аргумент (двоичная функция Integer -> String -> Double
), а затем второй (a String
), прежде чем возвращать Double
.
Обратите внимание, что это явление всегда появляется, когда полиморфный тип заканчивается ... -> b
для некоторой переменной типа b
.
Позвольте мне закончить с пустяками: как "многие" аргументы имеют функцию тождества id
? Ну, интуитивно я бы сказал один, но позвольте мне проверить...
> id (+) 3 4
7
> id id id id id (+) 3 4
7
... и, возможно, многие - лучший ответ.
Ответ 3
функция flip
переворачивает порядок аргументов, поэтому все они равны:
f (\x y -> x:y) [2, 4, 6]
[5,2,4,6]
flip f [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]