Чистые функции
Транскрипт урока
Мы уже написали множество функций и большинство из них работает так: принимает какие-то аргументы, рассчитывает что-то, используя эти аргументы, возвращает ответ. Ваши функции, даже те, которые используют другие функции хороши тем, что они предсказуемы, стабильны. Например, функция вычисления площади, которую вы писали в самом начале: даете ей число и она возвращает ответ. Если ей дать то же число снова, вы получите тот же результат. На самом деле, сколько бы раз вы не повторяли ее вызов со вводом одинаковых значений, результат будет идентичный.

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

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

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

const surfaceAreaCalculator = (radius) => {
  return 4 * 3.14 * square(radius);
}
Вам наверное интересно, как еще может вести себя функция. Недетерминированная функция непредсказуема, ее результат зависит от чего-то еще.

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

Еще более простой пример — генератор случайных чисел. Обычно в любом языке программирования есть какой-нибудь встроенный способ генерации случайных чисел. В JavaScript он такой:

Math.random();      // 0.6822304980945362
Math.random();      // 0.34656303876811245
Math.random();      // 0.44983037125501646
Как видите, каждый раз, когда вы вызываете его с одинаковыми аргументами (в данном случае — никаких аргументов вообще), результат всегда новый. Эта функция недетерминированная, но в этом вся ее суть. Генератор случайных чисел должен давать вам разные числа по определению, даже если вызовы функции выглядят одинаково.

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

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

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

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

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

А ваш хороший друг — функция console.log — имеет: она выводит что-то на экран. Эта штука на экране — определенно что-то за рамками функции, это компьютер, мир в котором живет функция.

Или рассмотрим следующий код:

let a = 0;

const f = () => {
  a = a + 1;
  return true;
}

f();
Функция f меняет значение глобальной переменной a. Эта переменная с точки зрения функции, находится во внешнем мире, а функция f меняет ее. Поэтому у f есть побочные эффекты.

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

Хотя и f, и console.log имеют побочные эффекты, они детерминированные. f всегда возвращает true, а console.log всегда возвращает undefined. То, что возвращают функции, не имеет ничего общего с тем, как они влияют на внешний мир.

console.log("Hello!");      // prints "Hello!", but returns undefined
Не путайте вывод на экран и возврат. Вывод на экран — это просто действие, то, что выполняет функция console.log. Но она так же возвращает значение — всегда undefined.

Если f вернет значение a вместо true, то она, очевидно, недетерминированная функция с побочными эффектами:

let a = 0;

const f = () => {
  a = a + 1;
  return a;
}

f();
Вы никогда не можете точно знать, какое f вернет значение, пока не узнаете что-то еще. Это зависит от внешнего фактора, а именно от текущего значения a.

Чем меньше побочных эффектов имеет функция, тем лучше.

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

Все, что касается чистых функций кажется "проще": они просты для чтения, для отладки и поиска ошибок, для тестирования. Чистые функции в системе не зависят ни от чего по-определению, поэтому порядок, в котором они вызываются не имеет значения. Это значит, что их легко запустить, например, параллельно — одновременно — на разных процессорах или даже разных компьютерах.

Чистые функции живут вне времени. Само понятие времени, действий, протекающих в какой-то последовательности, не касается чистых функций. Время их не беспокоит. Когда вы смотрите на чистые функции, читаете код, делаете отладку, тестируете или просто используете их — вам не нужно думать о времени, о том, что случилось до и случится после. Это дает некоторую свободу, и все "простые" штуки, касающиеся чистых функций — последствия этого факта.

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


Это был заключительный урок курса "Введение в программирование". Вы освоили много фундаментального материала и научились писать код. Помните — чем лучше фундамент, тем выше потолок, именно поэтому мы в этом курсе не старались как можно скорее сделать сайт или игру для телефона — мы занимались изучением важных фундаментальных тем программирования.

Если вы смотрели видео, но не выполняли упражнения и тесты — то обязательно сделайте это. Курс рассчитан именно на это, а видео сами по себе — это только часть процесса. Есть большая разница между "кажется, понял" и "умею".

Что дальше?

На Хекслете есть целые программы обучения, называемые "профессиями". Курс, который вы только что закончили — первый шаг в этих программах. Дальше вас ждут множество более глубоких курсов, с множеством упражнений, тестов с дополнительными материалами и бонусными заданиями. Важная часть программы обучения — полноценные проекты, над которыми вы будете работать на своем компьютере, а наша поддержка будут помогать вам.

Я надеюсь, что программирование станет важной и интересной частью вашей жизни.
Выводы
Детерминированная функция всегда возвращает одинаковое значение при определенном вводе (аргументы).

const surfaceAreaCalculator = (radius) => {
  return 4 * 3.14 * square(radius);
}
surfaceAreaCalculator это детерминированная функция.

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

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

Другой пример — генератор случайных чисел:

Math.random();      // 0.6822304980945362
Math.random();      // 0.34656303876811245
Math.random();      // 0.44983037125501646
Побочные эффекты: то, как функция меняет внешний мир.

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

Функция console.log имеет побочный эффект: она что-то выводит на экран.

Другой пример:

let a = 0;

const f = () => {
  a = a + 1;
  return true;
}

f();
Функция f меняет значение глобальной переменной a. Эта переменная, с точки зрения функции, находится во внешнем мире, а функция f это меняет. Поэтому f имеет побочный эффект.

И f, и console.log имеют побочные эффекты, но они детерминированные. f всегда возвращает true, а console.log всегда возвращает undefined.

То, что функции возвращают, не имеет ничего общего с тем, как они влияют на внешний мир.

console.log("Hello!");      // prints "Hello!", but returns undefined
Чем меньше побочных эффектов имеет функция, тем лучше.
Когда функция детерминированная и не имеет побочных эффектов, мы называем ее "чистой" функцией. Чистые функции:

  • проще читать
  • проще отлаживать
  • проще тестировать
  • не зависят от порядка, в котором они вызываются
  • просто запустить параллельно (одновременно)

Чистые функции независимы от времени. Недетерминизм и побочные эффекты добавляют понятие времени. Если функция зависит от чего-то, что может случиться, а может не случиться и меняет что-то за пределами своих границ, то она неожиданно становится зависимой от времени.
Дополнительные материалы
Тесты
Пройти тест
Может ли программа содержать несколько функций? (доверьтесь своим инстинктам ;)
Верно!
Неверно
Дальше
Проверить
Завершить тест
Могут ли функции возвращать строки (тексты), или они могут возвращать исключительно числа?
Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const height = findHeight();

Какое значение будет "записано" в константу height после выполнения?

Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const someFunction = (x) => {
  return 10 * 42;
};


const y = someFunction(9281);

Что будет "сохранено" в константе y после выполнения?
Неверно
Верно!
Неверно
Дальше
Проверить
Завершить тест
Каким будет результат вызова функции?

 const firstNum = 10;
 const secondNum = 5;


 const sum = (z, g) => z + g;


 sum(firstNum, secondNum);
Верно!
Неверно! Попробуйте еще раз
Неверно! Попробуйте еще раз
Дальше
Проверить
Завершить тест
Дана такая функция:

const sum = (a, b, c) => {
  return a + b + c;
};

Можно ли в этой ситуации использовать сокращенный синтаксис записи функции?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Пройти еще раз
Пройти еще раз
Пройти еще раз
Пройти еще раз
Упражнение
Реализуйте и экспортируйте по умолчанию функцию, которая работает следующим образом:

Дано неотрицательное целое число num. Складывать все входящие в него цифры до тех пор, пока не останется одна цифра.
Для числа 38 процесс будет выглядеть так:

  1. 3+ 8 = 11
  2. 1 + 1 = 2
Результат: 2


solution('hello, world!'); // Hello, World!
Примеры

addDigits(10); // 1
addDigits(19); // 1
addDigits(38); // 2
addDigits(1259); // 8
Подсказки

  • Вычисление длины строки: length(str).
  • Перевод строки/буквы в верхний регистр: toUpperCase(str).