Що таке чиста функція в JavaScript?

Чисті функції - це атомні будівельні блоки у функціональному програмуванні. Їх обожнюють за простоту та перевіреність.

Ця публікація охоплює короткий контрольний список, щоб визначити, чи функція чиста чи ні.

Контрольний список

Функція повинна пройти два тести, щоб вважатись "чистою":

  1. Однакові входи завжди повертають однакові виходи
  2. Відсутність побічних ефектів

Давайте збільшимо кожну з них.

1. Той самий вхід => Той самий вихід

Порівняйте це:

const add = (x, y) => x + y; add(2, 4); // 6 

До цього:

let x = 2; const add = (y) => { x += y; }; add(4); // x === 6 (the first time) 

Чисті функції = послідовні результати

Перший приклад повертає значення на основі заданих параметрів, незалежно від того, де / коли ви його викликаєте.

Якщо ви пройдете 2і 4, ви завжди отримаєте 6.

Ніщо інше не впливає на результат.

Нечисті функції = суперечливі результати

Другий приклад нічого не повертає. Він покладається на спільний стан для виконання своєї роботи, збільшуючи змінну за межами власного обсягу.

Ця модель є кошмаром палива розробника.

Спільний стан вводить часову залежність. Ви отримуєте різні результати залежно від того, коли ви викликали функцію. Перший раз призводить до 6, наступний раз - 10і так далі.

Про яку версію легше міркувати?

Хто рідше розмножує помилок, які трапляються лише за певних умов?

Хто з більших шансів досягти успіху в багатопотоковому середовищі, де часові залежності можуть зламати систему?

Безумовно, перший.

2. Відсутність побічних ефектів

Цей тест сам по собі є контрольним списком. Кілька прикладів побічних ефектів

  1. Мутація вводу
  2. console.log
  3. HTTP-дзвінки (AJAX / отримання)
  4. Зміна файлової системи (fs)
  5. Запит DOM

В основному будь-яка робота, яку виконує функція, не пов'язана з обчисленням кінцевого результату.

Ось нечиста функція з побічним ефектом.

Не так вже й погано

const impureDouble = (x) => { console.log('doubling', x); return x * 2; }; const result = impureDouble(4); console.log({ result }); 

console.logтут є побічним ефектом, але, по всій практичності, це не зашкодить нам. Ми все одно отримаємо однакові результати, отримавши ті самі дані.

Це , однак, може викликати проблеми.

“Нечисто” Зміна об’єкта

const impureAssoc = (key, value, object) => { object[key] = value; }; const person = { name: 'Bobo' }; const result = impureAssoc('shoeSize', 400, person); console.log({ person, result }); 

Змінна person,, була назавжди змінена, оскільки наша функція представила оператор присвоєння.

Спільний стан означає impureAssocвплив вже не повністю очевидним. Розуміння його впливу на систему тепер включає відстеження кожної змінної, яку вона коли-небудь торкнулася, та знання їх історії.

Спільний стан = залежності від часу.

Ми можемо очистити impureAssoc, просто повернувши новий предмет із нашими бажаними властивостями.

Очищення

const pureAssoc = (key, value, object) => ({ ...object, [key]: value }); const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

Тепер pureAssocповертається перевіряється результат, і ми ніколи не будемо хвилюватися, якщо він тихо щось змінив в іншому місці.

Ви навіть можете зробити наступне і залишатися чистим:

Ще один чистий шлях

const pureAssoc = (key, value, object) => { const newObject = { ...object }; newObject[key] = value; return newObject; }; const person = { name: 'Bobo' }; const result = pureAssoc('shoeSize', 400, person); console.log({ person, result }); 

Мутація введеного тексту може бути небезпечною, але мутація її копії не становить проблем. Наш кінцевий результат все ще перевіряється, передбачувана функція, яка працює незалежно від того, де / коли ви її викликаєте.

Мутація обмежена цим невеликим обсягом, і ви все одно повертаєте значення.

Об'єкти глибокого клонування

Вгору! За допомогою оператора поширення ...створюється неглибока копія об’єкта. Неглибокі копії не захищені від вкладених мутацій.

Дякую Родріго Фернандес Діасу за те, що звернув на це мою увагу!

Небезпечна вкладена мутація

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const shallowPersonClone = { ...person }; shallowPersonClone.address.number = 456; console.log({ person, shallowPersonClone }); 

Обидва personі shallowPersonCloneмутували, тому що їхні діти мають однакові посилання!

Безпечна вкладена мутація

To safely mutate nested properties, we need a deep clone.

const person = { name: 'Bobo', address: { street: 'Main Street', number: 123 } }; const deepPersonClone = JSON.parse(JSON.stringify(person)); deepPersonClone.address.number = 456; console.log({ person, deepPersonClone }); 

Now you’re guaranteed safety because they’re truly two separate entities!

Summary

  • A function’s pure if it’s free from side-effects and returns the same output, given the same input.
  • Side-effects include: mutating input, HTTP calls, writing to disk, printing to the screen.
  • You can safely clone, thenmutate, your input. Just leave the original one untouched.
  • Spread syntax ( syntax) is the easiest way to shallowly clone objects.
  • JSON.parse(JSON.stringify(object)) is the easiest way to deeply clone objects. Thanks again Rodrigo Fernández Díaz!

My Free Course

This tutorial was from my completely free course on Educative.io, Functional Programming Patterns With RamdaJS!

Please consider taking/sharing it if you enjoyed this content.

It’s full of lessons, graphics, exercises, and runnable code samples to teach you a basic functional programming style using RamdaJS.

Thanks for reading! Until next time.