Подтвердить что ты не робот

Ошибка компиляции Haskell с двумя ошибками полиморфизма

Учитывая следующие определения:

import Control.Monad.ST
import Data.STRef

fourty_two = do
  x <- newSTRef (42::Int)
  readSTRef x

Следующие компиляции под GHC:

main = (print . runST) fourty_two -- (1)

Но это не так:

main = (print . runST) $ fourty_two -- (2)

Но тогда, как указывает bdonlan в комментарии, это компилируется:

main = ((print . runST) $) fourty_two -- (3)

Но это не компилирует

main = (($) (print . runST)) fourty_two -- (4)

Который, по-видимому, указывает, что (3) компилируется только из-за специальной обработки инфикса $, однако он все еще не объясняет, почему (1) компилируется.

Вопросы:

1) Я прочитал следующие два вопроса (первый, второй), и я ' верили, что $ может быть введено только с мономорфными типами. Но я бы предположил, что . может быть создан только с мономорфными типами, и в результате аналогичным образом потерпит неудачу. Почему первый код преуспевает, а второй - нет? (например, существует ли специальное правило GHC для первого случая, что оно не может применяться во втором?)

2) Существует ли текущее расширение GHC, которое компилирует второй код? (возможно, ImpredicativePolymorphism сделал это в какой-то момент, но, похоже, он устарел, что-нибудь заменило его?)

3) Есть ли способ определить say `my_dollar` с помощью расширений GHC, чтобы делать то, что делает $, но также способен обрабатывать полиморфные типы, поэтому (print . runST) `my_dollar` fourty_two компилирует?

Изменить: Предлагаемый ответ:

Кроме того, следующее не удается скомпилировать:

main = ((.) print runST) fourty_two -- (5)

Это то же самое, что и (1), за исключением использования версии infix ..

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

4b9b3361

Ответ 1

  • Я не уверен, что понимаю, почему второй не работает. Мы можем посмотреть на тип print . runST и заметить, что он достаточно полиморфен, поэтому вина не лежит на (.). Я подозреваю, что специального правила, которое GHC имеет для infix ($), просто недостаточно. SPJ и друзья могут быть открыты для повторного изучения, если вы предлагаете этот фрагмент как ошибку на своем трекере.

    Что касается того, почему работает третий пример, ну, так как снова тип ((print . runST) $) достаточно полиморфен; на самом деле он равен типу print . runST.

  • Ничто не заменило ImpredicativePolymorphism, потому что люди GHC не видели каких-либо вариантов использования, в которых дополнительное программистское преимущество перевешивало дополнительный потенциал для ошибок компилятора. (Я не думаю, что они увидят это как убедительное, хотя, конечно, я не авторитет.)
  • Мы можем определить несколько менее полиморфный ($$):

    {-# LANGUAGE RankNTypes #-}
    infixl 0 $$
    ($$) :: ((forall s. f s a) -> b) -> ((forall s. f s a) -> b)
    f $$ x = f x
    

    Тогда ваш пример typechecks в порядке с этим новым оператором:

    *Main> (print . runST) $$ fourty_two
    42
    

Ответ 2

Я не могу сказать слишком много полномочий по этому вопросу, но здесь, как я думаю, может произойти:

Рассмотрим, что должен делать typechecker в каждом из этих случаев. (print . runST) имеет тип Show b => (forall s. ST s t) -> IO (). fourty_two имеет тип ST x Int.

forall здесь - спецификатор экзистенциального типа - здесь это означает, что переданный аргумент должен быть универсальным на s. То есть вы должны пройти в полиморфном типе, который поддерживает любое значение для s. Если вы явно не указали forall, Haskell ставит его на крайний уровень определения типа. Это означает, что fourty_two :: forall x. ST x Int и (print . runST) :: forall t. Show t => (forall s. ST s t) -> IO ()

Теперь мы можем сопоставить forall x. ST x Int с forall s. ST s t, разрешив t = Int, x = s. Так работает случай прямого вызова. Что произойдет, если мы будем использовать $, хотя?

$ имеет тип ($) :: forall a b. (a -> b) -> a -> b. Когда мы разрешаем a и b, так как тип для $ не имеет какого-либо явного типа, как показано на рисунке, аргумент x fourty_two получает доступ к самой внешней области в типе для ($) - so ($) :: forall x t. (a = forall s. ST s t -> b = IO ()) -> (a = ST x t) -> IO (). На этом этапе он пытается сопоставить a и b и не работает.

Если вы вместо этого напишите ((print . runST) $) fourty_two, тогда компилятор сначала разрешит тип ((print . runST $). Он решает тип для ($) как forall t. (a = forall s. ST s t -> b = IO ()) -> a -> b; обратите внимание, что, поскольку вторая ошибка a не ограничена, у нас нет такой переборчивой переменной, которая протекает в самый дальний объем! И поэтому совпадение выполняется успешно, функция частично применяется, и общий тип выражения forall t. (forall s. ST s t) -> IO (), который находится прямо здесь, где мы начали, и поэтому он преуспевает.