Так что для определений данных/новых типов левая сторона содержит определение , включая имя и некоторые переменные типа, а правая рука сторона содержит список конструкторов обычно также включает имена и типы. Пример может служить:
data List x = Nil | Cons x (List x)
Обратите внимание, что Nil
и Cons
ваши конструкторы, x
и List x
являются типами аргументов Cons
конструктора, а List
этого имени типа (который имеет вида * -> *
, это требует типа аргумент ___, чтобы «заполнить его», прежде чем он сможет описать список ___s).
Иногда мы хотим использовать псевдоним типа. Есть два способа сделать это. Сначала type
, что оставляет типы соразмерными - так что если вы пишете type DirectoryPath = [String]
, тогда, когда у вас есть DirectoryPath
, вы можете манипулировать им, как список строк; в частности, вы можете использовать :
или ++
, чтобы добавить к нему; так что "apps" : base_directory
был бы совершенно законным Haskell.
Иногда вы просто хотите скрыть эти функции большим предупреждающим знаком: «Не используйте это, если вы не знаете, что делаете». Для этого вы можете написать data FilePath = FilePath [String]
. Обратите внимание, что мы немного злоупотребляем обозначениями, называя тип тем же, что и для только для такого типа. Теперь, чтобы сделать то же самое, вам придется написать:
case base_directory of FilePath bd -> FilePath $ "apps" : bd
Зачем вам это нужно? Ну, во-первых, в приведенном выше синтаксисе структура каталогов растет справа налево, тогда как большинство людей пишут каталоги слева направо. Во-вторых, вы можете захотеть добавить .
как no-op (то есть bd ++ []
) и ..
как родительский указатель (то есть tail bd
). У вас также могут быть некоторые причудливые соглашения (например,если список начинается с «", то это абсолютный каталог, иначе он относится к текущему каталогу), который необходимо сохранить. Наконец, вы, возможно, захотите позже изменить код на Maybe [String]
, чтобы значение Nothing
могло представлять собой пути, которые делают сумасшедшие вещи, например /../..
(абсолютный путь к двум родителям над корнем).
Все это становится легче, если вы можете просто сказать:
FilePath xs ./ x
| x == "." = FilePath xs
| x == ".." = FilePath (tail xs)
| otherwise = ...
, а затем применять, что везде пишут люди base_directory ./ "apps"
.
Другой пример: newtype SanitizedString = Sanitized String
. Поскольку мы не использовали type
, мы получаем метку времени компиляции, которая отслеживает наш код, убедившись, что строки, предоставленные пользователем, были правильно экранированы раньше, скажем, они направляются в инструкции вставки базы данных или пользовательский интерфейс или где угодно.
Что вы, вероятно, используете для этого, это то, что вы можете написать экземпляр Monad
для этого типа и тем самым использовать его с помощью do
-notation.
Если ваш код только обертывает один существующий тип, newtype
избегает введения дополнительных проблем с задержкой и задержки конструктора данных и т. Д. В противном случае это точно так же, как data
. Так что в вашем коде:
newtype STR a = STR (Store -> (Result a, Store))
это не синоним type
, но больше как data
конструктор, который в конечном счете исчезает после компиляции. A STR a
- это псевдоним для Store -> (Result a, Store)
, который был обернут в конструктор STR
(поэтому он не может использоваться как функция напрямую без назначения деструкции).
Что означает «семантика нормальной объектной модели»? – dfeuer
Я согласен «нормальным» является спорным словом. Но я не мог найти лучшего способа выразить свое замешательство. –
Я хочу сказать, что гораздо лучше попытаться придумать простой язык, выражающий вашу точку зрения, чем использовать причудливые слова, такие как «семантика объектной модели». У Haskell даже нет объектов, поэтому такие описания с большей вероятностью будут путать, чем общаться. – dfeuer