Что я делаю неправильно с этим подразумеваемым тривиальным упражнением по полиморфизму более высокого ранга?

Более года назад я задал вопрос Как использовать прокси-сервер в Haskell, и с тех пор у меня есть небольшое использование расширения RankNTypes GHC. Проблема в том, что каждый раз, когда я пытаюсь работать с ней, я получаю причудливые сообщения об ошибках и взламываю код, пока они не уйдут. Или я сдаюсь.

Очевидно, я действительно не понимаю полиморфизм более высокого ранга в Haskell. Чтобы попытаться это разрешить, я решил перейти к самым простым примерам, которые я мог бы проверить, проверить все мои предположения и посмотреть, могу ли я получить момент Эврика.

Первое предположение - причина, по которой полиморфизм более высокого ранга не является стандартной функцией Haskell 98 (или 2010?), заключается в том, что, если вы принимаете некоторые не-очевидные ограничения, которые многие программисты даже не заметили, это isn Не нужно. Я могу определить полиморфные функции ранга 1 и 2, которые на первый взгляд эквивалентны. Если я загружу их в GHCi и назову их теми же параметрами, они дадут те же результаты.

Итак - простые примерные функции...

{-# LANGUAGE RankNTypes #-}

rank0 :: [Int] -> Bool
rank0 x = null x

rank1a :: [a] -> Bool
rank1a x = null x

rank1b :: forall a. [a] -> Bool
rank1b x = null x

rank2 :: (forall a. [a]) -> Bool
rank2 x = null x

и сеанс GHCi...

GHCi, version 7.4.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :load example01
[1 of 1] Compiling Main             ( example01.hs, interpreted )
Ok, modules loaded: Main.
*Main>

Пока нет ошибок - хорошее начало. Затем проверьте каждую функцию с пустым параметром списка...

*Main> rank0 []
True
*Main> rank1a []
True
*Main> rank1b []
True
*Main> rank2 []
True
*Main>

Честно говоря, я немного удивился тем, что в этом случае работали функции rank1a и rank1b. Список не знает, какие элементы типа он содержит, функции тоже не знают, но наверняка тип должен быть решен, чтобы сделать этот вызов? Я ожидал, что вам нужно предоставить явную подпись.

Это не проблема ATM, и результаты кажутся многообещающими. Следующий, непустые списки...

*Main> rank0 [1,2,3]
False
*Main> rank1a [1,2,3]
False
*Main> rank1b [1,2,3]
False
*Main> rank2 [1,2,3]

<interactive>:10:8:
    No instance for (Num a)
      arising from the literal `1'
    In the expression: 1
    In the first argument of `rank2', namely `[1, 2, 3]'
    In the expression: rank2 [1, 2, 3]
*Main>

О, дорогая, похоже, что версия ранга 2 не нравится, когда параметр знает немного больше об этом типе. Тем не менее, возможно, проблема в том, что литералы 1 и т.д. Являются полиморфными...

*Main> rank2 ([1,2,3] :: [Int])

<interactive>:11:8:
    Couldn't match type `a' with `Int'
      `a' is a rigid type variable bound by
          a type expected by the context: [a] at <interactive>:11:1
    Expected type: [a]
      Actual type: [Int]
    In the first argument of `rank2', namely `([1, 2, 3] :: [Int])'
    In the expression: rank2 ([1, 2, 3] :: [Int])
*Main>

Ошибка отличается, но она по-прежнему не работает, и я до сих пор не понимаю эти сообщения об ошибках.

Беседу с различными теориями, одна идея у меня была, что, может быть, мне нужно сказать GHC, чтобы "забыть" некоторый статический тип списка. По этой теории я пробовал разные вещи, в том числе...

*Main> [1,2,3] :: [a]

<interactive>:12:2:
    No instance for (Num a1)
      arising from the literal `1'
    In the expression: 1
    In the expression: [1, 2, 3] :: [a]
    In an equation for `it': it = [1, 2, 3] :: [a]
*Main>

ОК, GHCi не знает, о чем я говорю. В случае, если GHCi просто нужно было точно знать, какой тип забыть, я также пробовал...

*Main> ([1,2,3] :: [Int]) :: [a]

<interactive>:15:2:
    Couldn't match type `a1' with `Int'
      `a1' is a rigid type variable bound by
           an expression type signature: [a1] at <interactive>:15:1
    Expected type: [a]
      Actual type: [Int]
    In the expression: ([1, 2, 3] :: [Int]) :: [a]
    In an equation for `it': it = ([1, 2, 3] :: [Int]) :: [a]
*Main>

Так много для моих надежд получить сообщение об ошибке, что GHCi не знает, как show значение с забытым типом. Я не знаю, как создать список со "забытым" статическим типом, и я даже не уверен, что это имеет смысл.

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

Ответ 1

Подумайте, что означает тип rank2.

rank2 :: (forall a. [a]) -> Bool
rank2 x = null x

Первый аргумент rank2 должен быть чем-то типа forall a. [a]. forall, являющийся внешним, означает, что тот, кто получает такое значение, может выбрать свой выбор a. Подумайте об этом, как о принятии типа в качестве дополнительного аргумента.

Итак, чтобы дать что-то как аргумент rank2, он должен быть списком, чьи элементы могут быть любого типа, который может потребоваться для внутренней реализации rank2. Поскольку нет способа вызвать значения такого произвольного типа, единственными возможными входами являются [] или списки, содержащие undefined.

Контрастируйте это с помощью rank1b:

rank1b :: forall a. [a] -> Bool
rank1b x = null x

Здесь forall уже является внешним, поэтому любой, кто использует rank1b, сам выбирает тип.

Вариант, который будет работать, будет примерно таким:

rank2b :: (forall a. Num a => [a]) -> Bool
rank2b x = null x

Теперь вы можете передать ему список числовых литералов, которые являются полиморфными во всех экземплярах Num. Другой альтернативой было бы что-то вроде этого:

rank2c :: (forall a. [a -> a]) -> Bool
rank2c x = null x

Это работает, потому что вы действительно можете вызвать значения типа forall a. a -> a, в частности функцию id.

Ответ 2

Давайте сравним эти явные подписи типа forall:

rank1b :: forall a. [a] -> Bool
rank1b x = null x

Это означает forall a.([a]->Bool), поэтому работает, как вы ожидаете, во всех списках, независимо от типа, и возвращает Bool.

rank2 может принимать только полиморфные списки:

rank2 :: (forall a. [a]) -> Bool
rank2 x = null x

Теперь это другое - это означает, что аргумент x должен быть полиморфным. [] является полиморфным, поскольку он может быть любого типа:

>:t [] 
[] :: [a]

который тайно означает forall a.[a], но rank2 не примет ['2'], поскольку имеет тип [Char].

rank2 будет принимать только списки, которые действительно относятся к типу forall a.[a].

Ответ 3

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

Я не уверен, что более высокий уровень полиморфизма - это то, что вы думаете. Я думаю, что концепция имеет смысл только в отношении типов функций.

Например:

reverse :: forall a. [a] -> [a]
tail    :: forall a. [a] -> [a]

сообщает нам, что reverse и tail работают независимо от типа элемента списка. Теперь, учитывая эту функцию:

foo f = (f [1,2], f [True, False])

Каков тип foo? Стандартный вывод HM не может найти тип. В частности, он не может вывести тип f. Мы должны помочь здесь проверить тип и обещать, что мы передаем только функции, которые не заботятся о типе элементов списка:

foo :: (forall a. [a] -> [a]) -> ([Int], [Bool])

Теперь мы можем

foo reverse
foo tail

и, следовательно, иметь полезный тип ранга-2. Обратите внимание, что подпись типа запрещает передавать что-то вроде:

foo (map (1+))

потому что переданная функция не полностью независима от типа элемента: для нее требуются элементы Num.