Примітка: Ця відповідь базується на останніх спостереженнях за дискусіями Левіті. Все, що стосується поліморфізму Левіті, наразі реалізоване лише у кандидатах на випуск GHC 8.0 і як таке може змінюватися (див., Наприклад, №11471 )
TL; DR : Це спосіб зробити функції поліморфними над піднятими та непіднятими типами, що неможливо за допомогою звичайних функцій. Наприклад, наступний код не вводить перевірку за допомогою регулярних поліморфізмів, оскільки Int#
має kind #
, але змінні типу у id
мають kind *
:
{-# LANGUAGE MagicHash #-}
import GHC.Prim
example :: Int# -> Int#
example = id
Couldn't match kind ‘*’ with ‘#’
When matching types
a0 :: *
Int# :: #
Expected type: Int# -> Int#
Actual type: a0 -> a0
In the expression: id
Зверніть увагу, що (->)
все ще використовується деяка магія.
Перш ніж я почну відповідати на це питання, давайте зробимо крок назад і піти в один з найбільш часто використовуваних функцій, ($)
.
Що це ($)
за тип? Ну, згідно з Хакежем та звітом, це так
($) :: (a -> b) -> a -> b
Однак це не на 100%. Це зручна маленька брехня. Проблема полягає в тому, що поліморфні типи (як a
і b
) мають вигляд *
. Однак розробники (бібліотеки) хотіли використовувати ($)
не лише для типів з видом *
, але й для типів #
, наприклад
unwrapInt :: Int -> Int#
Поки Int
має вид *
(це може бути знизу), Int#
має вид #
(і не може бути знизу взагалі). Тим не менше, перевіряються такі типи коду:
unwrapInt $ 42
Це не повинно працювати. Пам'ятаєте тип повернення ($)
? Це було поліморфно, і поліморфні типи мають вигляд *
, ні #
! То чому це спрацювало? Спочатку це була помилка , а потім - хакер (уривок листа Райана Скотта зі списку розсилки ghc-dev):
То чому це відбувається?
Довга відповідь полягає в тому, що до GHC 8.0, у підписі типу ($) :: (a -> b) -> a -> b
, b
насправді було не в натуральній формі *
, а навпаки OpenKind
.
OpenKind
- це жахливий хак, який дозволяє заселяти як піднятий (вид *
), так і непіднятий (вид #
) тип, саме тому (unwrapInt $ 42)
перевірки типу.
То що ж ($)
нового типу в GHC 8.0? Його
($) :: forall (w :: Levity) a (b :: TYPE w). (a -> b) -> a -> b
Щоб зрозуміти це, ми повинні розглянути Levity
:
data Levity = Lifted | Unlifted
Тепер ми можемо думати про ($)
один із наступних типів, оскільки існує лише два варіанти w
:
($) :: forall a (b :: TYPE Lifted). (a -> b) -> a -> b
($) :: forall a (b :: TYPE Unlifted). (a -> b) -> a -> b
TYPE
є магічною константою, і вона перевизначає види *
та #
як
type * = TYPE Lifted
type # = TYPE Unlifted
Кількісне визначення видів також є досить новим і є частиною інтеграції залежних типів у Haskell .
Назва поліморфізм Левіті походить від того, що тепер ви можете писати поліморфні функції як для піднятого, так і для непіднятого типу, чогось, що було заборонено / можливо за попередніх обмежень поліморфізму. Це також позбавляє від OpenKind
злому одночасно. Це насправді "просто" про це, обробляючи обидва типи.
До речі, ви не самотні зі своїм запитанням. Навіть Саймон Пейтон Джонс сказав, що існує потреба у вікі-сторінці Левіті , а Річард Е. (поточний реалізатор цього) заявив, що вікі-сторінка потребує оновлення щодо поточного процесу.
Список літератури