# Глава 3. Киты и черепаха
Haskell стоит на Трёх Китах, имена которым: Функция, Тип и Класс типов. Они же, в свою очередь, покоятся на огромной Черепахе, имя которой — Выражение.
## Черепаха
Haskell-программа представляет собой совокупность выражений (англ. expression). Взгляните:
```haskell
1 + 2
```
Это — основной кирпич Haskell-программы, будь то Hello World или часть инфраструктуры международного банка. Конечно, помимо сложения единицы с двойкой существуют и другие выражения, но суть у них у всех одна.
**Выражение** — это то, что может дать нам некий полезный результат. Его мы получаем в результате вычисления (англ. evaluation) выражения. Все выражения можно вычислить, однако одни выражения в результате вычисления уменьшаются (англ. reduce), а другие — нет. Первые иногда называют редуцируемыми выражениями, а вторые — нередуцируемые. Так, выражение:
```haskell
1 + 2
```
относится к редуцируемым, потому что оно в результате вычисления уменьшится и даст нам другое выражение:
```haskell
3
```
Это выражение уже нельзя уменьшить, оно нередуцируемое и мы теперь лишь можем использовать его как есть.
Таким образом, выражения, составляющие программу, вычисляются/редуцируются до тех пор, пока не останется некое окончательное, корневое выражение. А запуск Haskell-программы на выполнение (англ. execution) — это запуск всей этой цепочки вычислений, причём с корнем этой цепочки мы уже познакомились ранее. Помните функцию `main`, определённую в модуле `app/Main.hs`? Вот эта функция и является главной точкой нашей программы, её Альфой и Омегой.
## Первый Кит
Вернёмся к выражению `1 + 2`. Полезный результат мы получим лишь после того, как вычислим это выражение, то есть осуществим сложение. И как же можно «осуществить сложение» в рамках Haskell-программы? С помощью функции. Именно функция делает выражение вычислимым, именно она оживляет нашу программу, потому я и назвал Функцию Первым Китом Haskell. Но дабы избежать недоразумений, определимся с понятиями.
Что такое функция в математике? Вспомним школьный курс.
**Функция** — это закон, описывающий зависимость одного значения от другого.
Рассмотрим функцию возведения целого числа в квадрат: {#block-square}
```haskell
square v = v * v
```
Функция square определяет простую зависимость: числу 2 соответствует число 4, числу 3 — 9, и так далее. Схематично это можно записать так:
```
2 -> 4
3 -> 9
4 -> 16
5 -> 25
...
```
Входное значение функции называют аргументом. А так как функция определяет однозначную зависимость выходного значения от аргумента, её, функцию, называют ещё **отображением:** она отображает/проецирует входное значение на выходное. Получается как бы труба: кинули в неё 2 — с другой стороны вылетело 4, кинули 5 — вылетело 25.
Чтобы заставить функцию сделать полезную работу, её необходимо применить (англ. apply) к аргументу. Пример:
```haskell
square 2
```
Мы применили функцию `square` к аргументу 2. Синтаксис предельно прост: имя функции и через пробел аргумент. Если аргументов более одного — просто дописываем их так же, через пробел. Например, функция `sum`, вычисляющая сумму двух своих целочисленных аргументов, применяется так:
```haskell
sum 10 20
```
Так вот, выражение `1 + 2` есть ни что иное, как применение функции! И чтобы яснее это увидеть, перепишем выражение:
```haskell
(+) 1 2
```
Это применение функции `(+)` к двум аргументам, 1 и 2. Не удивляйтесь, что имя функции заключено в скобки, вскоре я расскажу об этом подробнее. А пока запомните главное:
**Вычислить выражение** — это значит применить какие-то функции (одну или более) к каким-то аргументам (одному или более).
Для изменения порядка вычислений в выражении можно воспользоваться скобками. Например, результатом этого выражения
```haskell
3 / (3 - 1)
```
будет 1.5.
Второй пример: в Haskell есть встроенные функции `min` и `max`. Каждая из них принимает два аргумента и возвращает соответственно наименьшее или наибольшее из них значение. И вот такое выражение
```haskell
min (max 2 3) 4
```
означает следующее: вычислить `max` от 2 и 3, а затем `min` от полученного значения и числа 4.
Вычислите это выражение. {.task_text}
```haskell
(*) 2 (6 - 1)
```
```consoleoutput {.task_source #haskell_chapter_0030_task_0010}
```
Это применение функции `(*)` к двум аргументам, 2 и (6 - 1). Его можно переписать как `2 * (6 - 1)`. Что равносильно `2 * 5`, то есть 10. {.task_hint}
```haskell {.task_answer}
10
```
И ещё. Возможно, вы слышали о так называемом «вызове» функции. В Haskell функции не вызывают. Понятие «вызов» функции пришло к нам из почтенного языка C. Там функции действительно вызывают (англ. call), потому что в C, в отличие от Haskell, понятие «функция» не имеет никакого отношения к математике. Там это подпрограмма, то есть обособленный кусочек программы, доступный по некоторому адресу в памяти. Если у вас есть опыт разработки на C-подобных языках — забудьте о подпрограмме. В Haskell функция — это функция в математическом смысле слова, поэтому её не вызывают, а **применяют** к чему-то.
Создайте модуль `Main`, функция `main` которого выводит в консоль число 128. {.task_text}
Как вы помните, для вывода в консоль в Haskell есть встроенная функция `putStrLn`. Но она работает только со строками. Поэтому во избежание ошибки компиляции примените `putStrLn` не к самому числу, а к результату применения `show`. {.task_text}
Встроенная функция `show` принимает аргумент и конвертирует его в строку. {.task_text}
```haskell {.task_source #haskell_chapter_0030_task_0020}
```
Применение функции `f` к результату, возвращаемому функцией `g` от аргумента `x`: `f (g x)`. {.task_hint}
```haskell {.task_answer}
module Main where
main :: IO ()
main = putStrLn (show 128)
```
В предыдущей задаче возникла необходимость вместо строки печатать произвольный объект. Для этого мы воспользовались цепочкой применений `putStrLn (show 128)`. Для переиспользования кода руки чешутся обернуть это в отдельную функцию для печати в консоль произвольного объекта. Но не спешите! В Haskell есть встроенная функция, делающая именно это. Называется она `print`. {.task_text}
Замените цепочку из двух функций `show` и `putStrLn` на `print`. {.task_text}
```haskell {.task_source #haskell_chapter_0030_task_0030}
module Main where
main :: IO ()
main = putStrLn (show 128)
```
В теле функции `main` требуется применить функцию `print` к аргументу 128. {.task_hint}
```haskell {.task_answer}
module Main where
main :: IO ()
main = print 128
```
Внимательный читатель спросит, каким же образом использованная в задаче функция `print` узнаёт, как именно отобразить конкретное значение в виде строки? О, это интереснейшая тема, но она относится к Третьему Киту Haskell, до подробного знакомства с которым нам ещё далеко.
## Второй Кит
Итак, любое редуцируемое выражение суть применение функции к некоторому аргументу (тоже являющемуся выражением):
```haskell
square 2
```
Здесь `square` — функция, 2 — аргумент.
Аргумент представляет собой некоторое значение, его ещё называют «данное» (англ. data). Данные в Haskell — это сущности, обладающие двумя главными характеристиками: типом и конкретным значением/содержимым.
**Тип** — это Второй Кит в Haskell. Тип отражает конкретное содержимое данных, а потому все данные в программе обязательно имеют некий тип. Когда мы видим данное типа `Double`, мы точно знаем, что перед нами число с плавающей точкой, а когда видим данные типа `String` — можем ручаться, что перед нами строки.
Отношение к типам в Haskell очень серьёзное, и работа с типами характеризуется тремя важными чертами:
1. статическая проверка,
2. сила,
3. выведение.
Три эти свойства системы типов Haskell — наши добрые друзья, ведь они делают нашу программистскую жизнь счастливее. Познакомимся с ними.
### Статическая проверка
Статическая проверка типов (англ. static type checking) — это проверка типов всех данных в программе, осуществляемая на этапе компиляции. Haskell-компилятор упрям: когда ему что-либо не нравится в типах, он громко ругается. Поэтому если функция работает с целыми числами, применить её к строкам никак не получится. Так что если компиляция нашей программы завершилась успешно, мы точно знаем, что с типами у нас всё в порядке. Преимущества статической проверки невозможно переоценить, ведь она гарантирует отсутствие в наших программах целого ряда ошибок. Мы уже не сможем спутать числа со строками или вычесть метры из рублей.
Конечно, у этой медали есть и обратная сторона — время, затрачиваемое на компиляцию. Вам придётся свыкнуться с этой мыслью: внесли изменения в проект — будьте добры скомпилировать. Однако утешением вам пусть послужит тот факт, что преимущества статической проверки куда ценнее времени, потраченного на компиляцию.
### Сила
Сильная (англ. strong) система типов — это бескомпромиссный контроль соответствия ожидаемого действительному. Сила делает работу с типами ещё более аккуратной. Вот вам пример из мира C:
```c++
double coeff(double base) {
return base * 4.9856;
}
int main() {
int value = coeff(122.04);
...
}
```
Это канонический пример проблемы, обусловленной слабой (англ. weak) системой типов. Функция `coeff` возвращает значение типа `double`, однако вызывающая сторона ожидает почему-то целое число. Ну вот ошиблись мы, криво скопировали. В этом случае произойдёт жульничество, называемое скрытым приведением типов (англ. implicit type casting): число с плавающей точкой, возвращённое функцией `coeff`, будет грубо сломано путём приведения его к типу `int`, в результате чего дробная часть будет отброшена и мы получим не 608.4426, а 608. Подобная ошибка, кстати, приводила к серьёзным последствиям, таким как уничтожение космических аппаратов. Нет, это вовсе не означает, что слабая типизация ужасна сама по себе, просто есть иной путь.
Благодаря сильной типизации в Haskell подобный код не имеет ни малейших шансов пройти компиляцию. Мы всегда получаем то, что ожидаем, и если должно быть число с плавающей точкой — расшибись, но предоставь именно его. Компилятор скрупулёзно отслеживает соответствие ожидаемого типа фактическому, поэтому когда компиляция завершается успешно, мы абсолютно уверены в гармонии между типами всех наших данных.
### Выведение
Выведение (англ. inference) типов — это способность определить тип данных автоматически, по конкретному выражению. В том же языке C тип данных следует указывать явно:
```C++
double value = 122.04;
```
однако в Haskell мы напишем просто:
```haskell
value = 122.04
```
В этом случае компилятор автоматически выведет тип `value` как `Double`.
Выведение типов делает наш код лаконичнее и проще в сопровождении. Впрочем, мы можем указать тип значения и явно, а иногда даже должны это сделать. В последующих главах я объясню, почему.
Да, кстати, вот простейшие стандартные типы, они нам понадобятся:
```
123 Int
23.5798 Double
'a' Char
"Hello!" String
True Bool, истина
False Bool, ложь
```
С типами `Int` и `Double` вы уже знакомы. Тип `Char` — это Unicode-символ. Тип `String` — строка, состоящая из Unicode-символов. Тип `Bool` — логический тип, соответствующий истине или лжи. В последующих главах мы встретимся ещё с несколькими стандартными типами, но пока хватит и этих.
И заметьте: имя типа в Haskell всегда начинается с большой буквы.
### Третий Кит
А вот о Третьем Ките, о **Классе типов,** я пока умолчу, потому что знакомиться с ним следует лишь после того, как мы поближе подружимся с первыми двумя.
Уверен, после прочтения этой главы у вас появилось множество вопросов. Ответы будут, но позже. Более того, следующая глава несомненно удивит вас.
## Для любопытных
Если вы работали с объектно-ориентированными языками, такими как C++, вас удивит тот факт, что в Haskell между понятиями «тип» и «класс» проведено чёткое различие. А поскольку типам и классам типов в Haskell отведена колоссально важная роль, добрый вам совет: когда в будущих главах мы познакомимся с ними поближе, не пытайтесь проводить аналогии из других языков. Например, некоторые усматривают родство между классами типов в Haskell и интерфейсами в Java. Не делайте этого, во избежание путаницы.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!