Что делает uncurry ($)?
Я делаю несколько упражнений, где я должен добавить тип функции и объяснить, что она делает. Я застрял с этим:
phy = uncurry ($)
Тип, в соответствии с GHCi это phy :: (a -> b, a) -> b
, Мои знания по Haskell являются базовыми, поэтому я действительно понятия не имею, что они делают.
4 ответа
Давайте расшифруем типовую часть систематически. Начнем с типов 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's a <==> ($)'s a -> b
uncurry's b <==> ($)'s a
uncurry's c <==> ($)'s b
Это немного сбивает с толку, потому что a
а также b
переменные типа в одном типе не такие же, как в другом (так же, как x
в 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
Давайте переделаем "линию", используя переписанный тип:
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
Теперь подставим равные равным:
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 отличается от других языков тем, что очень часто это лучшая стратегия, чем пытаться читать код.
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)
,
Два других ответа в порядке. У меня просто немного другое мнение.
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
,
(Убедитесь, что вы понимаете функции высшего порядка и карри, прочитайте главу " Изучите вас на языке Haskell", посвященную функциям высшего порядка, а затем прочитайте разницу между. (Точка) и $ (знак доллара) и композицией функций (.) И применением функций ($) идиомы)
($)
это просто функциональное приложение, 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 всегда смотрите на типы, они дают вам много информации, прежде чем вы даже посмотрите на код. Итак, что это ($)
? Это функция от 2 аргументов. Что такое uncurry
? Это также функция 2 аргументов, первый из которых является функцией 2 аргументов. Так uncurry ($)
следует проверить, потому что 1-й аргумент 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)
, Теперь попытайтесь угадать, что он делает по одному типу.
Теперь перед ответом интерлюдия. Хаскель настолько строго типизирован, что запрещает возвращать значение конкретного типа, если объявление типа имеет переменную типа в качестве типа возвращаемого значения. Так что если у вас есть функция с типом a -> b
, вы не можете вернуться String
, Это имеет смысл, потому что если тип вашей функции был a -> a
и ты всегда возвращался String
Как пользователь сможет передать значение любого другого типа? Вы должны либо иметь тип String -> String
или есть тип a -> a
и вернуть значение, которое зависит исключительно от входной переменной. Но это ограничение также означает, что невозможно написать функцию для определенных типов. Нет функции с типом a -> b
потому что никто не знает, какой конкретный тип должен быть вместо b
, Или же [a] -> a
, вы знаете, что эта функция не может быть полной, потому что пользователь может передать пустой список, и что бы функция вернула в этом случае? Тип 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