>ghci> head [3,4,5,undefined,2,undefined]
>3
Теперь рассмотрите следующий тип:
>data CoolBool = CoolBool { getCoolBool :: Bool }
Это ваш обыкновенный алгебраический тип данных, который был объявлен с использованием ключевого слова >data
. Он имеет один конструктор данных, который содержит одно поле с типом >Bool
. Давайте создадим функцию, которая сопоставляет с образцом значение >CoolBool
и возвращает значение >"привет"
вне зависимости от того, было ли значение >Bool
в >CoolBool
равно >True
или >False
:
>helloMe :: CoolBool –> String helloMe (CoolBool _) = "привет"
Вместо того чтобы применять эту функцию к обычному значению типа >CoolBool
, давайте сделаем ей обманный бросок – применим её к значению >undefined
!
>ghci> helloMe undefined
>*** Exception: Prelude.undefined
Тьфу ты! Исключение! Почему оно возникло? Типы, определённые с помощью ключевого слова >data
, могут иметь много конструкторов данных(хотя >CoolBool
имеет только один конструктор). Поэтому для того чтобы понять, согласуется ли значение, переданное нашей функции, с образцом >(CoolBool _)
, язык Haskell должен вычислить значение ровно настолько, чтобы понять, какой конструктор данных был использован, когда мы создавали значение. И когда мы пытаемся вычислить значение >undefined
, будь оно даже небольшим, возникает исключение.
Вместо ключевого слова >data
для >CoolBool
давайте попробуем использовать >newtype
:
>newtype CoolBool = CoolBool { getCoolBool :: Bool }
Нам не нужно изменять нашу функцию >helloMe
, поскольку синтаксис сопоставления с образцом одинаков независимо от того, использовалось ли ключевое слово >newtype
или >data
для объявления вашего типа. Давайте сделаем здесь то же самое и применим >helloMe
к значению >undefined
:
>ghci> helloMe undefined
>"привет"
Сработало! Хм-м-м, почему? Ну, как вы уже узнали, когда вы используете ключевое слово >newtype
, язык Haskell внутренне может представлять значения нового типа таким же образом, как и первоначальные значения. Ему не нужно помещать их ещё в одну коробку; он просто должен быть в курсе, что значения имеют разные типы. И поскольку язык Haskell знает, что типы, созданные с помощью ключевого слова >newtype
, могут иметь лишь один конструктор данных и одно поле, ему не нужно вычислять значение, переданное функции, чтобы убедиться, что значение соответствует образцу >(CoolBool _)
.
Это различие в поведении может казаться незначительным, но на самом деле оно очень важно. Оно показывает, что хотя типы, определённые с помощью деклараций >data
и >newtype
, ведут себя одинаково с точки зрения программиста (так как оба имеют конструкторы данных и поля), это фактически два различных механизма. Тогда как ключевое слово