Что означает подпись типа для `undefined` в Haskell? - PullRequest
7 голосов
/ 26 мая 2019

Я новичок в Haskell, и я озадачен сигнатурой типа функции undefined.

Я ожидал чего-то более простого, но нашел это на Hackage:

undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). HasCallStack => a

Особый случай ошибки. Ожидается, что компиляторы распознают это и вставят сообщения об ошибках, которые больше соответствуют контексту, в котором появляется undefined.

Не могли бы вы объяснить, что означает эта подпись?

Спасибо!

Ответы [ 2 ]

5 голосов
/ 27 мая 2019

Это продолжение ответа @ leftaroundabout.

В Haskell значения имеют типы.Но типы сами также имеют типы.Когда тип действует как тип другого типа, мы называем его «родом».

Наиболее важный и распространенный тип в Haskell - Type, часто обозначаемый в сигнатурах как*.Это типы «поднятых» типов, то есть значения которых могут быть символами, которые при оценке могут расходиться, выдавать ошибку и т. Д. Например, тип Int имеет вид Type.

Существуют также другие типы, такие как Int#, которые не отменены.Значение типа Int# - это , никогда , это всегда актуальное значение в памяти.

Короче говоря, представление значений в памяти контролируется по виду их типа.

RuntimeRep - другой вид.Это такие типы, как LiftedRep и IntRep.Эти типы не имеют никаких значений, они существуют просто для выражения вещей на уровне типов , как мы увидим.

Существует супермагическая сущность уровня типов, называемая TYPE, что при параметризации с типом вида RuntimeRep (то есть с типом, который описывает представление в памяти) возвращает тип типов, значения которых имеют это представление.Например, Type равно TYPE LiftedRep, а Int# равно TYPE IntRep.

ghci> :set -XMagicHash
ghci> import GHC.Prim 
ghci> import GHC.Types
ghci> import Data.Kind
ghci> :kind (Int :: TYPE 'LiftedRep)
(Int :: TYPE 'LiftedRep) :: *
ghci> :kind Int#
Int# :: TYPE 'IntRep

Теперь мы можем вернуться к тому, почему undefined имеет такую ​​странную подпись.Дело в том, что мы хотим иметь возможность использовать undefined во всех функциях, будь то функции, которые возвращают типы вида Type, но также и функции, которые возвращают типы вида TYPE IntRep (в другихслова: тип Int#) или функции, которые возвращают другой неподнятый тип.В противном случае нам понадобится несколько разных версий undefined, что будет раздражать.

Решение состоит в том, чтобы сделать undefined levity-polymorphic .Подпись гласит: для любого возможного представления в памяти (RuntimeRep) и для любого возможного типа , значения которого имеют это представление, undefined считает члена этого типа.

5 голосов
/ 26 мая 2019

Для всех, что нужно знать новичку, подпись просто

undefined :: a

, что означает, как всегда с переменными типа (то есть любые строчные буквы), что a является универсально определенным количеством , что также может быть сделано в явном виде:

{-# LANGUAGE ExplicitForall #-}
undefined :: forall a. a

... или, как я предпочитаю, писать

{-# LANGUAGE ExplicitForall, UnicodeSyntax #-}
undefined :: ∀ a. a

Количественное определение превышает всетипы , то есть все вещи с видом * (читай: «тип», точнее тип поднятых типов - поднятый означает, что это может быть ленивый звук).Следовательно, вы можете использовать undefined в любом выражении, независимо от того, какой тип требуется.

Теперь, undefined, конечно, вещь, подобная «частичной функции», в основном функция, которая обнуляет аргументы, которые определенынет нигде.(FTR, это , а не функция, как функция по определению имеет аргумент [с].)
Вы хотели бы получить полезную ошибкусообщение, когда оно действительно вычисляется, но GHC по умолчанию не создает стек вызовов для всего (по соображениям производительности), поэтому раньше сообщение об ошибке было почти полностью бесполезным.Вот тут и приходит HasCallStack: это ограничение, которое, по сути, сообщает контексту, в котором некоторый код может получить undefined, что он должен отметить место, где это происходит, чтобы сообщение об ошибке действительнопокажи это.Итак,

undefined :: ∀ a. HasCallStack => a

Немного запутано, что HasCallStack появляется после ∀ a - это на самом деле не имеет ничего общего с a, но с контекстом, в котором undefined будетиспользоваться.Просто форма подписи всегда

<i>Identifier</i> :: <i>Quantifiers</i>. <i>Constraints</i> => <i>Type</i>

, а HasCallStack - ограничение, поэтому оно появляется посередине.(Чаще всего ограничения фактически применяются к одной из переменных типа, по которой вы количественно оценили.)

Наконец, этот материал RunTimeRep примерно равен полиморфизму легкомыслия .Я сам не очень понимаю, но это обсуждается в Почему неопределенная функция levity-polymorphic при использовании ее с распакованными типами?

...