Что такое чистая функция
Перейти к содержимому

Что такое чистая функция

  • автор:

Чистые функции — JS: Функции

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

Детерминированность

Встроенная в JavaScript функция Math.random() возвращает случайное число от 0 до 1:

Math.random(); // 0.9337432365797949 Math.random(); // 0.5550694016887598 

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

Например, недетерминированными являются функции, оперирующие системным временем. Так, функция Date.now() каждый раз возвращает новое значение:

// Возвращает текущее время в миллисекундах Date.now(); // 1571909874844 Date.now(); // 1571909876648 

А вот пример с аргументами. Представьте функцию getAge() , которая принимает на вход год рождения и возвращает возраст:

getAge(2000); // ? 

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

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

Интересно что, например, функция console.log() — детерминированная. Дело в том, что она всегда возвращает одно и то же значение для любых входных данных. Это значение undefined , а не то, что печатается на экран, как можно было бы подумать. Печать на экран — побочный эффект, о нём мы поговорим чуть позже.

console.log('Hexlet – Big Bang'); 

Вызов console.log(‘Hexlet — Big Bang’) выполнил два действия:

  • Вывел сообщение Hexlet — Big Bang в терминал (или консоль браузера, в зависимости от среды выполнения)
  • Вернул значение undefined . Какое сообщение бы мы ни печатали, возвращаемое значение всегда будет одно — undefined

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

const getCurrentShell = () => process.env.SHELL; getCurrentShell(); // /bin/bash 

Функция getCurrentShell() обращается к переменной окружения SHELL . Но в разные моменты времени и в разных окружениях значение этой переменной может быть различным.

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

Понятие «Детерминированность» не ограничивается программированием или математикой. Сквозь него можно рассматривать практически любой процесс. Например, подбрасывание монетки — недетерминированный процесс, его результат случаен.

Побочные эффекты (side effects)

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

const someFunction = () =>  // Функция fetch выполняет HTTP-запрос // HTTP-запрос — это побочный эффект fetch('https://ru.hexlet.io/courses'); >; 

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

const someFunction = (obj) =>  // Какая-то логика // Побочный эффект. Изменение входного аргумента. obj.key = 'value'; >; 

А вот вычисления (логика), напротив, не содержат побочных эффектов. Например, функция, суммирующая два переданных аргументами числа.

const sum = (num1, num2) => num1 + num2; 

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

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

console.log(sum(4, 11)); // => 15 

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

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

  1. Читает файл в самом начале работы программы.
  2. Записывает результат работы программы в новый файл.

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

Инкремент и декремент — единственные базовые арифметические операции в JS, которые обладают побочными эффектами (изменяют само значение в переменной). Именно поэтому с ними сложно работать в составных выражениях. Они могут приводить к таким сложноотлавливаемым ошибкам, что во многих языках вообще отказались от их введения (в Ruby и Python их нет). В JS стандарты кодирования предписывают их не использовать.

Чистые функции

Идеальная функция с точки зрения удобства работы с ней называется чистой (pure). Чистая функция — это детерминированная функция, которая не производит побочных эффектов. Такая функция зависит только от своих входных аргументов и всегда ведёт себя предсказуемо.

Чистые функции обладают рядом ключевых достоинств:

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

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

pure function (чистая функция)

Чистота функции – это требование к некоторым частям чистемы в Реакте. Она требуется к компонентам, редьюсерам и селекторам.

Характеристики чистых функций

  • Имьютабельность (immutability –”неизменность”) – чистая функция не должна мутировать (менять) то, что в нее пришло. Например, пришли пропсы или другие параметры, то функция не должна их мутировать, иначе это будет side effect (“побочный эффект”). Проще говоря, функция должна пришедшие параметры обработать и вернуть что-то, не меняя самого “оригинала” пришедших параметров.
    К примеру, некоторые методы массивов меняют оригинал (splice, reverse и др.), а другие (slice) – создают новый массив. То значит первые мы не можем применять к оригиналу.
    Редьюсеры – это тоже чистые функции. На примере них видно, что если нам нужно что-то все-таки изменить, то мы для этого копию объекта.
  • Читсая функция обязательно имеет return, чтобы что-то возвращать. В реакт это обычно jsx.
  • Чистые функции не должны иметь никаких side effects. К “побочным эффектам” относится то же изменение значений в глобальном мире, асинхронные запросы (это можно делать в методах жизненного цикла или внутри функции useEffect). По этой причине нельзя делать ajax-запросы в редьюсерах, но можно делать b[ в санках (они не чистые функции).
  • Идемпотентность (Idempotence) – свойство возвращать на выходе всегда тот же самый результат при получении одних и тех же данных. Это свойство касается также и запросов (GET-запросы – идемпонетнны, а POST – нет).

2 ответа к «pure function (чистая функция)»

Функция является детерминированной, если для одного и того же набора входных значений она возвращает одинаковый результат. (с) Wiki “Идемпотентность (Idempotence) – свойство возвращать на выходе всегда тот же самый результат при получении одних и тех же данных” Всегда возникал вопрос, в чем разница ?)

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

Что такое чистая функция

Все функции надо стараться делать чистыми: такими, чтобы их результат зависел только от входных параметров и не имели side-effects: не могли сломать другие функции.

Простой чеклист чистой функции:

  • не пишет в файловую систему;
  • не пишет в stdout, stderr (print);
  • не меняет глобальные переменные;
  • не меняет аргументы, поданные на вход функции (edited);
  • не меняет ход выполнения программы (не используют exit() , например);
  • не использует внутри себя грязные функции.

Вот некоторые плюсы чистых функций

  • безопасность. Потому что на результат работы функции ничего не влияет. В том числе, такие настройки системы как кодировка stdout (print).
  • предсказуемость. В каком бы окружении не была запущена функция, результат будет один. Можно спокойно использовать и не бояться ошибки.
  • тестируемость. Если результат зависит только от параметров, то тесты пишутся на счёт раз. Это очень важно.

Если у тебя возник вопрос “Так а как же вообще мне выводить сообщение в консоль?”. То ответ прост и сложен одновременно. Размещай ввод/вывод внутри конструкции if __name__ == ‘__main__’ , либо внутри специальных функций вызываемых оттуда. А все остальные функции сохраняй чистыми. Если обработать входные данные сложно — возвращай None или выкидывай исключение с кодом/описанием ошибки. Пусть с этим разбирается вызывающая программа/функция.

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

Нарушение этого негласного соглашения вызывает бурную реакцию WTF. Если без “грязной” функции не обойтись — используй в названии слова print, write, save и пр. слова ассоциирующиеся с операциями ввода/вывода. И никогда не мешай в одной функции ввод/вывод с нетривиальной обработкой данных, всегда разноси.

Почитать на тему можно вот что:

  • статья “Чистые фунции” на Хабре
  • статья “Чистые и детерминированные функции” на Хабре
  • про чистые функции на Википедии

Чистота – залог здоровья

Попробуйте бесплатные уроки по Python

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

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Чистые функции — Введение в программирование

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

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

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

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

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 

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

Когда функция детерминированная и не имеет побочных эффектов, мы называем ее «чистой» функцией. Чистые функции:

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

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

Дополнительные материалы
  1. Master the JavaScript Interview: What is a Pure Function?
  2. Pure function / Wikipedia
  3. Pure Functions

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

  • Статья «Как учиться и справляться с негативными мыслями»
  • Статья «Ловушки обучения»
  • Статья «Сложные простые задачи по программированию»
  • Урок «Как эффективно учиться на Хекслете»
  • Вебинар « Как самостоятельно учиться »

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *