Вы, вероятно, слышали о термине функциональное программирование, что это декларативная, неизменяемая, чистая функция (без побочных эффектов) и она использует рекурсию по циклу.

Неизменная, декларативная, чистая функция и рекурсия — мы повторяем эти слова каждый раз, когда говорим о функциональном программировании.

Но почему?

Сегодня мы собираемся изучить, как связаны друг с другом неизменяемость, декларативная, чистая функция и рекурсия и почему мы не можем упомянуть об одном, не упомянув об остальных.

неизменность

Сначала давайте рассмотрим, почему мы хотим избегать изменяемых переменных, если это возможно, сравнив их с неизменяемыми переменными.

Возможная ошибка с изменяемыми переменными:

let temperature = 30
let distance = 3000
//…others code
temperature = 2000 // mistake, you should change the distance!
// …others code
displayTemperature(temperature) // mistake 1
displayTemperature(distance) // mistake 2

Возможная ошибка с неизменяемыми переменными:

const temperature = 30
const distance = 3000
//…others code
temperature = 2000 // compiler will complain!!
// …others code
displayTemperature(temperature) // no problem
displayTemperature(distance) // mistake 1

и изменчивые, и неизменяемые страдают от одной и той же ошибки: читать их не в том месте

Это не то, о чем компилятор может позаботиться, вам нужно написать тесты, чтобы смягчить подобные ошибки.

Однако у изменяемых переменных есть одна дополнительная точка отказа: присвоение значения неправильной переменной. Это может быть очень сложно отладить, потому что присвоение значений может происходить где угодно.

Мы также можем смягчить неправильное присвоение значений переменных, написав тесты, но написание тестов отнимает наше драгоценное инженерное время. Подобную ошибку можно предотвратить, используя неизменяемые переменные.

Профилактика лучше лечения.

Декларативный код

Мы много слышали об «императивном» и «декларативном», но как отличить, какой код императивный, а какой декларативный?

Ключевое слово написания декларативного кода — «выражение».

Если мы можем выразить часть кода как значение, то это выражение, например:

может быть выражено в виде значения:

condition ? 1 : 2

не может быть выражено в виде значения:

if(condition){
 1
} else {
 2
}

Достаточно просто, но как это связано с неизменностью?

Значимая ценность

Ценность должна иметь цель, иначе было бы бессмысленно создавать ценность в первую очередь.

так какова цель значения?

Вспомните, что делает компьютер: принимает ввод, обрабатывает его и выводит результат.

Значение имеет смысл только в том случае, если мы его читаем (превращаем вывод в другой ввод)

Превратите выражение в ввод:

Назначив его переменной, предпочтительно неизменяемой переменной:

const input = condition ? 1 : 2
doSomething(input)

Прочитав выражение напрямую:

doSomething(condition ? 1 : 2)

Превратите код без выражения в ввод:

По изменяемой переменной:

let input = null
if(condition){
 input = 1
} else {
 input = 2
}
doSomething(input)

Это плохо, потому что нам нужно полагаться на изменяемую переменную, чтобы выйти из области действия `if`

С помощью `var` или глобальной переменной:

if(condition){
 var input = 1
} else {
 input = 2
}
doSomething(input)
if(condition){
    input = 1
} else {
    input = 2
}

doSomething(input)

Думаю, мне не нужно объяснять, почему это ужасные идеи.

Запустив побочный эффект в области действия `if`:

if(condition){
  doSomething(1)
} else {
  doSomething(2)
}

`doSomething` — это побочный эффект, он должен быть одним из них. Потому что, в конце концов, это либо отправка данных куда-то еще, либо сохранение данных в базе данных, либо отображение данных на экране (становится входом для нашего мозга).

Если нет, то `doSomething` бессмысленно, наше утверждение `if` бессмысленно.

Мы связали `doSomething` с оператором `if`. Это означает, что невозможно протестировать оператор if без тестирования побочного эффекта doSomething.

Область `{}`

Обратите внимание, что области видимости `{}` из таких операторов, как `if`, `try catch`, `switch`, `for`, `while`… являются причиной, по которой нам нужна изменяемая переменная для доступа к значениям, сгенерированным в областях видимости.

Scope ограничивает доступ к переменным, это хороший механизм, но нам нужно пожертвовать неизменяемостью.

Чем больше мы вкладываем нашу область видимости, тем больше мы полагаемся на изменяемые переменные.

Что означает, что вложенность областей видимости влияет не только на читабельность нашего кода, но и на его корректность.

Плохая удобочитаемость приводит к ошибкам, плохая удобочитаемость и изменяемые переменные приводят к еще большему количеству ошибок!

Так должны ли мы отказаться от масштаба?

Нет, на самом деле мы можем создать неизменяемую переменную из областей видимости.

Функция, Спаситель Объема

Не только область действия функции может ограничивать доступ к переменным, но и позволяет нам считывать значение из области видимости без создания какой-либо изменяемой переменной.

Из того же примера:

const getResult = (condition) => {
 if(condition){
   return 1
 } else {
   return 2
 }
}
const input = getResult(false)
doSomething(input)

проверка списка:
✅ область действия
✅ неизменяемость
✅ чистая функция

Однако это не работает с областью действия цикла, цикл всегда заканчивается изменяемой переменной или побочным эффектом.

const getSumUpToN= (n) => {
 let sum = 0 // mutation
 for(let i = 1; i <= n ;i ++){ // mutation
 sum += i
 }
 return sum 
}
const input = getSumUpToN(5)

Рекурсия

Вот где рекурсия входит в картину. Мы можем использовать рекурсию, чтобы сохранить неизменность.

Функция — это ключ, делающий возможной рекурсию.

const getSumUpToN= (n) => {
 return n <= 0 ? 0 : n + getSumUpToN(n — 1)
}
const input = getSumUpToN(5)
doSomething(input)

Теперь мы понимаем, почему функциональное программирование использует рекурсию по циклу, потому что **рекурсия может выражать цикл как значение и избегать изменяемых переменных**

Мы также понимаем, почему функциональное программирование является функциональным, потому что функция является выражением и делает возможной рекурсию.

Краткое содержание

1. Основой функционального программирования является выражение (значение)
2. Инструментом является функция. Функция может превращать все в выражения, возвращая значения из областей видимости и используя рекурсию по циклу.
3. Результат представляет собой сглаженный код, в котором нет необходимости ограничивать доступ к переменным
4. Основными преимуществами являются неизменяемость и чистое функционирование (улучшение корректности кода), дополнительное преимущество — декларативность (улучшение читабельности).

Последние мысли

Я считаю, что смысл функционального программирования в том, чтобы устранить потребность в области видимости.

Нам нужна область действия, чтобы ограничить доступ к переменным, чтобы код был безопаснее, но в то же время область действия либо вводит изменяемую переменную, либо связывает побочные эффекты с операторами.

Используя функцию, мы можем поддерживать неизменность и отделять побочные эффекты, ограничивая при этом доступность переменных.

Я надеюсь, что эта статья даст вам четкое представление о том, как писать базовый функциональный код и зачем вам его писать.

Будьте бдительны, товарищи программисты!