Большие идеи
Пользовательские типы данных. Лучшая практика Haskell - создавать пользовательские типы данных для моделирования домена. Мы хотим использовать типы данных для описания структуры данных, которые наша программа будет обрабатывать. Моделирование нашей области через типы данных очень важно, потому что это позволяет компилятору помочь нам в дальнейшем. Моделирование нашей предметной области с помощью типов данных похоже на моделирование нашего уровня сохраняемости в базах данных - сделайте эту часть правильно, и остальная часть программы будет проще и очевиднее.
Количество элементов, типы сумм, типы продуктов. Почему нас заботят типы сумм, типы продуктов и количество элементов? (Примечание: определения всех этих терминов приведены в разделе ниже). Причина, по которой эти вещи важны, заключается в том, что они говорят нам, сколько различных реализаций существует для данного типа.
Например, возьмем следующую подпись типа myFunc :: Bool
. Если мы посмотрим на тип данных Bool
, есть только два возможных значения для Bool
- True
или False
. Это означает, что есть только две возможные реализации myVal
: myVal = True
OR myVal = False
. Нет другого возможного способа реализовать myVal
, учитывая сигнатуру типа.
Давайте попробуем что-нибудь с типом продукта. Допустим, у нас есть следующий тип: data Product = Product Int8 Int8
. Конструктор данных Product
должен применяться к двум значениям, поэтому мы знаем, что тип данных Product
является типом продукта. Какова мощность типа данных Product
? Ну, Int8
- это тип, который представляет значения от -127
до 128
. Его мощность равна 256
, поэтому мощность Product
равна 256 * 256 = 65,536
. Это означает, что если у меня есть подпись типа myVal :: Product
, есть 65,536
способ удовлетворить эту подпись типа.
В конце концов, урок состоит в том, что гораздо проще рассуждать о типе, мощность которого меньше. Это становится очень важным для функций, потому что количество элементов функции экспоненциально. Мы берем мощность второго аргумента и повышаем до мощности первого.
Например, у меня есть следующая подпись типа:
myFunc :: Bool —> Bool
Мощность Bool
равна 2. Я могу вычислить мощность типа myFunc
(помните, что ->
является конструктором типа) как 2 ^ 2
или 4
. Однако, поскольку все экспоненциально, даже небольшое увеличение мощности аргументов типа до ->
приводит к значительному увеличению мощности всего типа.
Вид. Как и количество элементов, вид относится к свойству типов данных. В частности, добрый - это тип типа. Например:
data Person = Person String
Тип данных Person
имеет вид *
. *
указывает конкретный тип данных. Другими словами, конструктор типа Person
уже является константой конкретного типа / типа данных, и поэтому его не нужно применять к аргументу типа.
Однако давайте возьмем другое объявление данных:
data Person' a = Person' String a
Тип данных Person'
имеет вид * -> *
. Это означает, что конструктор типа Person'
должен быть применен к конкретному типу, чтобы получить конкретный тип. Заметьте, что чрезвычайно полезно держать в центре внимания тот факт, что ->
- это конструктор типов. Тип конструктора типа ->
- -> :: * -> * -> *
. Другими словами, ->
должен применяться к двум конкретным типам, чтобы получить конкретный тип.
Newtype. Подавляющее большинство преимуществ Haskell связано с его системой типов. Отсюда следует, что важно знать, как эффективно использовать систему типов. Мы не сможем получить все предполагаемые преимущества Haskell, не зная, как пользоваться системой типов. Я поднимаю этот вопрос сейчас в разделе newtype, поскольку считаю, что newtype является прекрасным примером того, как эффективное использование системы типов необходимо для повышения безопасности нашего кода. т. е. количество ошибок, которые может отловить система типов.
Например, допустим, у нас есть следующая функция:
tooManyGoats = Int -> Bool
Поскольку мы использовали Int
, мы еще не смоделировали наш домен. Это значение могло быть откуда угодно и относиться к чему угодно. Возможно, это относится к количеству имеющихся у нас коров, но имя нашей функции указывает на то, что она должна проверять количество имеющихся у нас коз.
Мы можем повысить безопасность нашего кода - мы можем более эффективно использовать систему типов - используя в этом случае объявление newtype:
newtype Goat -> Goat Int
(Мы могли бы сделать то же самое с объявлением данных вместо объявления newtype, но newtype более эффективен. (Конструктор данных newtype также строг, в то время как конструктор данных из объявления данных ленив, но что угодно - для меня это не важно ).
Итак, newtype может помочь нам более эффективно использовать систему типов Haskell и тем самым повысить безопасность нашего кода. Однако есть еще одна причина для использования объявлений newtype, и это то, что мы можем создать экземпляр класса типов, который отличается от экземпляра этого класса типов, реализованного содержащимся в нем значением.
Синтаксис записи - я действительно не хочу говорить о синтаксисе записи. Я не фанат. Мне нравится идея передачи аргументов по полям, а не по позициям, но есть много крайних случаев, когда я думаю, что записи могут быть больше хлопот, чем они того стоят. Например, нельзя иметь две разные записи с одним и тем же полем, поскольку функции доступа создаются автоматически. Затем в книге есть раздел о случайных низах и т. Д.
Терминология / Понятия
Конструктор нулевых данных. Конструктор нулевых данных - это конструктор данных, который не принимает аргументов. Например:
data Bool = True | False
Конструкторы данных True
и False
являются нулевыми. Конструкторы нулевых данных также называются константами.
Конструктор унарных данных. Конструктор унарных данных - это конструктор данных, который должен применяться к одному аргументу перед созданием / получением значения. Например:
data Person = Person String
Конструктор данных Person
должен быть применен к значению типа String
до создания значения типа Person
.
Типы сумм. Тип суммы - это тип, в котором содержится более одного конструктора. Тип суммы обозначается |
, и это означает, что у нас есть дизъюнкция. Например:
data Bool = True | False
Опять же, |
подсказывает нам, что у нас есть тип суммы. Тип суммы сообщает нам, что значение типа Bool
будет построено из конструктора данных True
ИЛИ конструктора данных False
. Одно или другое, но не то и другое.
Типы продуктов. Тип продукта - это тип, конструктор данных которого принимает более одного аргумента. Другими словами, чтобы создать значение этого типа, мы должны применить конструктор данных к значению первого типа И значению второго типа. Например:
data Person = Person String Integer
Конструктор данных Person
должен быть применен к значению типа String
И значению типа Integer
, чтобы было создано значение типа Person
.
Количество элементов. Количество элементов относится к свойству типов данных. В частности, это относится к числу возможных значений, которые могут находиться в этом типе данных. Если мы думаем о типах данных как о наборе связанных значений, мощность типа данных - это количество значений в этом наборе. В предыдущем разделе мы говорили о том, как количество элементов типа может быть определено исключительно из его сигнатуры типа.
Расширение кода в Haskell. Одна из причин, по которой я хочу изучать Haskell, заключается в том, что система типов Haskell позволяет нам без страха выполнять рефакторинг, а также без опасений расширять нашу программу. (Конечно, все это предполагает, что мы используем систему типов в наших интересах - нам нужно иметь возможность моделировать наш домен в системе типов).
Прекрасным примером этого преимущества Haskell является задача № 5 упражнения «Транспортные средства». В нем мы решили, что мы собираемся изменить нашу модель - в частности, мы собираемся расширить тип данных Vehicle
так, чтобы конструктор данных Plane
теперь должен был применяться не только к значению типа Airline
, но и к значение типа Size
. Если мы внесем это изменение в тип, а затем попытаемся скомпилировать нашу программу, система типов Haskell повсюду сообщит нам, что нам нужно учесть это изменение.
Другими словами, мы изменяем нашу модель предметной области, чтобы включить новое требование - то, что происходит постоянно при разработке программного обеспечения - и Haskell укажет нам, где это изменение нарушает наш код, чтобы мы могли его исправить.
Разное
Уровень типа. Помните, что конструкторы типов используются на уровне типов, а конструкторы данных - на уровне терминов.
Константы данных. Мы уже упоминали об этом, но конструкторы данных с нулевым значением также известны как константы.
Константа типа - Подобно конструкторам / константам данных, у нас также есть конструкторы типов и константы типов. Тип с видом *
- это конкретная константа типа / типа.
Конструкторы типов - поскольку типы также могут быть параметризованы - это означает, что они должны применяться к аргументу типа - их также можно описать с помощью таких терминов, как нулевой, унарный и т. д.
Переменные типа. Типы, в объявлении данных которых есть переменная типа, называются параметризованными типами. Очень важно помнить, что переменная типа является заполнителем - в конечном итоге мы должны заменить эту переменную типа конкретным типом. Только после того, как мы применили конструктор типа к конкретному типу, мы получаем другой конкретный тип.
Классификации конструкторов данных. Конструкторы данных классифицируются по количеству аргументов, к которым они должны применяться:
0
arguments - конструктор данных nullary; точнее, константа данных.1
аргумент - унарный конструктор данных2
arguments - конструктор двоичных данных; тоже продукт.
Language Pragma - языковая директива - это инструкция компилятору обрабатывать ввод способами, выходящими за рамки того, что предусмотрено стандартом.
объявления типов - объявления типов создают конструкторы типов, но не конструкторы данных. Например:
type Name = String
Теперь у нас есть конструктор типа Name
, но нет конструктора данных String
.
зависимые типы. Мы не говорим о зависимых типах в Haskell, но одна мысль, которая поразила меня во время обсуждения кардинальности, заключается в том, что все зависимые типы сводятся к уменьшению количества элементов.
Проверка типов помогает тем, кто помогает себе.
Инфиксные операторы - не буквенно-цифровые операторы по умолчанию являются инфиксными. Все инфиксные конструкторы данных должны начинаться с двоеточия - :
. Конструктор данных с одним двоеточием уже занят операцией cons.