# Глава 22. Новый тип
Помимо `data` существует ещё одно ключевое слово, предназначенное для определения нового типа. Оно так и называется — `newtype`. Эти слова похожи друг на друга «в одну сторону»: вы можете поставить `data` на место `newtype`, но не наоборот.
## Различия
Тип, определяемый с помощью слова `newtype`, обязан иметь один и только один конструктор значения. Мы можем написать так:
```haskell
newtype IPAddress = IP String
```
А вот так не можем:
```haskell
newtype IPAddress = IP String | Localhost
```
Компилятор заупрямится:
```
A newtype must have exactly one constructor,
but ‘IPAddress’ has two
In the newtype declaration for ‘IPAddress’
```
Кроме того, в таком типе должно быть одно и лишь одно поле. То есть можно так:
```haskell
newtype IPAddress = IP String
```
Или же так, с меткой:
```haskell
newtype IPAddress = IP { value :: String }
```
А вот два или более полей запихнуть не удастся:
```haskell
newtype EndPoint = EndPoint String Int
```
Компилятор вновь обратит наше внимание на проблему:
```
The constructor of a newtype must have exactly one field
but ‘EndPoint’ has two
In the definition of data constructor ‘EndPoint’
In the newtype declaration for ‘EndPoint’
```
Более того, нульарный конструктор тоже не подойдёт:
```haskell
newtype HardDay = Monday
```
И вновь ошибка:
```
The constructor of a newtype must have exactly one field
but ‘Monday’ has none
```
## Зачем он нужен?
В самом деле, зачем нам нужно такое хозяйство? Это нельзя, то нельзя. Какой смысл?
Смысл в оптимизации. Обратите внимание на модель `newtype`:
```haskell
newtype IPAddress = IP String
-- новый название конструктор Поле
-- тип значения
```
Фактически, `newtype` берёт одно-единственное значение некоторого существующего типа и всего лишь оборачивает его в свой конструктор. Именно поэтому тип, введённый с помощью `newtype`, не относится к АТД, и с точки зрения компилятора он является лишь переименованием типа (англ. type renaming). Это делает такой тип более простым и эффективным с точки зрения представления в памяти, нежели тип, определяемый с `data`.
Когда мы пишем так:
```haskell
data IPAddress = IP String
```
мы говорим компилятору: `IPAddress` — это абсолютно новый и самобытный тип, которого никогда не было ранее. А когда пишем так:
```haskell
newtype IPAddress = IP String
```
мы говорим: `IPAddress` — это всего лишь обёртка для значения уже существующего типа `String`.
## type vs newtype
Внимательный читатель спросит, в чём же фундаментальное отличие типов, вводимых с помощью `newtype`, от типов, вводимых с помощью `type`? Там синоним, тут — обёртка. Отличие вот в чём.
Когда мы пишем так:
```haskell
type String = [Char]
```
мы объявляем: тип `String` — это эквивалентная замена типу `[Char]`. И поэтому везде, где в коде стоит `[Char]`, мы можем поставить `String`, и везде, где стоит `String`, мы можем поставить `[Char]`. Например, если функция объявлена так:
```haskell
replace :: String
-> String
-> String
-> String
```
мы можем спокойно переписать объявление:
```haskell
replace :: [Char]
-> [Char]
-> [Char]
-> [Char]
```
и ничего не изменится.
Когда же мы пишем так:
```haskell
newtype MyInt = MyInt Int
```
мы объявляем: тип `MyInt` — это новый тип, представление которого такое же, как у типа `Int`. Мы не можем просто взять и поставить `MyInt` на место `Int`, потому что эти типы равны лишь с точки зрения представления в памяти, с точки зрения системы типов они абсолютно различны.
А зачем же нам нужно это? Для простоты и надёжности кода. Допустим, есть такая функция:
```haskell
getBuildsInfo :: String -> Int -> BuildsInfo
getBuildsInfo projectName limit = ...
```
Эта функция запрашивает у CI-сервиса (через REST API) информацию о сборках проекта. Из определения мы видим, что первым аргументом выступает имя проекта, а вторым — количество сборок. Однако в месте применения функции это может быть не столь очевидным:
```haskell
let info = getBuildsInfo "ohaskell.guide" 4
```
Что такое первая строка? Что такое второе число? Неясно, нужно глядеть в определение, ведь даже объявление не расскажет нам правду:
```haskell
getBuildsInfo :: String -> Int -> BuildsInfo
-- что за что за
-- строка? число?
```
Вот тут нам и помогают наши типы, ведь стандартные `String` и `Int` сами по себе не несут никакой полезной информации о своём содержимом. Конечно, мы могли бы обойтись и без типов, просто введя промежуточные выражения:
```haskell
let project = "ohaskell.guide"
limit = 4
info = getBuildsInfo project limit
```
Однако программист может этого и не сделать, и тогда мы получим «магические значения», смысл которых нам неизвестен. Куда лучше ввести собственные типы:
```haskell
newtype Project = Project String
newtype Limit = Limit Int
getBuildsInfo :: Project -> Limit -> BuildsInfo
-- уже не уже не
-- просто просто
-- строка число
```
Это заставит нас писать явно:
```haskell
let info = getBuildsInfo (Project "ohaskell.guide")
(Limit 4)
```
Теперь, даже без промежуточных выражений, смысл строки и числа вполне очевиден.
Это **важный принцип** в Haskell: безликие типы наподобие `String` или `Int` заменять на типы, имеющие конкретный смысл для нас.
Кроме того, `newtype`-типы помогают нам не допускать глупых ошибок. Например, есть другая функция:
```haskell
getArtifacts :: String -> Int -> Int -> [Project]
getArtifacts projectName limit offset = ...
```
Мало того, что перед нами вновь безликие `Int`, так их ещё и два. И вот какая нелепая ошибка может нас поджидать:
```haskell
let project = "ohaskell.guide"
limit = 4
offset = 1
info = getArtifacts project offset limit
```
Заметили? Мы случайно перепутали аргументы местами, поставив `offset` на место `limit`. Работа функции при этом нарушится, однако компилятор останется нем как рыба, ведь с точки зрения системы типов ошибки не произошло: и там `Int`, и тут `Int`. Синонимы для `Int` также не помогли бы. Однако если у нас будут `newtype`-типы:
```haskell
newtype Limit = Limit Int
newtype Offset = Offset Int
```
тогда подобная ошибка не пройдёт незамеченной:
```haskell
let project = "ohaskell.guide"
limit = Limit 4
offset = Offset 1
info = getArtifacts offset limit
```
Типы аргументов теперь разные, а значит, путаница между ними гарантированно прервёт компиляцию.
Кстати, для `newtype`-типов директива `deriving` применяется точно так же, как для типов, объявленных через `data`!
Функция `segmentLength` получает id географического объекта. Например, тропинки или железной дороги. А также индексы начальной и конечной точки на этом объекте, между которыми требуется получить длину геометрии географического объекта. {.task_text}
Как видите, объявление функции плохо читается. А при ее применении в функции `main` был перепутан порядок аргументов. {.task_text}
Заведите `newtype`-типы для id объекта, индекса на геометрии и возвращаемой длины. Используйте их в коде. {.task_text}
```haskell {.task_source #haskell_chapter_0220_task_0010}
module Main where
segmentLength :: Int -> Int -> Int -> Double
segmentLength geoId indexStart indexEnd =
-- Представьте, что мы получаем геометрию из бд
-- и честно считаем длину
if indexStart > 10 && indexEnd > 20 && geoId > 0
then 60.5
else 70.0
main :: IO ()
main = print $ segmentLength start end gid
where
start = 134
end = 136
gid = 136694862
```
Пример объявления типа: `newtype GeoId = GeoId Int deriving (Eq, Ord)`. {.task_hint}
```haskell {.task_answer}
module Main where
newtype GeoId = GeoId Int deriving (Eq, Ord)
newtype Index = Index Int deriving (Eq, Ord)
newtype Length = Length Double deriving (Show)
segmentLength :: GeoId -> Index -> Index -> Length
segmentLength geoId indexStart indexEnd =
-- Представьте, что мы геометрию из бд
-- и честно считаем длину
if indexStart > Index 10 &&
indexEnd > Index 20 &&
geoId > GeoId 0
then
Length 60.5
else
Length 70.0
main :: IO ()
main = print $ segmentLength gid start end
where
start = Index 134
end = Index 136
gid = GeoId 136694862
```
Вот такие они, `newtype`-типы. В последующих главах мы увидим ещё большую мощь системы типов Haskell.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!