GHC построен на основе набора примитивных типов данных и операций; «примитивные» в том смысле, что они не могут быть определены в самом Haskell. Хотя вы действительно можете использовать эти вещи для написания быстрого кода, мы обычно находим это гораздо менее болезненным и более удовлетворительным в долгосрочной перспективе, используя функции и библиотеки языка высокого уровня. С любой удачей, код, который вы пишете, будет оптимизирован до эффективной неупакованной версии в любом случае. И если это не так, мы хотели бы узнать об этом.
Все эти примитивные типы данных и операции экспортируются модулем GHC.Exts.
Если вы хотите упомянуть любые примитивные типы данных или операции в вашей программе, вы должны сначала импортировать GHC.Exts, чтобы включить их в область видимости. Многие из них имеют имена, заканчивающиеся на #, и для упоминания таких имён вам нужна расширение MagicHash.
Чтобы разрешить определение литералов для других примитивных типов данных, см. расширение ExtendedLiterals.
Primops широко используют неупакованные типы и неупакованные кортежи, которые мы кратко изложим здесь.
6.16.1. Неупакованные типы
Большинство типов в GHC упакованы, что означает, что значения этого типа представляются указателем на объект в куче. Представление Haskell Int, например, представляет собой двухсловный объект в куче. Однако неупакованный тип представлен самим значением, указатели и выделение в куче не участвуют.
Неупакованные типы соответствуют «сырым машинным» типам, которые вы бы использовали в C: Int# (long int), Double# (double), Addr# (void *), и т. д. Примитивные операции (PrimOps) над этими типами — это то, чего вы, вероятно, ожидаете; например, (+#) — это сложение над Int#, и это машинное сложение, которое мы все знаем и любим — обычно одна инструкция.
Примитивные (неупакованные) типы не могут быть определены в Haskell и поэтому встроены в язык и компилятор. Примитивные типы всегда неусловно подняты; другими словами, значение примитивного типа не может быть неопределённым. (Примечание: «упакованный» тип означает, что значение представлено указателем на объект в куче; «поднятый» тип означает, что термины этого типа могут быть неопределёнными. Пример см. в следующем абзаце.) Мы используем соглашение (но это только соглашение), что примитивные типы, значения и операции имеют суффикс # (см. Магический хеш). Для некоторых примитивных типов у нас есть специальный синтаксис для литералов, также описанный в той же секции.
Примитивные значения часто представляются простым битовым шаблоном, например, Int#, Float#, Double#. Но это не обязательно так: примитивное значение может быть представлено указателем на объект, выделенный в куче. Примеры включают Array#, тип примитивных массивов. Таким образом, Array# — это неусловно поднятый, упакованный тип. Примитивный массив выделяется в куче, потому что он слишком большой, чтобы поместиться в регистр, и слишком дорого копировать; в некотором смысле, случайность заключается в том, что он представлен указателем. Если указатель представляет примитивное значение, то он действительно указывает на это значение: нет неоценённых thunk, нет косвенных обращений. Ничего не может находиться в конце указателя, кроме примитивного значения. Программа, интенсивно использующая численные вычисления и неупакованные типы, может работать гораздо быстрее, чем её «стандартный» аналог — мы наблюдали трехкратное ускорение на одном примере.
6.16.2. Виды неупакованных типов
Поскольку неупакованные типы представлены без использования указателей, мы не можем хранить их в полиморфном типе данных. Например, узел Just типа Just 42# должен отличаться от узла Just типа Just 42; первый хранит целое число непосредственно, а второй хранит указатель. GHC в настоящее время не поддерживает такое разнообразие узлов Just (ни для других типов данных). Соответственно, вид неупакованного типа отличается от вида упакованного типа.
Отчёт о Haskell описывает, что * (написанный Type и импортированный из Data.Kind в диалекте Haskell GHC) является видом обычных типов данных, таких как Int. Кроме того, конструкторы типов могут иметь виды со стрелками; например, Maybe имеет вид Type -> Type. Неупакованные типы имеют вид, определяющий их представление во время выполнения. Например, тип Int# имеет вид TYPE IntRep, а Double# имеет вид TYPE DoubleRep. Эти виды говорят, что представление во время выполнения Int# — это машинный целое число, а представление во время выполнения Double# — это машинная двойная точность с плавающей запятой. В отличие от этого, вид Type фактически является просто синонимом для TYPE
LiftedRep. Более подробные сведения о механизмах TYPE см. в разделе о полиморфизме представления во время выполнения.
Учитывая, что вид Int# не равен Type, следует, что Maybe
Int# запрещено. Аналогично, поскольку переменные типов, как правило, имеют вид Type (например, в (.) :: (b -> c) -> (a -> b) -> a -> c все переменные типов имеют вид Type), полиморфизм, как правило, не работает над примитивными типами. Если вернуться назад, это имеет смысл, потому что полиморфная функция должна манипулировать указателями на свои данные, а большинство примитивных типов неупакованы.
Существуют некоторые ограничения на использование примитивных типов:
-
Вы не можете определить новый тип, тип представления которого (аргумент конструктора данных) является неупакованным типом. Таким образом, это недопустимо:
newtype A = MkA Int#
Однако это ограничение может быть ослаблено, включив расширение
UnliftedNewtypes. В разделе о неусловно поднятых новых типах подробно описывается поведение таких типов. - Вы не можете привязать переменную с неупакованным типом в глобальной привязке.
- Вы не можете привязать переменную с неупакованным типом в рекурсивной привязке.
-
Вы можете привязать неупакованные переменные в (нерекурсивной, неглобальной) привязке по шаблону, но вы должны сделать любой такой шаблонный поиск строгим. (Если вы этого не сделаете, будет выдано предупреждение
-Wunbanged-strict-patterns.) Например, вместо:data Foo = Foo Int Int# f x = let (Foo a b, w) = ..rhs.. in ..body..
вы должны написать:
data Foo = Foo Int Int# f x = let !(Foo a b, w) = ..rhs.. in ..body..
поскольку
bимеет типInt#. См. Динамическая семантика шаблонов bang.
6.16.3. Неупакованные кортежи
-
UnboxedTuples -
- Подразумевает:
- С:
-
6.8.1
Неупакованные кортежи фактически не экспортируются GHC.Exts; они являются синтаксическим расширением (UnboxedTuples). Неупакованный кортеж выглядит так:
(# e_1, ..., e_n #)
где e_1..e_n — выражения любого типа (примитивного или не примитивного).
Тип неупакованного кортежа выглядит так же.
Обратите внимание, что при включении неупакованных кортежей, (# — это единственный лексем, поэтому, например, при использовании операторов, таких как # и #-, вам нужно написать ( # ) и ( #- ) вместо (#) и (#-).
Неупакованные кортежи используются для функций, которые должны возвращать несколько значений, но они избегают выделения в куче, обычно связанного с использованием полноценных кортежей. При возвращении неупакованного кортежа компоненты помещаются непосредственно в регистры или на стек; сам неупакованный кортеж не имеет составного представления. Многие примитивные операции, перечисленные в primops.txt.pp, возвращают неупакованные кортежи. В частности, монады IO и ST используют неупакованные кортежи, чтобы избежать ненужного выделения при последовательностях операций.
Типичное использование неупакованных кортежей — просто возвращать несколько значений, привязывая эти несколько результатов с помощью выражения case, таким образом:
f x y = (# x+1, y-1 #)
g x = case f x x of { (# a, b #) -> a + b }
Вы можете использовать неупакованный кортеж в привязке по шаблону, например:
f x = let (# p,q #) = h x in ..body..
Если типы p и q не неупакованы, результирующая привязка является ленивой, как любая другая привязка по шаблону Haskell. Приведённый выше пример переводится следующим образом:
f x = let t = case h x of { (# p,q #) -> (p,q) }
p = fst t
q = snd t
in ..body..
Действительно, привязки могут быть даже рекурсивными. См. Динамическую семантику шаблонов bang для более точного описания.
Для ссылки на конструкторы типов неупакованных кортежей самих по себе, например, если вы хотите прикрепить к ним экземпляры, используйте (# #), (#,#), (#,,#) и т. д. Это отражает синтаксис для упакованных кортежей (), (,), (,,) и т. д.