Як ви називаєте функцію, коли той самий вхід завжди повертатиме один і той же вихід, але також має побічні ефекти?


43

Скажімо, у нас є нормальна чиста функція, така як

function add(a, b) {
  return a + b
}

А потім ми його змінюємо таким чином, щоб він мав побічну дію

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

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

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


91
"Не чиста функція".
Росс Паттерсон

2
@RossPatterson це те, що я думав також, але, запитуючи, я дізнався про референтну прозорість, тому я радий, що не дотримувався цього.
m0meni

9
Якщо writeToDatabaseце не вдалося, це може викликати виняток, завдяки чому ваша друга addфункція створює виняток, навіть якщо викликається тими ж аргументами, що раніше не виникало проблем ... більшість випадків виникнення побічних ефектів вводить подібні умови, пов’язані з помилками, які порушуються "чистота вводу-виводу".
Бакуріу

25
Те, що завжди дає однаковий вихід для даного входу, називається детермінованим .
njzk2

2
@ njzk2: Це правда, але це також без громадянства . З збереженням стану детермінованою функції може не дати той же вихід для кожного входу. Приклад: F(x)визначається для повернення, trueякщо він викликається тим же аргументом, що і попередній виклик. Зрозуміло, що з послідовністю {1,2,2} => {undefined, false, true}це детерміновано, але воно дає різні результати для F(2).
MSalters

Відповіді:


85

Я не впевнений у універсальних визначеннях чистоти, але з точки зору Haskell (мови, де програмісти прагнуть дбати про такі речі, як чистота та референтна прозорість), лише перша з ваших функцій є "чистою". Друга версія addне є чистою . Отже, відповідаючи на ваше запитання, я б назвав це "нечистим";)

Згідно з цим визначенням, чиста функція - це функція, яка:

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

З цим визначенням зрозуміло, що вашу другу функцію не можна вважати чистою, оскільки вона порушує правило 2. Тобто наступні дві програми НЕ еквівалентні:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

і

function g(a, b) {
    c = add(a, b);
    return c + c;
}

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

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


