Привіт! Мене зовуть Профессор Франклін Рісбі. Дуже приємно з вами познайомитись. Ми з вами проведемо деякий час разом, оскільки я збираюсь трохи навчити вас функціональному програмуванню. Але досить про мене. Як щодо вас? Я сподіваюсь, що ви принаймні трохи знайомі з мовою JavaScript, маєте крихітний досвід об'єктно-орієнтованого програмування і в думках вважаєте себе робочим програмістом. Ви не мусите бути доктором наук, але маєте знати як знаходити і знищувати деякі баги.
Я не буду припускати, що у вас вже є якісь знання у функціональном програмуванні , бо ми всі знаємо, що виходить з припущень. Я все таки сподіваюсь, що ви зіштовхувались з неприємними ситуаціями, які виникали через роботу зі змінюючимся(мутабельним) станом, із необмеженими побічними ефектами і безпринципним дизайном. Тепер, коли ми познайомились правильним чином, давайте приступимо.
Ця частина ставить перед собою за мету дати нам зрозуміти як ми себе почуваємо після того, як почали писати функціональні програми. Для того, щоб зрозуміти наступні частини, ми повинні розуміти, що робить програму функціональною. Бо інакше, ми опинемося у ситуації, коли ми строчитимемо код безцільно, уникаючи об'єктів будь-якою ціною і все це буде марною втратою сил. Нам потрібна чітка ціль в яку ми зможемо цілити нашим кодом.
Зараз існують деякі загальні принципи програмування - різноманітні абревіатурні скорочення які ведуть нас темними тонелями будь-якої програми: DRY (don't repeat yourself - не повторюй себе), YAGNI (ya ain't gonna need it - тобі це не знадобиться), принцип найменшої несподіванки, єдиної відповідальності і так далі.
Я не хочу вас засмучувати перераховуючи усі рекомендації, які я чув протягом багатьох років... Справа в тому, що вони всі вони мають відношення до функціонального програмування, але стосуються нашої кінцевої мети лише по дотичній. Єдине, що я хотів би донести до вас, перед тим як ми продовжимо, це те, що наша мета не просто набирати код, клацаючи по клавіатурі, а досягти функціональної Нірвани.
Давайте почнемо з нотки божевілля. Уявімо програму чайки. Коли зграйки об'єднуються - вони стають більшими зграйками, а коли чайки паруються - вони збільшують чисельність зграї на кількість чайок з якими вони паруються. Ця програмка не претендує на те, щоб бути гарним об'єктно-орієнтовним кодом, але зауважте, що він підкреслює небезпечність сучаного, базуючогось на присвоєнні підходу. Ось погляньте:
var Flock = function(n) {
this.seagulls = n;
};
Flock.prototype.conjoin = function(other) {
this.seagulls += other.seagulls;
return this;
};
Flock.prototype.breed = function(other) {
this.seagulls = this.seagulls * other.seagulls;
return this;
};
var flock_a = new Flock(4);
var flock_b = new Flock(2);
var flock_c = new Flock(0);
var result = flock_a.conjoin(flock_c)
.breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;
//=> 32
Хто б у Світі міг би створити таку гидоту? Адже це невиправдано складно слідкувати за зміную внутрішнього стану програми. І, Слава Яйцям, відповідь навіть не правильна! Мало б бути 16
, але зграйка flock_a
була остаточно змінена в процесі розмноження. Бідна зграйка. Це - анархія в I.T.! Це арифметика диких тварин!
Якщо ви не розумієте цю програму - не лякайтесь, я теж її не розумію. Але що важливо винести з цього прикладу, так це те, що стан програми та змінюємі(мутабельні) значення важко відслідковувати, навіть у такому невеличкому прикладі, як з нещасними чайками.
Давайте спробуємо знову, але цього разу використаємо більш функціональний підхід:
var conjoin = function(flock_x, flock_y) { return flock_x + flock_y; };
var breed = function(flock_x, flock_y) { return flock_x * flock_y; };
var flock_a = 4;
var flock_b = 2;
var flock_c = 0;
var result = conjoin(
breed(flock_b, conjoin(flock_a, flock_c)), breed(flock_a, flock_b)
);
//=>16
Ну що ж, цього разу ми отримали правильну відповідь. І менше писанини, доречі. Щоправда вкладеність функції трохи збентежлива...(ми виправимо цю ситуацію у Частині 5). Це вже краще, але давайте копати трохи глибше. Є безсумнівні переваги від називання лопати лопатою. Якби ми розглянули наші функцію трохи детальніше, ми б помітили, що ми працюємо зі звичайним додаванням(conjoin
) та множенням(breed
).
Виходить, що в цих двох функціяї немає нічого дивного окрім їх назв. Тоє давайте переіменуємо наші функції у multiply
(помножити) та add
(додати) для того, щоб продемострувати їхні справжні сутності.
var add = function(x, y) { return x + y; };
var multiply = function(x, y) { return x * y; };
var flock_a = 4;
var flock_b = 2;
var flock_c = 0;
var result = add(
multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b)
);
//=>16
Тепер давайте пригадаємо знання предків:
// асоціатив
add(add(x, y), z) === add(x, add(y, z));
// комутатив
add(x, y) === add(y, x);
// ідентичність
add(x, 0) === x;
// дистрибутив
multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));
О так, ті старі добрі математичні властивості мають стати нам в нагоді. Не переймайтесь, якщо ви не можете їй отак одразу пригадати. Для багатьох з нас сплило вже достатньо часу з того моменту, коли ми вчили ті закони арифметики. Давайте краще поглянемо чи зможемо ми використати ті математичні штуки для того, щоб спростити нашу програму "Чайка".
// Початкова строка
add(multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b));
// Застосуємо властивість ідентичності, щоб прибрати зайвий `add`
// (add(flock_a, flock_c) == flock_a)
add(multiply(flock_b, flock_a), multiply(flock_a, flock_b));
// Застосуємо властивість дистрибутиву, щоб отримати наш результат
multiply(flock_b, add(flock_a, flock_a));
Відмінно! Ми не повинні писати додатковий код, а лише викликати наші функції. Ми включили сюди add
та multiply
для повноти картини, але насправді немає необхідності писати їх самостійно, бо, безумовно, вже існують бібліотеки, які реалізують готові методи add
та multiply
.
Ви можете подумати, "хто взагалі надав такий код в якості прикладу". Чи "справжні програми не настільки примітивні і їх не можна писати таким чином". Я обрав цей приклаж, оскільки більшість з нас знайомі з додаванням та множенням, тож це спрощує пояснення та усвідомлення того, наскільки математика може бути тут корисною для нас.
Не засмучуйтесь, в цій книзі ми будемо занурюватись у різні теорії та лямбда-вирахування і будемо писати справжні приклади з реального Світу, які будуть настільки ж елегантні як і наша програма "Чайка". І при цьому вам не потрібно бути математиками. Це буде дуже природньо та легко, так само легко, як коли ви використовуєте "нормальний" фреймворк або API.
Ви напевно здивуєтесь, коли я скажу вам, що ми можемо писати повноцінні щоденні програми у рядок, як у нашому попередньому функціональному прикладі. Програми, які не багатослівні, але достатньо зрозумілі і які легко читати. Програми які не винаходять повторно колеса. Беззаконня та анархія кльові, якщо ви злочинець, але в цій книзі ми захочемо визнати та поважати закони математики.
Ми захочемо використовувати теорію, в якій кожна частинка ідеально підходить одна до одної. Ми захочемо висвітлити нашу конкретну проблему з точки зору загальниї, взаємозамінниї частин і дослідити їх властивості для досягнення наших цілей. Це вимагатиме трохи більше дисципліни ніж звичайний "все можна" підхід імперативного програмування (ми перейдемо до точного визначення "імперативного" програмування, але до тих пір, вважайте, що все не функціональне - імперативне). І результат роботи у чіткому математичному підході вас дійсно вразить.
Ми з вами побачили, як спалахнула наша Північна Зоря Функціонального Програмування(ФП), але є ще декілька конкретних концепцій, які потрібно зрозуміти, перед тим як ми насправді продовжимо нашу подорож.