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]