1
Хіба не "лише залежить від її вкладу" надлишковий з огляду на референтну прозорість? Що б означало, що RT є синонімом чистоти? (Я все більше
плутаюсь з

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

Хамф. Скажімо, у нас є така функція, яка є чистою, крім побічних ефектів, а також гарантовано мати таку поведінку, коли ваші два зразки є рівнозначними. (У мене справді вийшов цей випадок, тому це не гіпотетично.) Я думаю, що ми не зовсім готові.
Джошуа

2
Я б стверджував, що друга функція теж може порушити правило №1. Залежно від мови та методів роботи з помилками використовуваного API бази даних, функція може взагалі нічого не повернути, якщо база даних недоступна або записування db з якоїсь причини не вдається.
Зак Ліптон

1
З моменту згадки про Haskell: У Haskell додавання такого побічного ефекту вимагає зміни підпису функції . (подумайте про це, як надання оригінальної бази даних як додатковий вхід та отримання модифікованої бази даних як додаткове повернене значення функції). Насправді таким чином можна досить елегантно моделювати побічні ефекти в системі типів, просто сучасні основні мови недостатньо дбають про побічні ефекти та чистоту для цього.
ComicSansMS

19

Як ви називаєте функцію [для якої] той самий вхід завжди поверне той самий вихід, але також має побічні ефекти?

Така функція називається

детермінований

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

termwiki.com

Щодо штату:

Залежно від того, чиє визначення функції ви використовуєте, функція не має стану. Якщо ви прийшли з об'єктно-орієнтованого світу, пам’ятайте, що x.f(y)це метод. Як функція це виглядало б f(x,y). І якщо ви замикаєтесь із закритим лексичним діапазоном, пам’ятайте, що незмінний стан також може бути частиною вираження функцій. Тільки стан, що змінюється, може впливати на функції детермінованого характеру. Тож f (x) = x + 1 є детермінованим до тих пір, поки 1 не зміниться. Не має значення, де зберігається 1.

Ваші функції обидві детерміновані. Ваше перше - це також чиста функція. Ваша друга не є чистою.

Чиста функція

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

  2. Оцінка результату не спричиняє жодних семантично помітних побічних ефектів або результатів, таких як мутація змінних об'єктів або вихід на пристрої вводу / виводу.

wikipedia.org

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


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

@Frax Системна пам'ять може бути недоступною. ЦП може бути недоступним. Бути детермінованим не гарантує успіху. Це гарантує, що успішна поведінка передбачувана.
candied_orange

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

@candied_orange Я погодився б, якщо запис у БД залежав лише від входів. Але це Math.random(). Отже, ні, якщо ми не припустимо, що PRNG (замість фізичного RNG) і не врахуємо, що PRNG вказує частину вхідних даних (що це не так, посилання є жорстким кодом), це не детерміновано.
марстато

1
@candied_orange цитування детермінованих станів "алгоритм, поведінку якого можна повністю передбачити з вхідних даних". Письмо в IO, для мене, безумовно, поведінка, а не результат.
марстато

9

Якщо вам не байдужий побічний ефект, то він референтно прозорий. Звичайно, можливо, що вам не байдуже, але це робить хтось інший, тому застосовність терміна залежить від контексту.

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

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

Комбінації ідентифікованих функцій можуть або не можуть бути ідентичними в цілому.

* Використання idempotent в комп’ютерній науці відрізняється від математики, ніж, мабуть, пов'язане з неправильним використанням математичного терміна, який тоді був прийнятий, оскільки концепція корисна.


3
термін "референтно прозорий" більш чітко визначений, ніж "байдуже когось". навіть якщо ми нехтуємо питаннями вводу-виводу, такими як проблеми з підключенням, відсутніми рядками підключення, таймаутами тощо, все одно легко показати, що програма, яка заміняє, випаде (f x, f x)з let y = f x in (y, y)дискового простору - винятки вдвічі швидше, ви можете стверджувати, що це крайні справи, які вас не хвилюють, але з таким нечітким визначенням ми можемо також назвати new Random().Next()референтно прозорі, бо чорт, мені все одно, яке число я отримую.
сара

@kai: Залежно від контексту, ви можете ігнорувати побічні ефекти. З іншого боку, повернене значення функції, як випадкова, не є побічним ефектом: це її головний ефект.
Джорджіо

@Giorgio Random.Nextв .NET дійсно має побічні ефекти. Дуже так. Якщо ви можете Next, призначте її змінній, а потім зателефонуйте Nextще раз та призначте її іншій змінній, ймовірно, вони не будуть рівними. Чому? Тому що виклик Nextзмінює деякий прихований внутрішній стан в Randomоб’єкті. Це полярна протилежність референсної прозорості. Я не розумію Вашого твердження, що "основні ефекти" не можуть бути побічними ефектами. В імперативному коді частіше, ніж ні, головним ефектом є побічний ефект, оскільки імперативні програми мають за своєю суттю.
сара

3

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


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

2

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

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

Більш реалістичним прикладом може бути, якби ви намагалися налагодити цю функцію та додали оператори журналу. Технічно лісозаготівля - це побічний ефект. Чи роблять це колоди нечистими? Немає.


Ну, це залежить. Можливо, журнали роблять це нечистим, наприклад, якщо ви дбаєте про те, скільки разів та в який час у вашому журналі з'являється "INFO f () закликаний". Що ви часто робите :)
Андрес Ф.

8
-1 Журнали мають значення. На більшості платформ вихід будь-якого типу неявно синхронізує потік виконання. Поведінка вашої програми стає залежною від запису інших потоків, зовнішніх записів журналів, іноді - зчитування журналу, стану дескрипторів файлів. Це так само чисто, як відро бруду.
Василевс

@AndresF. Ну, напевно, ти не переймаєшся буквальною кількістю разів. Ви, мабуть, дбаєте лише про те, щоб він реєструвався стільки разів, скільки викликалася функція.
DeadMG

@Basilevs Поведінка функції зовсім не залежить від них. Якщо запис у журнал не вдається, ви просто продовжуєте роботу.
DeadMG

2
Справа в тому, чи ви вирішите визначити реєстратор як частину середовища виконання чи ні. Для іншого прикладу, чи є моя чиста функція чистою, якщо я приєднаю відладчик до процесу і встановлю на ньому точку розриву? З POV людини, яка використовує налагоджувач, явно функція має побічні ефекти, але зазвичай ми аналізуємо програму з умовою, що це "не рахується". Те саме може (але не потрібно) проходити для ведення журналів, що використовуються для налагодження, і я припускаю, саме тому слід приховує свою домішку. Критична місія журналу, наприклад, для аудиту, звичайно, є суттєвим побічним ефектом.
Стів Джессоп

1

Я б сказав, що найкраще запитати - це не те, як ми б це називали, а як би ми проаналізували такий фрагмент коду. І моє перше ключове питання в такому аналізі:

  • Чи побічний ефект залежить від аргументу функції чи результату від побічного ефекту?
    • Ні: "Ефективна функція" може бути перетворена на чисту функцію, ефективну дію та механізм їх поєднання.
    • Так: "Ефективна функція" - це функція, яка дає монодічний результат.

Це просто проілюструвати в Haskell (і це речення - лише половина жарту). Приклад випадку "ні" може бути таким:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

У цьому прикладі дія, яку ми вживаємо (друкуємо рядок "I'm doubling some number"), не впливає на взаємозв'язок між xрезультатом і результатом. Це означає, що ми можемо змінити його таким чином (використовуючи Applicativeклас та його *>оператор), що показує, що функція та ефект насправді є ортогональними:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

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

Приклад сортування "так", коли чисті та ефективні частини не є ортогональними:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Тепер, рядок, який ви друкуєте, залежить від значення x. Функція частина (помножити xна два), однак, не залежить від впливу на всіх, так що ми можемо по- , як і раніше враховувати це:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

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

Це одна з причин, що Haskell Monadтак широко використовує свій клас. Монади є (серед іншого) інструментом для проведення такого роду аналізу та рефакторингу.


-2

Функції, які покликані викликати побічні ефекти, часто називають ефективними . Приклад https://slpopejoy.github.io/posts/Effectful01.html


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

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.