Чисті функції: Чи не означає "відсутність побічних ефектів" "Завжди однаковий результат, враховуючи однакові дані"?


84

Дві умови, що визначають функцію, pureтакі:

  1. Жодних побічних ефектів (тобто дозволяються лише зміни до місцевого масштабу)
  2. Завжди повертайте однакові результати, давши однакові дані

Якщо перша умова завжди відповідає дійсності, чи бували випадки, коли друга умова не відповідає дійсності?

Тобто це дійсно потрібно лише з першою умовою?


3
Ваше приміщення не вказано. "Введення" занадто широке. Можна вважати, що функції мають два типи введення. Їх аргументи і "екологічні" / "контекстуальні". Функцію, яка повертає системний час, можна вважати чистою (навіть якщо вона не є загальною), якщо ви не розрізняєте ці два типи введення.
Олександр -

4
@Alexander: У контексті "чистої функції" під "входом" зазвичай розуміють параметри / аргументи, які передаються явно (за будь-яким механізмом, який використовує мова програмування). Це частина визначення "чистої функції". Але ви маєте рацію, важливо пам’ятати про визначення.
sleske

3
Тривіальний приклад: повернення значення глобальної змінної. Жодних побічних ефектів (глобальне лише коли-небудь читається!), Але все одно потенційно різні результати кожен раз. (Якщо вам не подобаються глобальні символи, поверніть адресу локальної змінної, яка залежить від стеку викликів під час виконання).
Пітер -

2
Вам потрібно розширити визначення "побічних ефектів"; ви говорите, що чистий метод не викликає побічних ефектів, але потрібно також зазначити, що чистий метод не споживає побічних ефектів, що виникають в інших місцях.
Ерік Ліпперт

2
@sleske Можливо, загальновідомий, але відсутність такої різниці є точною причиною плутанини ОП.
Олександр -

Відповіді:


114

Ось кілька контраприкладів, які не змінюють зовнішній обсяг, але все ще вважаються нечистими:

  • function a() { return Date.now(); }
  • function b() { return window.globalMutableVar; }
  • function c() { return document.getElementById("myInput").value; }
  • function d() { return Math.random(); } (що, правда, змінює PRNG, але не вважається спостережуваним)

Доступу до непостійних нелокальних змінних достатньо, щоб мати можливість порушити другу умову.

Я завжди вважаю дві умови чистоти додатковими:

  • оцінка результату не повинна впливати на побічний стан
  • на результат оцінки не повинно впливати побічний стан

Термін побічний ефект стосується лише першої, функції, що модифікує нелокальний стан. Однак іноді операції читання також розглядаються як побічні ефекти: коли вони є операціями і включають також запис, навіть якщо їх основною метою є доступ до значення. Прикладами цього є генерування псевдовипадкового числа, яке змінює внутрішній стан генератора, зчитування з вхідного потоку, що зміщує позицію зчитування, або зчитування з зовнішнього датчика, що включає команду "зробити вимірювання".


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

17
Якщо prompt("you choose")немає побічних ефектів, нам слід зробити крок назад і уточнити значення побічних ефектів.
Holger

1
@Magnus Так, саме це означає ефект . Я спробую пояснити в моїй обороні , як добре, я не очікував такої великої уваги і хочуть , щоб відповісти гідно десятків голосів :-)
Берги

2
Ну що ж, все, що ви знаєте, Math.random () повертає тепловий діод. Насправді не вказано використовувати поганий RNG.
Джошуа

1
З двох умов я чув першу, яка називається "ефектами", а друга - "коефіцієнтами". І те, і інше є «побічними ефектами» та нечистими. f (коефіцієнти, вхід) -> ефекти, вихід Коефекти - це вхідні дані, що походять від змін у ширшому середовищі, ефекти - результати, що змінюють ширше середовище. Наприклад, Elm і Clojurescrips переформатують роботу з цією моделлю.

30

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

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

Так, наприклад, якби C printfбули відносно прозорими, ці дві програми мали б однакове значення:

printf("Hello");

і

5;

і всі наступні програми повинні мати однакове значення:

5 + 5;

printf("Hello") + 5;

printf("Hello") + printf("Hello");

Оскільки printfповертає кількість записаних символів, в даному випадку 5.

Це стає ще більш очевидним з voidфункціями. Якщо у мене є функція void foo, тоді

foo(bar, baz, quux);

має бути таким же, як

;

Тобто, оскільки fooнічого не повертає, я мав би змогу замінити це нічим, не змінюючи значення програми.

Тоді очевидно, що ні те, printfні fooінше не є посилально прозорими, а отже, жодне з них не є чистим. Насправді voidфункція ніколи не може бути референтно прозорою, якщо вона не є операцією.

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

func fib(n):
    return memo[n] if memo.has_key?(n)
    return 1 if n <= 1
    return memo[n] = fib(n-1) + fib(n-2)

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

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


2
Ця домішка впливає на всю програму, коли у вас є паралельність.
R .. GitHub СТОП ДОПОМОГАЙ

@R .. Чи можете ви придумати спосіб, за допомогою якого паралельність може зробити описану Фібоначчі функцією зовнішньої нечистоти? Я не можу. Запис на memo[n]це ідемпотентно, і якщо його не читати, це просто витрачає цикли процесора.
Brilliand

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

@R .. Неважко уявити версію, яка відповідає паралельності.
user253751

1
@Brilliand Наприклад, memo[n] = ...може спочатку створити словниковий запис, а потім зберегти в ньому значення. Це залишає вікно, протягом якого інший потік може бачити неініціалізований запис.
user253751

