Понимание forall в функции Monad '>> ='?

Следуя этим ответам, я реализовал общую функцию лифта в своей программе:

liftTupe :: (x -> c x) -> (a, b) -> (c a, c b) --This will error
liftTuple :: (forall x. x -> c x) -> (a, b) -> (c a, c b)

Я понимаю, что в этом контексте forall позволяет x быть любого типа ([], Maybe и т.д.).

Теперь я изучаю определение >>= в Monads:

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

Я не могу понять роль этого forall в определении функции? Поскольку, в отличие от liftTuple, он не привязан к определенной функции (x -> c x)?

Ответ 1

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

Итак, в первом вы получаете функцию, которая принимает x и дает c x, тогда у вас есть кортеж с a и b, и вы ожидаете кортеж с c a и c b. Поскольку вы уже сказали, что первая функция принимает x, вы можете сделать x таким же, как a, но это не будет b, потому что x определяется один раз для всего объявления. Поэтому вы не можете заставить функцию принимать как a, так и b.

Однако во втором случае область x ограничена функцией, принимающей x. Мы в основном говорим, что есть функция, которая берет что-то и делает c с чем-то, и это может быть любой тип. Это позволяет нам сначала передать a, а затем b, и это сработает. x не должно быть чем-то особенным теперь снаружи.

То, что вы видите в определении Monad, - это расширение языка ExplicitForAll. Описание Haskell Prime для этого расширения

ExplicitForAll позволяет использовать ключевое слово forall для явного указания того, что тип является полиморфным в своих переменных типа. Он не позволяет записывать какие-либо типы, которые уже не могут быть записаны; он просто позволяет программисту явно указать (в настоящее время неявное) количественное определение.

Это языковое расширение является чисто визуальным, позволяет выписывать явно указанные переменные, которые вы ранее не могли. Вы можете просто опустить forall a b. из объявления Monad, и программа будет функционировать точно так же.

Скажем, с помощью этого расширения вы можете переписать liftTupe как forall a b x. (x -> c x) -> (a, b) -> (c a, c b). Определение одно и то же, и оно работает одинаково, но теперь читатели ясно видят, что все переменные типа определены на самом верхнем уровне.

Ответ 2

Каждая функция, которую вы пишете, неявно определяется количественно по ее переменным типа:

id :: a -> a           -- this is actually universally quantified over a
id :: forall a. a -> a
id x = x

Вы можете включить это поведение с помощью ExplicitForall языковой прагмы.

Это свойство очень полезно, поскольку оно ограничивает вас написанием кода, который работает только с некоторыми типами. Подумайте, что может сделать функция id: он может либо вернуть свой аргумент, либо цикл навсегда. Это единственные две вещи, которые он может сделать, и вы можете понять это на основе его сигнатуры типа.

Принуждение всех экземпляров полиморфной функции вести себя одинаково, независимо от аргумента типа, называется параметризацией и объясняется в this blog post Бартошем Милевским. TL; DR: Используя параметричность, мы можем гарантировать, что некоторые переупорядочения в структуре программы не влияют на ее поведение. Для математически более строгого рассмотрения этого, см. Теоремы бесплатно! от Филиппа Вадлера.

Ответ 3

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

Например, тип liftTuple с явным forall является

liftTuple :: forall c a b. (forall x. x -> c x) -> (a, b) -> (c a, c b)

И для >>= случай тот же.

Ответ 4

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

Итак, давайте рассмотрим разницу между двумя функциями forall и тем, как haskell может их увидеть:

Неявное:

foo ::  (x -> f x) -> a -> b -> (f a, f b)
-- same as
foo :: forall f x a b . (x -> f x) -> a -> b -> (f a, f b)
-- our function is applied to a, so x is equal to a
foo :: forall f x a b . (x ~ a) => (x -> f x) -> a -> b -> (f a, f b)
-- our function is also applied to b, so x is equal to b
foo :: forall f x a b . (x ~ a, x ~ b) => (x -> f x) -> a -> b -> (f a, f b)

Uh oh, (x ~ a, x ~ b) потребует (a ~ b). Это было бы выведено без аннотации, но поскольку мы явно использовали разные переменные типа, все взрывается. Для этого нам нужно, чтобы f оставалась полиморфной внутри нашей функции.

Стандартный haskell не может выразить это, поэтому нам понадобятся rank2types или rankntypes. С этим мы можем написать:

foo ::  (forall x . x -> f x) -> a -> b -> (f a, f b)

Обратите внимание, что forall является частью типа функции. Таким образом, он остается полиморфным в нашей функции, и мы можем применять его к различным типам без всякого взрыва!

Обратите внимание, что мы также можем просто сделать:

foo :: Monad m => a -> b -> (m a, m b)
foo a b = (return a, return b)