chapters/06-immutability-n-purity.md
# Неизменность и чистота
В предыдущей главе мы познакомились с функциями и выражениями, увидев близкую связь этих понятий. В этой главе мы познакомимся с функциями поближе, а также узнаем, что такое «чисто функциональный» язык и почему в нём нет места оператору присваивания.
## Объявляем и определяем
Применение функции нам уже знакомо, осталось узнать про объявление и определение, без них использовать функцию не получится. Помните функцию `square`, возводящую свой единственный аргумент в квадрат? Вот как выглядит её объявление и определение:
```haskell
square :: Int -> Int
square v = v * v
```
Первая строка содержит объявление, вторая — определение. Объявление (англ. declaration) — это весть всему миру о том, что такая функция существует, вот её имя и вот типы, с которыми она работает. Определение (англ. definition) — это весть о том, что конкретно делает данная функция.
Рассмотрим объявление:
```haskell
square :: Int -> Int
```
Оно разделено двойным двоеточием на две части: слева указано имя функции, справа — типы, с которыми эта функция работает, а именно типы аргументов и тип вычисленного, итогового значения. Как вы узнали из предыдущей главы, все данные в Haskell-программе имеют конкретный тип, а поскольку функция работает с данными, её объявление содержит типы этих данных. Типы разделены стрелками. Схематично это выглядит так:
```haskell
square :: Int -> Int
имя тип тип
функции аргумента вычисленного
значения
```
Такое объявление сообщает нам о том, что функция `square` принимает единственный аргумент типа `Int` и возвращает значение того же типа `Int`. Если же аргументов более одного, объявление просто вытягивается. Например, объявление функции `prod`, возвращающей произведение двух целочисленных аргументов, могло бы выглядеть так:
```haskell
prod :: Int -> Int -> Int
имя тип тип тип
функции первого второго вычисленного
аргумента аргумента значения
```
Идею вы поняли: ищем крайнюю правую стрелку, и всё что левее от неё — то типы аргументов, а всё что правее — то тип вычисленного значения.
Мы не можем работать с функцией, которая ничего не вычисляет. То есть аналога C-функции `void f(int i)` в Haskell быть не может, так как это противоречит математической природе. Однако мы можем работать с функцией, которая ничего не принимает, то есть с аналогом C-функции `int f(void)`. С такими функциями мы познакомимся в следующих главах.
Теперь рассмотрим определение функции `square`:
```haskell
square v = v * v
```
Схема определения такова:
```haskell
square v = v * v
имя имя это выражение
функции аргумента
```
А функция `prod` определена так:
```haskell
prod x y = x * y
имя имя имя это выражение
функции первого второго
аргумента аргумента
```
Определение тоже разделено на две части: слева от знака равенства — имя функции и имена аргументов (имена, а не типы), разделённые пробелами, а справа — выражение, составляющее суть функции, её содержимое. Иногда эти части называют «головой» и «телом»:
```haskell
square v = v * v
голова функции тело функции
(англ. head) (англ. body)
```
Обратите внимание, речь здесь идёт именно о знаке равенства, а никак не об операторе присваивания. Мы ничего не присваиваем, мы лишь декларируем равенство левой и правой частей. Когда мы пишем:
```haskell
prod x y = x * y
```
мы объявляем следующее: «Отныне выражение `prod x y` равно выражению `x * y`». Мы можем безопасно заменить выражение `prod 2 5` выражением `2 * 5`, а выражение `prod 120 500` — выражением `120 * 500`, и при этом работа программы гарантированно останется неизменной.
Но откуда у меня такая уверенность? А вот откуда.
## Чисто функциональный
Haskell — чисто функциональный (англ. purely functional) язык. Чисто функциональным он называется потому, что центральное место в нём уделено чистой функции (англ. pure function). А чистой называется такая функция, которая предельно честна с нами: её выходное значение всецело определяется её аргументами и более ничем. Это и есть функция в математическом смысле. Вспомним функцию `prod`: когда на входе числа `10` и `20` — на выходе всегда будет `200`, и ничто не способно помешать этому. Функция `prod` является чистой, а потому характеризуется отсутствием побочных эффектов (англ. side effects): она не способна сделать ничего, кроме как вернуть произведение двух своих аргументов. Именно поэтому чистая функция предельно надёжна, ведь она не может преподнести нам никаких сюрпризов.
Скажу больше: чистые функции не видят окружающий мир. Вообще. Они не могут вывести текст на консоль, их нельзя заставить обработать HTTP-запрос, они не умеют дружить с базой данных и прочесть файл они также неспособны. Они суть вещь в себе.
А чтобы удивить вас ещё больше, открою ещё один секрет Haskell.
## «Присваивание? Не, не слышал…»
В мире Haskell нет места оператору присваивания. Впрочем, этот факт удивителен лишь на первый взгляд. Задумаемся: если каждая функция в конечном итоге представляет собою выражение, вычисляемое посредством применения каких-то других функций к каким-то другим аргументам, тогда нам просто не нужно ничего ничему присваивать.
Вспомним, что присваивание (англ. assignment) пришло к нам из императивных языков. Императивное программирование (англ. imperative programming) — это направление в разработке, объединяющее несколько парадигм программирования, одной из которых является знаменитая объектно-ориентированная парадигма. В рамках этого направления программа воспринимается как набор инструкций, выполнение которых неразрывно связано с изменением состояния (англ. state) этой программы. Вот почему в императивных языках обязательно присутствует понятие «переменная» (англ. variable). А раз есть переменные — должен быть и оператор присваивания. Когда мы пишем:
```c
coeff = 0.569;
```
мы тем самым приказываем: «Возьми значение `0.569` и перезапиши им то значение, которое уже содержалось в переменной `coeff` до этого». И перезаписывать это значение мы можем множество раз, а следовательно, мы вынуждены внимательно отслеживать текущее состояние переменной `coeff`, равно как и состояния всех остальных переменных в нашем коде.
Однако существует принципиально иной подход к разработке, а именно декларативное программирование (англ. declarative programming). Данное направление также включает в себя несколько парадигм, одной из которых является функциональная парадигма, нашедшая своё воплощение в Haskell. При этом подходе программа воспринимается уже не как набор инструкций, а как набор выражений. А поскольку выражения вычисляются путём применения функций к аргументам (то есть, по сути, к другим выражениям), там нет места ни переменным, ни оператору присваивания. Все данные в Haskell-программе, будучи созданными единожды, уже не могут быть изменены. Поэтому нам не нужен не только оператор присваивания, но и ключевое слово `const`. И когда в Haskell-коде мы пишем:
```haskell
coeff = 0.569
```
мы просто объявляем: «Отныне значение `coeff` равно `0.569`, и так оно будет всегда». Вот почему в Haskell-коде символ `=` — это знак равенства в математическом смысле, и с присваиванием он не имеет ничего общего.
Уверен, вы удивлены. Как же можно написать реальную программу на языке, в котором нельзя изменять данные? Какой прок от этих чистых функций, если они не способны ни файл прочесть, ни запрос по сети отправить? Оказывается, прок есть, и на Haskell можно написать очень даже реальную программу. За примером далеко ходить не буду: сама эта книга построена с помощью программы, написанной на Haskell, о чём я подробнее расскажу в следующих главах.
А теперь, дабы не мучить вас вопросами без ответов, мы начнём ближе знакомиться с Китами Haskell, и детали большой головоломки постепенно сложатся в красивую картину.
## Для любопытных
В процессе работы Haskell-программы в памяти создаётся великое множество различных данных, ведь мы постоянно строим новые данные на основе уже имеющихся. За их своевременное уничтожение отвечает сборщик мусора (англ. garbage collector, GC), встраиваемый в программы компилятором GHC.