Почему разница в выводе типа по сравнению с шаблоном 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