12

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

Дозвольте навести вам приклад, припустимо, у вас є функція для додавання тієї, яка також реєструється на консолі:

function addOneAndLog(x) {
  console.log(x);
  return x + 1;
}

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

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

Припустимо, у нас є функція, яка просто додає:

function addOne(x) {
  return x + 1;
}

Ми можемо замінити addOne(5)з 6ніде в нашій програмі і нічого не зміниться.

На відміну від цього, ми не можемо замінити addOneAndLog(x)значення 6будь-де в нашій програмі, не змінивши поведінку, оскільки перший вираз призводить до того, що щось записується на консоль, тоді як друге - ні.

Ми розглядаємо будь-яку цю додаткову поведінку, яка addOneAndLog(x)виконує окрім повернення результату, як побічний ефект .


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

@sleske ви помиляєтесь. Я дав чіткі визначення термінів чистий та побічний ефект. В рамках цих обмежень немає нічого, що функція, яка не має побічних ефектів, крім того, повертає однакові результати для даного входу. Однак я навів приклади, коли друга умова може бути виконана без першої. Основоположною концепцією розуміння поняття чистоти є референційна прозорість.
TheInnerLight

Невелика друкарська помилка: функція, яка не має побічних ефектів, може нічого зробити, крім повернення одного і того ж виводу для даного вводу.
TheInnerLight

А як щодо повернення поточного часу? Це не має побічних ефектів, але повертає інший вихід для одного входу. Або загальніше, будь-яка функція, де результат залежить не тільки від вхідних параметрів, але й від (змінної) глобальної змінної.
sleske

2
Здається, ви використовуєте інше визначення поняття "побічний ефект", ніж загальновживане. Побічний ефект зазвичай визначається як "спостережуваний ефект, крім повернення значення", або "спостережувана зміна стану" - див., Наприклад, Вікіпедія , цей пост на softwareengineering.SE . Ви абсолютно праві, що Date.now()не є чистим / довідково прозорим, але не тому, що він має побічні ефекти, а тому, що його результат залежить не лише від його вводу.
sleske

7

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

У будь-якому випадку я все, що я можу придумати.


3
На мою думку, ці "випадковість поза системою" є формою побічного ефекту. Функції з цією поведінкою не є "чистими".
Джозеф М. Діон,

2

Проблема з визначеннями FP полягає в тому, що вони дуже штучні. Кожне оцінювання / розрахунок має побічні ефекти на оцінювача. Це теоретично правда. Заперечення цього свідчить лише про те, що апологети FP ігнорують філософію та логіку: "оцінка" означає зміну стану деякого інтелектуального середовища (машини, мозку тощо). Така природа процесу оцінювання. Без змін - без "чисел". Ефект може бути дуже помітним: нагрівання центрального процесора або його збій, вимкнення материнської плати у разі перегріву тощо.

Коли ви говорите про референтну прозорість, ви повинні розуміти, що інформація про таку прозорість доступна людині як творцеві цілої системи та власнику семантичної інформації, і може бути недоступною для компілятора. Наприклад, функція може прочитати якийсь зовнішній ресурс, і вона матиме в підписі монаду вводу-виводу, але вона весь час повертатиме одне і те ж значення (наприклад, результат current_year > 0). Компілятор не знає, що функція повертатиме однаковий результат, тому функція є нечистою, але має відносно прозору властивість і може бути замінена Trueконстантою.

Отже, щоб уникнути такої неточності, слід розрізняти математичні функції та "функції" в мовах програмування. Функції в Haskell завжди нечисті, і визначення чистоти, пов'язане з ними, завжди дуже умовно: вони працюють на реальному обладнанні з реальними побічними ефектами та фізичними властивостями, що неправильно для математичних функцій. Це означає, що приклад із функцією "printf" абсолютно неправильний.

Але не всі математичні функції теж чисті: кожна функція, яка має параметр t(час), може бути нечистою: tзберігає всі ефекти та стохастичний характер функції: у загальному випадку у вас є вхідний сигнал і ви не маєте уявлення про фактичні значення, це може бути навіть шумом.


2

Якщо перша умова завжди відповідає дійсності, чи бували випадки, коли друга умова не відповідає дійсності?

Так

Розглянемо простий фрагмент коду нижче

public int Sum(int a, int b) {
    Random rnd = new Random();
    return rnd.Next(1, 10);
}

Цей код поверне випадковий вивід для того самого набору входів, проте він не має жодних побічних ефектів.

Загальний ефект обох точок №1 та №2, про які ви згадали, поєднані разом, означає: У будь-який момент часу, якщо функція Sumз однаковим i / p замінюється результатом у програмі, загальне значення програми не змінюється . Це не що інше, як референційна прозорість .


Але в цьому випадку перша умова не перевіряється: запис на консоль вважається побічним ефектом, оскільки це змінює стан самої машини.
Права нога

@Rightleg thx за вказівку. Якось я неправильно зрозумів OP зовсім по-іншому. виправлена ​​відповідь.
rahulaga_dev

2
Чи це не змінює стан генератора випадкових випадків?
Ерік Думініл

1
Генерування випадкового числа саме по собі є побічним ефектом, якщо стан генератора випадкових чисел не
подано

1
rndне уникне функції, тому той факт, що її стан змінюється, не має значення для чистоти функції, але той факт, що Randomконструктор використовує поточний час як початкове значення, означає, що є "входи", крім aі b.
Снефтель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.