Skip to content

Latest commit

 

History

History
125 lines (88 loc) · 12.7 KB

ch01-uk.md

File metadata and controls

125 lines (88 loc) · 12.7 KB

Частина 1: Що Ми Взагалі Робимо?

Вступ

Привіт! Мене звуть Профессор Франклін Рісбі. Дуже приємно з вами познайомитись. Ми з вами проведемо деякий час разом, оскільки я збираюсь трохи навчити вас функціональному програмуванню. Але досить про мене. Як щодо вас? Я сподіваюсь, що ви принаймні трохи знайомі з мовою JavaScript, маєте крихітний досвід об'єктно-орієнтованого програмування і в думках вважаєте себе робочим програмістом. Ви не мусите бути доктором наук, але маєте знати як знаходити і знищувати деякі баги.

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

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

Зараз існують деякі загальні принципи програмування - різноманітні абревіатурні скорочення які ведуть нас темними тунелями будь-якої програми: DRY (don't repeat yourself - не повторюй себе), YAGNI (ya ain't gonna need it - тобі це не знадобиться), принцип найменшої несподіванки, єдиної відповідальності і так далі.

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

Коротке Знайомство

Давайте почнемо з нотки божевілля. Уявімо програму чайки. Коли зграйки об'єднуються - вони стають більшими зграйками, а коли чайки паруються - вони збільшують чисельність зграї на кількість чайок, з якими вони паруються. Ця програмка не претендує на те, щоб бути гарним об'єктно-орієнтовним кодом, але зауважте, що він підкреслює небезпечність сучасного підходу, що грунтується на присвоєнні. Ось погляньте:

class Flock {
  constructor(n) {
    this.seagulls = n;
  }

  conjoin(other) {
    this.seagulls += other.seagulls;
    return this;
  }

  breed(other) {
    this.seagulls = this.seagulls * other.seagulls;
    return this;
  }
}

const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
  .conjoin(flockC)
  .breed(flockB)
  .conjoin(flockA.breed(flockB))
  .seagulls;
// 32

Хто б у Світі міг би створити таку гидоту? Адже це невиправдано складно - слідкувати за зміною внутрішнього стану програми. І, Слава Яйцям, відповідь навіть не правильна! Мало б бути 16, але зграйка flock_a була остаточно змінена в процесі розмноження. Бідна зграйка. Це - анархія в I.T.! Це арифметика диких тварин!

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

Давайте спробуємо знову, але цього разу використаємо більш функціональний підхід:

const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;

const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
    conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
// 16

Ну що ж, цього разу ми отримали правильну відповідь. І менше писанини, до речі. Щоправда, вкладеність функції трохи збентежує... (ми виправимо цю ситуацію в Частині 5). Це вже краще, але давайте копати трохи глибше. Є безсумнівні переваги від називання лопати лопатою. Якби ми розглянули наші функції трохи детальніше, то помітили би, що працюємо зі звичайним додаванням (conjoin) та множенням (breed).

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

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
    add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// 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(flockB, add(flockA, flockC)), multiply(flockA, flockB));

// Застосуємо властивість ідентичності, щоб прибрати зайвий `add`
// (add(flockA, flockC) == flockA)
add(multiply(flockB, flockA), multiply(flockA, flockB));

// Застосуємо властивість дистрибутиву, щоб отримати наш результат
multiply(flockB, add(flockA, flockA));

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

Ви можете подумати: "Хто взагалі надав такий код в якості прикладу?", або: "Справжні програми не настільки примітивні та їх не можна писати таким чином". Я обрав цей приклад, оскільки більшість з нас знайомі з додаванням та множенням, тож це спрощує пояснення та усвідомлення того, наскільки математика може бути тут корисною для нас.

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

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

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

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

Частина 2: Функції Першого Класу