RankNTypes и PolyKinds
В чем разница между f1
и f2
?
$ ghci -XRankNTypes -XPolyKinds
Prelude> let f1 = undefined :: (forall a m. m a -> Int) -> Int
Prelude> let f2 = undefined :: (forall (a :: k) m. m a -> Int) -> Int
Prelude> :t f1
f1 :: (forall (a :: k) (m :: k -> *). m a -> Int) -> Int
Prelude> :t f2
f2 :: (forall (k :: BOX) (a :: k) (m :: k -> *). m a -> Int) -> Int
В связи с этим вопросом на RankNTypes и область forall. Пример, взятый из руководства пользователя GHC в вид полиморфизма.
Ответы
Ответ 1
f2
требует, чтобы его аргумент был полиморфным в виде k
, а f1
просто полиморфен в самом виде. Поэтому, если вы определяете
{-# LANGUAGE RankNTypes, PolyKinds #-}
f1 = undefined :: (forall a m. m a -> Int) -> Int
f2 = undefined :: (forall (a :: k) m. m a -> Int) -> Int
x = undefined :: forall (a :: *) m. m a -> Int
тогда :t f1 x
отлично, а :t f2 x
жалуется:
*Main> :t f2 x
<interactive>:1:4:
Kind incompatibility when matching types:
m0 :: * -> *
m :: k -> *
Expected type: m a -> Int
Actual type: m0 a0 -> Int
In the first argument of ‘f2’, namely ‘x’
In the expression: f2 x
Ответ 2
Пусть будет кровавая. Мы должны количественно определить все и дать область количественной оценки. Значения имеют типы; вещи типа уровня имеют виды; виды живут в BOX
.
f1 :: forall (k :: BOX).
(forall (a :: k) (m :: k -> *). m a -> Int)
-> Int
f2 :: (forall (k :: BOX) (a :: k) (m :: k -> *). m a -> Int)
-> Int
Теперь ни один из примеров не является явно выраженным k
, поэтому ghc решает, где положить этот forall (k :: BOX)
, на основании того, упоминается ли и где k
. Я не совсем уверен, что понимаю или готов защищать политику, как указано.
Ørjan дает хороший пример разницы на практике. Позвольте быть кровавым об этом. Я напишу /\ (a :: k). t
, чтобы сделать явным абстракцию, соответствующую forall
, и f @ type
для соответствующего приложения. Игра состоит в том, что мы можем выбрать аргументы @
-ed, но мы должны быть готовы мириться с любыми /\
-ed аргументами, которые может выбрать дьявол.
Мы имеем
x :: forall (a :: *) (m :: * -> *). m a -> Int
и, следовательно, может обнаружить, что f1 x
действительно
f1 @ * (/\ (a :: *) (m :: * -> *). x @ a @ m)
Однако, если мы попытаемся дать такое же обращение, то мы увидим
f2 (/\ (k :: BOX) (a :: k) (m :: k -> *). x @ ?m0 @ ?a0)
?m0 :: *
?a0 :: * -> *
where m a = m0 a0
Система типа Haskell рассматривает приложение типа как чисто синтаксическое, поэтому единственный способ решения этого уравнения - определить функции и идентифицировать аргументы
(?m0 :: * -> *) = (m :: k -> *)
(?a0 :: *) = (a :: k)
но эти уравнения не очень хорошо сочтены, потому что k
не может быть выбрано свободно: оно /\
-ed not @
-ed.
Как правило, чтобы справиться с этими uber-полиморфными типами, хорошо написать все кванторы, а затем выяснить, как это превращается в вашу игру против дьявола. Кто выбирает то, что и в каком порядке. Перемещение a forall
внутри типа аргумента изменяет его выборку и часто может делать разницу между победой и поражением.
Ответ 3
Тип f1
помещает больше ограничений на его определение, тогда как тип f2
помещает больше ограничений на свой аргумент.
То есть: тип f1
требует, чтобы его определение было полиморфным в виде k
, тогда как тип f2
требует, чтобы его аргумент был полиморфным в виде k
.
f1 :: forall (k::BOX). (forall (a::k) (m::k->*). m a -> Int) -> Int
f2 :: (forall (k::BOX) (a::k) (m::k->*). m a -> Int) -> Int
-- Show restriction on *definition*
f1 g = g (Just True) -- NOT OK. f1 must work for all k, but this assumes k is *
f2 g = g (Just True) -- OK
-- Show restriction on *argument* (thanks to Ørjan)
x = undefined :: forall (a::*) (m::*->*). m a -> Int
f1 x -- OK
f2 x -- NOT OK. the argument for f2 must work for all k, but x only works for *