Все эксперименты, описанные ниже, были выполнены с помощью GHC 8.0.1.Случай исчезающего ограничения: странности более высокого ранга
Этот вопрос является дополнением к RankNTypes with type aliases confusion. Проблема там сводилась к типам функций, как этот ...
{-# LANGUAGE RankNTypes #-}
sleight1 :: a -> (Num a => [a]) -> a
sleight1 x (y:_) = x + y
... которые отвергнут проверки типов ...
ThinAir.hs:4:13: error:
* No instance for (Num a) arising from a pattern
Possible fix:
add (Num a) to the context of
the type signature for:
sleight1 :: a -> (Num a => [a]) -> a
* In the pattern: y : _
In an equation for `sleight1': sleight1 x (y : _) = x + y
... потому что более высокий ранг Num a
cannot be moved outside of the type of the second argument (как можно было бы, если бы у нас было a -> a -> (Num a => [a])
). Раз это так, мы в конечном итоге пытаемся добавить более высокий ранг ограничение на переменный уже количественно по всему этому, то есть:
sleight1 :: forall a. a -> (Num a => [a]) -> a
С этой репризой сделано, мы могли бы попытаться упростить пример немного. Давайте заменим (+)
с чем-то, что не требует Num
и разъединять тип проблемного аргумента от результата:
sleight2 :: a -> (Num b => b) -> a
sleight2 x y = const x y
Это не работает так же, как и раньше (за исключением незначительного изменения ошибки сообщение):
ThinAir.hs:7:24: error:
* No instance for (Num b) arising from a use of `y'
Possible fix:
add (Num b) to the context of
the type signature for:
sleight2 :: a -> (Num b => b) -> a
* In the second argument of `const', namely `y'
In the expression: const x y
In an equation for `sleight2': sleight2 x y = const x y
Failed, modules loaded: none.
Использование const
здесь, однако, возможно, нет необходимости, так что мы могли бы попытаться писать реализацию себя:
sleight3 :: a -> (Num b => b) -> a
sleight3 x y = x
Удивительно, но это действительно работает!
Prelude> :r
[1 of 1] Compiling Main (ThinAir.hs, interpreted)
Ok, modules loaded: Main.
*Main> :t sleight3
sleight3 :: a -> (Num b => b) -> a
*Main> sleight3 1 2
1
Еще более странно, там, кажется, нет никакого фактического Num
ограничение на второй аргумент:
*Main> sleight3 1 "wat"
1
Я не совсем уверен, о том, как сделать это понятным. Возможно, мы могли бы сказать это, так же, как мы можем манипулировать undefined
, если мы никогда его не оценим, неудовлетворительное ограничение может прилипать к типу, которое просто отлично, если оно не используется для объединения в любой части правой части. Это, однако, похоже на довольно слабую аналогию, специально учитывая, что не строгость, как мы обычно понимаем, это понятие, включающее ценности, а не типы. Более того, это не дает нам приблизиться к пониманию того, как в мире String
объединяет Num b => b
- предполагая, что такое происходит на самом деле, что-то, о чем я не уверен. Что же тогда представляет собой точное описание того, что происходит, когда ограничение, похоже, исчезает таким образом?
Я нахожу это несколько естественным, так как, например, 'x' в типе' f :: A -> (x -> B) -> C' in в позиции _positive_ (или ковариантной). Грубо говоря, 'f' обещает _provide_' x' вместо того, чтобы требовать его извне. Это распространяется на 'y :: C a => a'. Кроме того, каждый тип, который вы используете, 'y', GHC должен предоставить словарь, чтобы получить значение типа' a', поэтому он будет жаловаться на отсутствие такого словаря. – chi
Это кажется очень странным, я согласен. Вероятно, только разработчики GHC знают, какая магия происходит за «forall» :) – Shersh
@chi Постановка вопроса с точки зрения положительных и отрицательных позиций вполне проясняется. Интересно, как это имеет смысл здесь, если вы подрываете интуитивный взгляд на ограничения как некоторую ограниченную количественную оценку (например, «Num a => a» означает какой-то тип 'a' в' Num') и начинают видеть их как, в сущности, функции из словарей. – duplode