Давайте систематически изложим типовую деталь. Мы начнем с типами 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
«s для своих типов контрагентов в ($)
, мы получаем:
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 ($)' на самом деле то же самое, что 'uncurry id'! Это происходит потому, что '($)' на самом деле просто 'id' специализируется на функциях. Попробуйте ': t uncurry id', чтобы понять, что я имею в виду. –
Этот код иллюстрирует силу мышления в типах. Выяснение того, что 'uncurry ($)' действительно задумывается, но должно быть очевидно, что делает функция типа '(a -> b, a) -> b'. На самом деле, есть только одна вещь, функция с этим типом * может * делать. Вы должны развивать эту интуицию естественным образом, если обратить внимание на типы, которые вы программируете в Haskell. Если вы позже захотите формализовать свою интуицию, попробуйте статью «[Теоремы бесплатно] (http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf)» [PDF] by Philip Wadler – pash