Что делает uncurry ($)?

Я делаю несколько упражнений, где мне нужно добавить тип функции и объяснить, что она делает. Я застрял в этом:

phy = uncurry ($)

Тип, согласно GHCi, равен phy :: (a -> b, a) -> b. Мои знания haskell являются основными, поэтому я действительно не знаю, что он делает.

Ответ 1

Позвольте описать часть типа систематически. Мы начнем с типов uncurry и ($):

uncurry :: (a -> b -> c) -> (a, b) -> c
($)     :: (a -> b) -> a -> b

Так как целевое выражение имеет ($) в качестве аргумента uncurry, выстройте их типы, чтобы отразить это:

uncurry :: (a       -> b -> c) -> (a, b) -> c
($)     :: (a -> b) -> a -> b

Весь тип ($) выстраивается в линию с первым типом аргумента uncurry, а аргументы и типы результатов ($) совпадают с параметрами первого аргумента uncurry, как показано. Это соответствие:

uncurry a  <==> ($) a -> b
uncurry b  <==> ($) a
uncurry c  <==> ($) b

Это сбивает с толку, потому что переменные типа a и b в одном типе не такие же, как в другом (точно так же, как x in plusTwo x = x + 2 не совпадает с x в timesTwo x = x * 2). Но мы можем переписать типы, чтобы помочь разобраться в этом. В простых сигнатурах типа Haskell, подобных этому, в любое время, когда вы видите переменную типа, вы можете заменить все ее вхождения каким-либо другим типом, также получить допустимый тип. Если вы выберете новые переменные типа (введите переменные, которые не отображаются нигде в оригинале), вы получите эквивалентный тип (тот, который можно преобразовать обратно в оригинал); если вы выберете не свежий тип, вы получите специализированную версию оригинала, которая работает с более узким диапазоном типов.

Но в любом случае, примените это к типу uncurry::

-- Substitute a ==> x, b ==> y, c ==> z:
uncurry :: (x -> y -> z) -> (x, y) -> z

Повторите команду "line up", используя перезаписанный тип:

uncurry :: (x       -> y -> z) -> (x, y) -> z
($)     :: (a -> b) -> a -> b

Теперь очевидно: x <==> a -> b, y <==> a и z <==> b. Теперь, заменив переменные типа uncurry для их типов-партнеров в ($), получим:

uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b
($)     ::  (a -> b) -> a -> b

И наконец:

uncurry ($) :: (a -> b, a) -> b

Итак, как вы выясните тип. Как насчет того, что он делает? Ну, лучший способ сделать это в этом случае - посмотреть на тип и подумать об этом внимательно, выяснить, что нам нужно написать, чтобы получить функцию этого типа. Переписывайте его таким образом, чтобы сделать его более загадочным:

mystery :: (a -> b, a) -> b
mystery = ...

Так как мы знаем, что mystery является функцией одного аргумента, мы можем расширить это определение, чтобы отразить это:

mystery x = ...

Мы также знаем, что его аргумент представляет собой пару, поэтому мы можем развернуть немного больше:

mystery (x, y) = ...

Так как мы знаем, что x является функцией и y :: a, мне нравится использовать f для обозначения "функции" и для обозначения переменных так же, как и их тип, - это помогает мне рассуждать о функциях, поэтому пусть сделайте это:

mystery (f, a) = ...

Теперь, что мы вкладываем в правую сторону? Мы знаем, что это должно быть типа b, но мы не знаем, какой тип b (это фактически то, что выбирает вызывающий, поэтому мы не можем знать). Поэтому мы должны как-то сделать b используя нашу функцию f :: a -> b и значение a :: a. Ага! Мы можем просто вызвать функцию со значением:

mystery (f, a) = f a

Мы написали эту функцию, не глядя на uncurry ($), но получается, что она делает то же самое, что и uncurry ($), и мы можем это доказать. Начнем с определений uncurry и ($):

uncurry f (a, b) = f a b
f $ a = f a

Теперь, заменив equals на equals:

uncurry ($) (f, a) = ($) f a         -- definition of uncurry, left to right
                   = f $ a           -- Haskell syntax rule
                   = f a             -- definition of ($), left to right
                   = mystery (f, a)  -- definition of mystery, right to left

Итак, один из способов атаковать тип, который вы не понимаете в Haskell, - это просто попытаться написать код, который имеет этот тип. Haskell отличается от других языков тем, что очень часто это лучшая стратегия, чем попытка прочитать код.

Ответ 2

uncurry :: (a -> b -> c) -> (a, b) -> c

($) :: (a -> b) -> a -> b

uncurry ($) :: (a -> b, a) -> b

Если вы проверяете типы uncurry и $ и его описание:

uncurry преобразует курсированную функцию в функцию на парах.

Все, что он делает, это функция (a -> b -> c) и возвращает функцию, которая принимает параметры как кортеж.

Итак, phy делает то же самое, что и $, но вместо f $ x или ($) f x вы называете это как phy (f, x).

Ответ 3

Остальные два ответа хороши. Я просто немного изменил его.

