Каковы эти явные "forall"?

Какова цель forall в этом коде?

class  Monad m  where
    (>>=)       :: forall a b. m a -> (a -> m b) -> m b
    (>>)        :: forall a b. m a -> m b -> m b
        -- Explicit for-alls so that we know what order to
        -- give type arguments when desugaring

(некоторый код опущен). Это из кода для Monads.


Мой фон: я действительно не понимаю forall или когда Haskell имеет их неявно.

Кроме того, это может быть незначительным, но GHCi позволяет мне пропускать forall при предоставлении >> типа:

Prelude> :t (>>) :: Monad m => m a -> m b -> m b
(>>) :: Monad m => m a -> m b -> m b
  :: (Monad m) => m a -> m b -> m b

(без ошибок).

Ответы

Ответ 1

Мой фон: я действительно не понимаю forall или когда Haskell имеет их неявно.

Хорошо, рассмотрим тип id, a -> a. Что означает a, и откуда оно взялось? Когда вы определяете значение, вы не можете просто использовать произвольные переменные, которые не определены нигде. Вам нужно определение верхнего уровня, или аргумент функции, или предложение where, & c. В общем случае, если вы используете переменную, она должна быть привязана где-то.

То же самое относится к переменным типа, а forall - один из способов привязки переменной типа. В любом месте вы видите переменную типа, которая явно не привязана (например, class Foo a where ... связывает a внутри определения класса), она неявно связана с помощью forall.

Итак, тип id неявно forall a. a -> a. Что это значит? В значительной степени то, что он говорит. Мы можем получить тип a -> a для всех возможных типов a или с другой точки зрения, если вы выберете какой-либо конкретный тип, вы можете получить тип, представляющий "функции из выбранного вами типа для себя". Последнее выражение должно звучать немного как определение функции, и как таковое вы можете думать о forall как о подобном абстракции лямбда для типов.

В процессе компиляции GHC использует различные промежуточные представления, и одно из применяемых им преобразований делает более похожим на функции более прямые: неявные forall становятся явными, и в любом случае полиморфное значение используется для определенного типа, оно сначала применяется к аргументу типа.

Мы можем даже записать как forall, так и lambdas как одно выражение. Я буду называть нотацию на мгновение и заменить forall a. на /\a => для визуальной согласованности. В этом стиле мы можем определить id = /\a => \(x::a) -> (x::a) или что-то подобное. Таким образом, выражение, подобное id True в вашем коде, будет переведено на что-то вроде id Bool True; просто id True уже не имеет смысла.

Так же, как вы можете изменить порядок аргументов функции, вы также можете изменить порядок аргументов типа, подчиняясь только (довольно очевидному) ограничению, которое должны указывать аргументы типа перед любыми аргументами значения этого типа. Поскольку неявный forall всегда является самым внешним слоем, GHC потенциально может выбрать любой желаемый порядок, делая его явным. При нормальных обстоятельствах это, очевидно, не имеет значения.

Я не уверен, что происходит в этом случае, но на основе комментариев я бы предположил, что преобразование в использование явных аргументов типа и десурацию нотации do в некотором смысле не знают о каждом другие, и поэтому порядок аргументов типа указан явно для обеспечения согласованности. В конце концов, если что-то вслепую применяет два выражения типа к выражению, важно, является ли этот тип выражения forall a b. m a -> m b -> m b или forall b a. m a -> m b -> m b!