Чи сама пам'ятна чиста функція вважається чистою?


47

Скажімо, fn(x)це чиста функція, яка робить щось дороге, як повернення списку основних факторів x.

Скажімо, ми робимо запам’ятовувану версію тієї ж функції, що називається memoizedFn(x). Він завжди повертає той самий результат для заданого вводу, але він підтримує приватний кеш попередніх результатів для підвищення продуктивності.

Формально кажучи, memoizedFn(x)вважається чистим?

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


24
Можливо, це не чисто для пуристів, але "досить чисто" для прагматичних людей ;-)
Док Браун,

2
@DocBrown Я згоден, просто цікаво, чи існує більш формальний термін для "достатньо чистого"
callum

13
Виконання чистої функції, швидше за все, змінить кеш інструкцій процесора, передбачувачів гілок тощо. Але це, мабуть, "достатньо чисто" і для пуристів - або ви можете взагалі забути про чисті функції.
gnasher729

10
@callum Ні, офіційного визначення поняття "достатньо чистого" немає. Сперечаючись про чистоту та смислову еквівалентність двох "референтно прозорих" викликів, ви завжди повинні точно вказати, яку семантику ви збираєтеся застосувати. За деякого низького рівня деталізації щодо впровадження, він завжди буде руйнуватися та матимуть різні ефекти пам'яті або терміни пам'яті. Ось чому ви повинні бути прагматичними: який рівень деталізації корисний для міркувань щодо вашого коду?
Бергі

3
Тоді заради прагматизму я б сказав, що чистота залежить від того, чи вважаєте ви час обчислення частиною результату. funcx(){sleep(cached_time--); return 0;}щоразу повертає один і той же вал, але виступатиме по-різному
Марс

Відповіді:


41

Так. Запам'ятована версія чистої функції також є чистою функцією.

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

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


19

Вікіпедія визначає "чисту функцію" як функцію, яка має такі властивості:

  • Його повернене значення є однаковим для тих же аргументів (відсутність змін з локальними статичними змінними, нелокальними змінними, змінними аргументами, що змінюються, або потоками вводу з пристроїв вводу / виводу).

  • Його оцінка не має побічних ефектів (відсутність мутації локальних статичних змінних, нелокальних змінних, змінних опорних аргументів або потоків вводу / виводу).

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

Функціонально чисті мови, такі як Haskell, регулярно використовують запам'ятовування для прискорення функції, кешуючи її раніше обчислені результати.


16
Я можу щось пропустити, але як ви збираєтеся зберігати кеш без побічних ефектів?
val

1
Утримуючи його всередині функції.
Роберт Харві

4
"Не мутація локальної статичної змінної", схоже, також виключає локальні змінні, стійкі між дзвінками.
val

3
Це насправді не відповідає на питання, навіть якщо вам здається, що це означає, що це так.
Марс

6
@val Ви маєте рацію: цей стан потрібно трохи послабити. Чисто функціональна пам'ять, на яку він посилається, не має видимих мутацій будь-яких статичних даних. Що трапляється, то результат обчислюється і запам'ятовується при першому виклику функції і повертає те саме значення, коли воно викликається. Багато мов мають для цього ідіому: static constлокальна змінна в C ++ (але не C) або ліниво оцінена структура даних у Haskell. Вам потрібна ще одна умова: ініціалізація повинна бути безпечною для потоків.
Девіслор

7

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

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

Одним із прикладів комп'ютерного вченого, що використовує термін "чисто функціональний" таким чином, є публікація в блозі Conal Elliott про автоматичне запам'ятовування:

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

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

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

Деякі імперативні мови мають подібну ідіому. Наприклад, static constзмінна в C ++ ініціалізується лише один раз, перш ніж використовувати її значення, і ніколи не мутує.


3

Це залежить від того, як ви це зробите.

Зазвичай люди хочуть запам'ятовувати, мутуючи якийсь словник кеша. У цьому є всі проблеми, пов’язані з нечистою мутацією, наприклад, турбуватися про одночасність, турбуватися про те, що кеш стає занадто великим тощо.

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

У наданому посиланням Робертом Харві застосовується лінива оцінка, щоб уникнути побічних ефектів.

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

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

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


Я не думаю, що будь-яке більш чисте рішення не вирішує вищезазначені проблеми: Хоча ви втрачаєте будь-які турботи про одночасність, ви також втрачаєте будь-який шанс на два одночасно розпочаті дзвінки, як collatz(100)і collatz(200)співпрацювати. І IIUIC, проблема з надмірними розмірами кешу залишається (хоча у Haskell можуть бути приємні прийоми для цього?).
maaartinus

Примітка: IOчисто. Всі нечисті методи на IOі Кішки названі unsafe. Async.memoizeтакож чистий, тому нам не доведеться погоджуватися на "досить чистий" :)
Самуїл

2

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

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


0

Ви можете здійснити запам'ятовування без побічних ефектів, використовуючи стан монади .

[Державна монада] - це в основному функція S => (S, A), де S - тип, який представляє ваш стан, а A - результат, який виробляє функція - Cats State .

У вашому випадку стан буде пам'ятним значенням або нічого (тобто Haskell Maybeабо Scala Option[A]). Якщо запам'ятоване значення присутнє, воно повертається як A, інакше Aобчислюється і повертається як перехідний стан, так і результат.

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