uncurry :: (a -> b -> c) -> (a, b) -> c
($)     :: (a -> b) -> a -> b

Так как символа "- > " в типе ассоциируется справа, я могу эквивалентно записать эти две сигнатуры типа следующим образом:

uncurry :: (a -> b -> c) -> ((a, b) -> c)
($)     :: (a -> b) -> (a -> b)

uncurry принимает произвольную функцию из двух входов и превращает ее в функционал одного аргумента, где этот аргумент является кортежем исходных двух аргументов.

($) принимает простую функцию с одним аргументом и превращает ее в... сам. Его единственный эффект является синтаксическим. f $ эквивалентно f.

Ответ 4

(Удостоверьтесь, что вы понимаете функции более высокого порядка и currying, читайте "Learn You the Haskell" в главе функции более высокого порядка, затем прочтите разницу между. (точками) и знаками $(знак доллара) и (.) и функцией application ($) идиомы)

($) - это просто приложение-функция, f $ x эквивалентно f x. Но это хорошо, потому что мы можем использовать явное приложение функции, например:

map ($2) $ map ($3) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]

что эквивалентно:

map (($2) . ($3)) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]

Проверьте тип ($): ($) :: (a -> b) -> a -> b. Вы знаете, что объявления типа являются право-ассоциативными, поэтому тип ($) также может быть записан как (a -> b) -> (a -> b). Подождите секунду, что это? Функция, которая получает унарную функцию и возвращает унарную функцию того же типа? Это похоже на конкретную версию функции идентификации id :: a -> a. Хорошо, сначала некоторые типы:

($) :: (a -> b) -> a -> b
id :: a -> a
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry ($) :: (b -> c, b) -> c
uncurry id :: (b -> c, b) -> c

При кодировании Haskell, всегда смотрите на типы, они дают вам много информации, прежде чем вы даже посмотрите на код. Итак, что a ($)? Это функция из двух аргументов. Что такое uncurry? Это функция из 2 аргументов, первая из которых является функцией из двух аргументов. Таким образом, uncurry ($) должен иметь вид typecheck, потому что 1 st аргумент uncurry должен быть функцией из 2 аргументов, которые ($) есть. Теперь попробуйте угадать тип uncurry ($). Если тип ($) равен (a -> b) -> a -> b, замените его на (a -> b -> c): a становится (a -> b), b становится a, c становится b, поэтому uncurry ($) возвращает функцию тип ((a -> b), a) -> b. Или (b -> c, b) -> c, как указано выше, что то же самое. Так что говорит нам этот тип? uncurry ($) принимает кортеж (function, value). Теперь попробуйте угадать, что он делает только с типом.

Теперь, перед ответом, интерлюдия. Haskell настолько строго типизирован, что он запрещает возвращать значение конкретного типа, если объявление типа имеет переменную типа в качестве возвращаемого значения тип. Поэтому, если у вас есть функция с типом a -> b, вы не можете вернуть String. Это имеет смысл, потому что если ваш тип функции был a -> a и вы всегда возвращали String, как пользователь мог бы передать значение любого другого типа? Вы должны либо иметь тип String -> String, либо иметь тип a -> a и возвращать значение, зависящее исключительно от входной переменной. Но это ограничение также означает, что невозможно написать функцию для определенных типов. Нет функции с типом a -> b, потому что никто не знает, какой конкретный тип должен быть вместо b. Или [a] -> a, вы знаете, что эта функция не может быть total, поскольку пользователь может передать пустой список и что функция вернет в таком случае? Тип a должен зависеть от типа внутри списка, но в списке нет "внутри", его пуст, поэтому вы не знаете, что такое тип элементов внутри пустого списка. Это ограничение допускает только для очень узкой локтевой комнаты для возможных функций под определенным типом, и именно поэтому вы получаете так много информации о возможном поведении функции, просто читая тип.

uncurry ($) возвращает что-то типа c, но это переменная типа, а не конкретный тип, поэтому ее значение зависит от того, что также относится к типу c. И мы видим из объявления типа, что функция в кортеже возвращает значения типа c. И та же функция запрашивает значение типа b, которое может быть найдено только в одном кортеже. Нет конкретных типов и типов, поэтому единственное, что может сделать uncurry ($), это взять snd кортежа, поместить его как аргумент в функцию в fst кортежа, вернуть все, что он возвращает:

uncurry ($) ((+2), 2) -- 4
uncurry ($) (head, [1,2,3]) -- 1
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4]

Существует милая программа djinn, которая генерирует программы Haskell на основе типов. Поиграйте с ним, чтобы увидеть, что наши предположения типа uncurry ($) верны:

Djinn> f ? a -> a
f :: a -> a
f a = a
Djinn> f ? a -> b
-- f cannot be realized.
Djinn> f ? (b -> c, b) -> c
f :: (b -> c, b) -> c
f (a, b) = a b

Это также показывает, что fst и snd являются единственными функциями, которые могут иметь соответствующие типы:

Djinn> f ? (a, b) -> a
f :: (a, b) -> a
f (a, _) = a
Djinn> f ? (a, b) -> b
f :: (a, b) -> b
f (_, a) = a