Как haskell определяет порядок переменных типа в неявных динамиках?

Поэтому я недавно узнал и начал использовать TypeApplications, и TypeApplications было интересно, как мы можем вообще знать, какие переменные типа мы назначаем. В документации по TypeApplications я нашел, упоминается:

Какой порядок используется для создания экземпляров переменных типа?

Влево-вправо порядок переменных типа, появляющихся в foralls. Это самый логичный порядок, который возникает, когда инстанцирование выполняется на уровне переменной типа. Вложенные фолды работают немного по-другому, но в одном месте forall с несколькими переменными имеет место порядок слева направо. (См. Ниже вложенные фолды).

Однако я не нашел упоминания о том, как определяется порядок переменных типа в неявных динамиках. Я попытался рассмотреть различные примеры с -fprint-explicit-foralls чтобы увидеть, существует ли простой шаблон, но я получаю разные результаты в разных версиях ghci. :/

В версии ghci 8.0.2 я получаю:

> :t (,,)
(,,) :: forall {c} {b} {a}. a -> b -> c -> (a, b, c)

в то время как в версии ghci 8.4.3 я получаю:

> :t (,,)
(,,) :: forall {a} {b} {c}. a -> b -> c -> (a, b, c)

Опять же, может быть, это просто ошибка в том, как форматирование печаталось в 8.0.2, потому что в противном случае приложения типа, похоже, выполняются справа налево с переменными forall, вопреки тому, что говорит документация:

> :t (,,) @Bool
(,,) @Bool :: forall {c} {b}. Bool -> b -> c -> (Bool, b, c)

Итак, переменные типа помещаются в неявные foralls всегда в том порядке, в котором они появляются сначала слева направо в типе body (включая ограничения)?

Ответ 1

TL; DR: Тип переменной порядок определяется первой встречей слева направо. В случае сомнений используйте :type +v.

Не использовать :type

Использование :type вводит в заблуждение. :type выводит тип целого выражения. Поэтому, когда вы пишете :t (,), проверяющий тип смотрит на

(,) :: forall a b. a -> b -> (a, b)

и создает экземпляры всех переменных с новыми переменными типа

(,) :: a1 -> b1 -> (a1, b1)

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

(,) :: forall {b} {a}. a -> b -> (a, b)

Этот шаг не дает никаких гарантий над порядком свободной переменной, и компилятор может свободно меняться.

Также обратите внимание, что он пишет forall {a} вместо forall a, что означает, что вы не можете использовать приложение видимого типа здесь.

Использование :type +v

Но, конечно, вы можете использовать (,) @Bool но здесь тип-checker рассматривает первое выражение по-разному и не выполняет этот этап создания/обобщения.

И вы можете получить это поведение в GHCi, а также - передать +v на :type:

:type +v (,)
(,) :: forall a b. a -> b -> (a, b)
:type +v (,) @Bool
(,) @Bool :: forall b. Bool -> b -> (Bool, b)

Посмотрите, нет {…} вокруг переменных типа!

Откуда этот порядок?

Раздел руководства пользователя GHC о приложении видимого типа:

Если сигнатура типа идентификатора не содержит явного forall, аргументы переменной типа отображаются в порядке слева направо, в котором переменные появляются в типе. Таким образом, foo :: Monad m => ab → m (ac) будет иметь свои типовые переменные, упорядоченные как m, a, b, c.

Это относится только к вещам, которые имеют явную подпись типа. Нет гарантированного порядка для переменных в предполагаемых типах, но вы также не можете использовать выражения с выведенными типами с помощью VisibleTypeApplication.