Фраза «конструктор типов» употребляется двумя способами. Некоторые люди используют их для любого типа со стрелкой; но в отчете на Haskell он довольно последовательно используется по-другому, поэтому я поговорю об этом определении.
Конструкторы создаются объявлениями data
и newtype
и являются единственным новым именем, введенным этими уровнями на уровне типа. Вот несколько примеров конструкторов типов:
Either
Maybe
[]
Bool
Упс, вы заметили этот последний? Правильно, в фразе «конструктор типов» нет ничего, что подразумевало бы, что она должна иметь возможность принимать аргументы. Bool
- это имя уровня типа, введенное объявлением данных - следовательно, является конструктором типа. Вот несколько примеров типов, которые не являются конструкторами:
Maybe Int
a -> b
Either ()
m -- even if we know, say, Monad m holds
Упс, вы заметили эти последние два? Правильно, если идти в другом направлении, нет ничего в том, чтобы иметь возможность принимать дополнительные аргументы типа, которые делают вас конструктором типов. Каждый из Either
и ()
является конструктором, но применение Either
к ()
не является, потому что это не одно имя уровня типа, созданное объявлением data
или newtype
. Точно так же, m
является переменной типа, а не конструктором - ее значение не фиксируется ни одним объявлением data
или newtype
.
Помимо конструкторов и переменных, в стандартном Haskell есть еще один тип имени уровня типа: псевдонимы типов. Существует два основных различия между псевдонимами типов и конструкторами:
Конструкторы являются инъективными, псевдонимы могут не быть. Если FooC a b c
и FooC a' b' c'
имеют одинаковый тип, а FooC
является конструктором, тогда a
и a'
имеют одинаковый тип, b
и b'
имеют одинаковый тип, а c
и c'
того же типа. Контраст
type FooA a = String
, в котором FooA ()
и FooA Bool
относятся к одному типу, хотя ()
и Bool
не относятся к одному типу.
Конструкторы могут быть применены частично, псевдонимы типов не могут быть. Например, если вы напишите
type BarA a = Maybe a
тогда StateT Int BarA ()
недопустимо - BarA
всегда должен быть немедленно передан аргумент типа - даже если StateT Int Maybe ()
имеет значение. Конечно, с
type BarEtaA = Maybe
затем StateT Int BarEtaA ()
снова допустим, потому что псевдоним BarEtaA
не нуждается в аргументах, прежде чем он расширится до определенного значения Maybe
.
Есть некоторые другие небольшие различия между псевдонимами и конструкторами, но они не являются фундаментальными (и смягчаются подходящими расширениями GHC).
Существует только одно различие между конструкторами и переменными, о котором я могу подумать в стандартном Haskell, а именно их взаимодействие с механизмом классов типов. В частности, экземпляры должны иметь форму instance <class> (<constructor> <variable> <variable> <variable> ...) where ...
, а ограничения / контексты должны иметь форму <class> (<constructor> <variable> <variable> ...) => ...
. Эти ограничения ослаблены подходящими расширениями GHC.
Extended Haskell, реализованный GHC, также включает в себя две другие формы определенных имен на уровне типов, семейства типов и семейства данных, которые смешивают некоторые из перечисленных выше свойств. Имена, определяемые семействами данных, очень похожи на конструкторы (они инъективны и могут применяться частично), в то время как имена, определяемые семействами типов, очень похожи на псевдонимы (они не гарантированно являются инъективными и не могут применяться частично). Основное отличие состоит в том, что они могут выполнять «сопоставление с образцом на уровне типа», в котором есть несколько определений, которые применяются в разных случаях. Полное описание, вероятно, не вписывается в ответ StackOverflow, но руководство описывает их и ссылки на несколько длинных статей, обсуждающих их.