Хоча це іноді виражається таким чином, функціональне програмування¹ не перешкоджає обчислювачам. Що це робить, це змусити програміста зробити явний стан.
Наприклад, візьмемо основну структуру якоїсь програми, використовуючи імперативну чергу (в деяких псевдомовах):
q := Queue.new();
while (true) {
if (Queue.is_empty(q)) {
Queue.add(q, producer());
} else {
consumer(Queue.take(q));
}
}
Відповідна структура з функціональною структурою даних черги (все ще необхідною мовою, щоб вирішити одну різницю за один раз) виглядала б так:
q := Queue.empty;
while (true) {
if (q = Queue.empty) {
q := Queue.add(q, producer());
} else {
(tail, element) := Queue.take(q);
consumer(element);
q := tail;
}
}
Оскільки черга тепер незмінна, сам об’єкт не змінюється. У цьому псевдокоді q
сама по собі є змінною; завдання q := Queue.add(…)
та q := tail
змусити його вказувати на інший об’єкт. Інтерфейс функцій черги змінився: кожен повинен повернути новий об’єкт черги, що є результатом операції.
На суто функціональній мові, тобто на мові, яка не має побічних ефектів, потрібно зробити всі стану явними. Оскільки виробник і споживач, імовірно, щось роблять, їх стан також повинен знаходитися в інтерфейсі абонента.
main_loop(q, other_state) {
if (q = Queue.empty) {
let (new_state, element) = producer(other_state);
main_loop(Queue.add(q, element), new_state);
} else {
let (tail, element) = Queue.take(q);
let new_state = consumer(other_state, element);
main_loop(tail, new_state);
}
}
main_loop(Queue.empty, initial_state)
Зверніть увагу, як тепер явно керується кожним фрагментом держави. Функції маніпулювання чергою приймають чергу як вхід і створюють нову чергу як вихід. Виробник і споживач також проходять свою державу.
Одночасне програмування не так добре вписується у функціональне програмування, але воно дуже добре вписується у функціональне програмування. Ідея полягає в тому, щоб запустити купу окремих вузлів обчислень і дозволити їм обмінюватися повідомленнями. Кожен вузол запускає функціональну програму, і його стан змінюється, коли він надсилає та приймає повідомлення.
Продовжуючи приклад, оскільки є одна черга, вона керується одним конкретним вузлом. Споживачі надсилають цьому вузлу повідомлення для отримання елемента. Виробники надсилають цьому вузлу повідомлення, щоб додати елемент.
main_loop(q) =
consumer->consume(q->take()) || q->add(producer->produce());
main_loop(q)
Один «промислові» мову , який отримує паралелізм right³ є Erlang . Навчання Ерлангу - це безумовно шлях до просвітництва⁴ про одночасне програмування.
Усі зараз переходять на мови, що не мають побічних ефектів!
¹ Цей термін має кілька значень; тут я думаю, що ви використовуєте це для того, щоб означати програмування без побічних ефектів, і це сенс, який я також використовую.
² Програмування з неявним станом - це імперативне програмування ; орієнтація об'єкта - це абсолютно ортогональна проблема.
³ Запальний, я знаю, але маю на увазі це. Нитки із загальною пам'яттю - це мова складання одночасного програмування. Передача повідомлень набагато простіше зрозуміти, а відсутність побічних ефектів справді світиться, як тільки ви вводите одночасність.
⁴ І це йде від того, хто не фанат Ерланг, але з інших причин.