Почему разница в выводе типа по сравнению с шаблоном as в двух похожих определениях функций?
У меня есть следующие два аналогичных определения функции:
left f (Left x) = Left (f x)
left _ (Right x) = Right x
left' f (Left x) = Left (f x)
left' _ [email protected](Right _) = r
Когда я проверяю сигнатуры типов двух функций, меня смущают следующие типы:
ghci> :t left
left :: (t -> a) -> Either t b -> Either a b
ghci> :t left'
left' :: (a -> a) -> Either a b -> Either a b
Я полагаю, что left
и left'
должны иметь одинаковую сигнатуру типа, но вывод типа haskell дает мне сюрприз.
Я не могу понять, почему сигнатуры типа left
и left'
отличаются. Кто-нибудь может дать мне некоторую информацию? Спасибо!
Ответы
Ответ 1
Во второй строке left'
:
left' _ [email protected](Right _) = r
-- ^^^^^^^^^^^ ^
Поскольку вы используете шаблон as, вы требуете, чтобы входной тип был равен возвращаемому типу, поскольку очевидно, что это одно и то же значение. Тип left'
затем ограничивается чем-то вида a → b → b
.
Затем это ограничение распространяется в обратном направлении, чтобы, в свою очередь, ограничивать тип функции - надеюсь, вы увидите, как; это не так уж сложно.
Однако во второй строке left
вы создаете новое значение
left _ (Right x) = Right x
-- ^^^^^^^
Тип этого нового значения не был ограничен, и, следовательно, та же проблема не возникает; это может быть что-то в форме a → b → c
, что вам нужно.
По этой причине тип left'
более ограничен, чем тип left
, из-за чего их типы неравны.
Чтобы проиллюстрировать эту концепцию более конкретно, я опишу вам более точно, как это ограничение распространяется назад.
Мы знаем, что left'
подпись имеет вид (a → b) → Either aq → Either bq
. Однако в строке 2 указано, что Either aq = Either bq
, что означает, что a = b
, поэтому тип теперь становится (a → a) → Either aq → Either aq
.
Ответ 2
Проблема здесь в том, что есть некоторые "скрытые" типы, которые имеют значение. Механизм вывода типа может вывести их в первом случае, но не может во втором.
Когда мы используем
Right :: forall a b. b -> Either a b
нам нужно выбрать типы a
и b
. К счастью, вывод типа делает этот выбор для нас в большинстве случаев. Тип b
легко выбрать: это тип значения внутри аргумента Right
. Введите a
вместо этого может быть что угодно - механизм логического вывода должен опираться на контекст для "силы" какой - то выбор для. a
Например, обратите внимание, что все эти проверки типов:
test0 :: Either Int Bool
test0 = Right True
test1 :: Either String Bool
test1 = Right True
test2 :: Either [(Char, Int)] Bool
test2 = Right True
Теперь в вашей первой функции
left :: (t -> a) -> Either t b -> Either a b
left f (Left x) = Left (f x)
left _ (Right x) = Right x
Первый совпавший Right x
на самом деле является Right x :: Either tb
, где неявные аргументы типа выбираются как t
и b
. Это делает x
иметь тип b
. С этой информацией вывод типа пытается определить тип для второго Right x
. Там видно, что он должен быть в форме Either?? b
Either?? b
поскольку x :: b
, но, как это было в наших test
примерах выше, мы можем использовать что угодно для ??
, Таким образом, механизм вывода типов выбирает другую переменную типа a
(тип, который может быть t
, но может быть и другим).
Вместо этого во второй функции
left' :: (t -> t) -> Either t b -> Either t b
left' f (Left x) = Left (f x)
left' _ [email protected](Right _) = r
мы даем имя (r
) шаблону Right _
. Как и выше, предполагается, что тип Either tb
. Однако теперь мы используем имя r
справа от =
, поэтому вывод типа не должен ничего выводить и может (на самом деле, должен) просто повторно использовать тип, который уже выведен для r
. Это делает тип вывода тем же Either tb
что и для входа, и, в свою очередь, заставляет тип f
иметь тип t → t
.
Если это сбивает с толку, вы можете попытаться полностью избежать вывода типов и предоставить явный выбор для типов, используя синтаксис Right @T @U
для выбора функции U → Either TU
. (Для этого вам потребуется включить ScopedTypeVariables
, расширения TypeApplications
.) Тогда мы можем написать:
left :: forall t a b. (t -> a) -> Either t b -> Either a b
left f (Left x) = Left @a @b (f x)
left _ (Right x) = Right @a @b x
-- ^^ -- we don't have to pick @t here!
Мы также можем иметь
left :: forall t b. (t -> t) -> Either t b -> Either t b
left f (Left x) = Left @t @b (f x)
left _ (Right x) = Right @t @b x
и это будет работать просто отлично. GHC предпочитает первый тип, поскольку он является более общим, что позволяет a
быть что - либо ( в том числе t
, но и в том числе и другие виды).
Во втором определении нет приложения типа, которое можно было бы использовать, так как оно использует то же значение r
справа, что и слева:
left' :: forall t b. (t -> t) -> Either t b -> Either t b
left' f (Left x) = Left @t @b (f x)
left' _ [email protected](Right x) = r
-- ^^ -- can't pick @a here! it the same as on the LHS