Почему неопределенная функция levity-polymorphic при использовании ее с unboxed-типами?

Я только что закончил читать статью Политизма Левита.

У меня возник вопрос о том, почему undefined может быть levity-polymorphic, когда используется как unboxed type.

Во-первых, давайте начнем с некоторых определений бокса из статьи:

  • в коробке:

    • Коробчатое значение представлено указателем на кучу.

    • Int и Bool являются примерами типов, которые имеют значения в коробке.

  • unboxed:

    • Значение unboxed представлено самим значением (а не указателем на кучу).

    • Int# и Char# из модуля GHC.Prim являются примерами типов с незанятыми значениями.

    • Unboxed value не может быть thunk. Аргументы функций для распакованных типов должны передаваться по значению.

Далее следует статья с некоторыми определениями легкомыслия:

  • снято:

    • Поднятый тип - это ленивый.

    • Поднятый тип имеет дополнительный элемент за пределами обычных, представляющий собой не завершающий вычисление.

    • Например, тип Bool поднимается, что означает, что для Bool есть три разных значения: True, False и (внизу).

    • Все поднятые типы ДОЛЖНЫ быть в коробке.

  • unlifted

    • Неперекрытый тип - это строгий.

    • Элемент не существует в незанятом типе.

    • Int# и Char# являются примерами несвязанных типов.

Далее в документе объясняется, как GHC 8 обеспечивает функциональность, позволяющую переменным типа иметь полиморфную легкость.

Это позволяет вам написать levity-polymorphic undefined со следующим типом:

undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). a

Это говорит о том, что undefined должен работать для любых RuntimeRep, даже не связанных типов.

Ниже приведен пример использования undefined как unboxed, unifted Int# в GHCi:

> :set -XMagicHash
> import GHC.Prim
> import GHC.Exts
> I# (undefined :: Int#)
*** Exception: Prelude.undefined

Я всегда думал о undefined как о том же, что и (внизу). Однако в документе говорится: "Элемент не существует в несимметричном типе".

Что здесь происходит? Является undefined фактически не при использовании в качестве unlifted типа? Неужели я что-то недопонимаю о бумаге (или о боксе или легкомыслии)?

Ответы

Ответ 1

Автор статьи "Политизм левитации".

Во-первых, я хочу прояснить несколько заблуждений, изложенных выше:

  • Bool действительно поднят, в коробке. Однако он все еще имеет только 2 значения: True и False. Выражение ⊥ просто не является значением. Это выражение, и переменная может быть привязана к ⊥, но это не делает ⊥ значение. Возможно, лучший способ сказать все это в том, что если x :: Bool, то оценка x может привести к трем различным результатам: оценка True, оценка False и ⊥. Здесь ⊥ представляет собой любое вычисление, которое не прекращается, как правило, в том числе метательного исключение (что undefined действительно) и зацикливания.

  • Подобно этой последней точке, undefined не является значением. Это житель любого типа, но это не значение. Значение - это то, что при оценке ничего не делает, но undefined не соответствует этой спецификации.

  • В зависимости от того, как вы смотрите на него, ⊥ может существовать в нераскрытом типе. Например, возьмите это определение:

    loop :: () -> Int#
    loop x = loop x
    

    Это определение принимается GHC. Тем не менее loop() является элементом of неперекрытого, unboxed типа Int#. Разница между непринятым типом и поднятым типом в этом отношении заключается в том, что невозможно привязать переменную к loop(). Любая попытка сделать это (например, let z = loop() in...) будет зацикливаться до того, как переменная будет привязана.

    Обратите внимание, что это не отличается от бесконечно рекурсивной функции в неуправляемом языке, например C.

Итак, как мы разрешаем undefined иметь несдержанный тип? @dfeuer имеет это право: undefined тайно функция. Его полная подпись типа не undefined :: forall (r :: RuntimeRep) (a :: TYPE r). HasCallStack => a undefined :: forall (r :: RuntimeRep) (a :: TYPE r). HasCallStack => a, что означает, что это функция, как и loop, выше. Во время выполнения он будет принимать текущий стек вызовов в качестве параметра. Таким образом, всякий раз, когда вы используете undefined, вы просто вызываете функцию, которая генерирует исключение, не создавая никаких проблем.

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