Нека разберем какви са принципите за функционално програмиране с помощта на Javascript

Преди да преминем към неговите принципи, нека първо разберем малко за него.

Какво е функционално програмиране

  • Функционалното програмиране е парадигма, която има своите корени в математиката, основно произтичаща от „ламбда смятането“.
  • Основният му фокус е върху „какво да реша“ за разлика от императивния стил, където основният фокус е „как да реша“.
  • Функционалното програмиране има за цел да бъде декларативно и третира приложенията като резултат от чисти функции, които са съставени една с друга.
  • Основната цел на този стил на програмиране е да се избегнат проблемите, които идват със споделеното състояние, променливите данни и „страничните ефекти“, които са често срещани в обектно-ориентираното програмиране.
  • Функционалното програмиране обикновено е по-предсказуемо и по-лесно за тестване от обектно-ориентираното програмиране, но също така може да изглежда плътно и трудно за научаване за новодошлите, но функционалното програмиране не е толкова трудно, колкото изглежда на пръв поглед.

Защо имаме нужда от функционално програмиране?

Има няколко основни предимства на функционалното програмиране и малки недостатъци:

Предимства:

  1. Повече четливост, следователно поддръжка
  2. По-малко бъги, особено в едновременен контекст
  3. Нов начин на мислене за решаване на проблеми
  4. (Личен бонус) Страхотно е да научите!

Недостатъци:

  1. Може да има проблеми с производителността
  2. По-малко интуитивен за работа при работа със състояние и I/O
  3. Непозната за повечето хора + математическа терминология, която забавя учебния процес

Функционалното програмиране е по-предвидимо и по-лесно за тестване от обектно-ориентираното програмиране.

Принципи

На по-широко ниво има основно 6 принципа на функционалното програмиране

  1. Чистота
  2. Неизменност
  3. Дисциплинирано състояние
  4. Функции от първи клас и функции от висок ред
  5. Типове системи
  6. Референтна прозрачност

Нека ги видим в подробности по-долу.

Чистота

  • AЧиста функцияе функция (блок от код), която винаги връща един и същ резултат, ако се подадат едни и същи аргументи.
  • Не зависи от каквото и да е състояние или промяна на данните по време на изпълнение на програмата. По-скоро зависи само от входните аргументи.
  • Освен това чистата функция не произвежда видими странични ефекти като мрежови заявки или мутация на данни и др.
  • Няма странични ефекти като модифициране на аргумент или глобална променлива или извеждане на нещо.
  • Чистите функции са предвидими и надеждни. Най-вече изчисляват само своя резултат. Единственият резултат от извикването на чиста функция е върнатата стойност.

Нека да видим пример по-долу за изчисляване на GST.

const calculateGST = (productPrice) => {
    return productPrice * 0.05;
}
  • Изчислението в квадратна функция зависи от входовете. Без значение колко пъти извикваме квадратна функция със същия вход, тя винаги ще връща същия изход.

Пример по-долу

const sqaureIt= (number) => {
    return number * number;
}

Неизменност

  • Неизменните данни не могат да променят своята структура или данните в нея.
  • След като присвоите стойност на нещо, тази стойност няма да се промени.

Неизменността е в основата на функционалното програмиране. Това елиминира страничните ефекти (всичко извън обхвата на локалната функция), например промяна на други променливи извън функцията.

  • Неизменността помага да се поддържа състоянието през цялото време на изпълнение на програмата.
  • Това прави кода прост, тестван и способен да работи на разпределени и многонишкови системи
  • Тъй като функцията има дисциплинирано състояние и не променя други променливи извън функцията, не е необходимо да разглеждаме кода извън дефиницията на функцията.

Неизменността влиза в игра често, когато работим със структури от данни. Много методи за масиви в JavaScript директно променят масива.

Например .pop() директно премахва елемент от края на масива, а .splice() ви позволява да вземете част от масива.

// We are mutating myArr directly
const myArr = [1, 2, 3];
myArr.pop();
// [1, 2]

Вместо това, в рамките на функционалната парадигма, ще копираме масива и в този процес ще премахнем елемента, който искаме да елиминираме.

// We are copying the array without the last element and storing it to a variable
let myArr = [1, 2, 3];
let myNewArr = myArr.slice(0, 2);
// [1, 2]
console.log(myArr);

Дисциплинирана държава

  • Дисциплинираното състояние е обратното на споделеното, променливо състояние.
  • Споделено променливо състояние е трудно да се поддържа правилно, тъй като има много функции, които имат директен достъп до това състояние. Освен това е трудно за четене и поддръжка.
  • При променливото състояние трябва да потърсим всички функции, които използват споделени променливи, за да разберем логиката. Трудно е да се отстранят грешки поради същата причина.
  • Когато кодираме с предвид принципите на функционалното програмиране, вие избягвате, доколкото е възможно, да имате споделено променливо състояние.
  • Разбира се, можем да имаме състояние, но трябва да го запазите локално, което означава вътре в нашата функция. Това е държавната дисциплина: използваме държава, но по много дисциплиниран начин.

Пример за недостатъците на споделеното, променливо състояние би бил следният:

const logElements = (arr) => {
  while (arr.length > 0) {
    console.log(arr.shift());
  }
}

const main = () => {
  const arr = ['Node', 'React', 'Javascript', 'Typescript'];

  console.log('=== Before sorting ===');
  logElements(arr);

  arr.sort();

  console.log('=== After sorting ===');
  logElements(arr);
}

main();

// === Before sorting ===
// "Node"
// "React"
// "Javascript"
// "Typescript"

// === After sorting ===
// undefined

Можем да видим, че второто извикване не дава резултат, тъй като първото изпразни входния масив и по този начин мутира състоянието на приложението, генерирайки неочакван изход.

За да коригираме това, се обръщаме към неизменността и използването на копия, за да запазим първоначалното състояние прозрачно и неизменно.

const logElements = (arr) => {
  while (arr.length > 0) {
    console.log(arr.shift());
  }
}

const main = () => {
  const arr = ['Node', 'React', 'Javascript', 'Typescript'];

  console.log('=== Before sorting ===');
  logElements([...arr]); // Change in line

  const sorted = [...arr].sort(); // Assign sorted items to another variable

  console.log('=== After sorting ===');
  logElements([...sorted]); // Change in line
}

main();

// === Before sorting ===
// "Node"
// "React"
// "Javascript"
// "Typescript"

// === After sorting ===
// "Javascript"
// "Node"
// "React"
// "Typescript"

Функции от първи клас и функции от висок ред

Функциите от по-висок ред са функции, които извършват поне едно от следните неща:

  1. Приема една или повече функции като аргументи
  2. Връща функция като неин резултат

Функционалното програмиране третира функциите като първокласни граждани.

Това означава, че функциите могат да се предават като аргументи на други функции, да се връщат като стойности от други функции, да се съхраняват в структури от данни и да се присвояват на променливи.

const plusFive = (number) => {
  return number + 5;  
};

// f is assigned the value of plusFive
let f = plusFive;

plusFive(3); // 8

// Since f has a function value, it can be invoked. 

f(9); // 14

Функция от по-висок ред е функцията, която приема една или повече функции като аргументи или връща функция като резултат.

Типове системи

Използвайки типове, ние използваме компилатор, за да ни помогне да избегнем често срещани грешки, които могат да възникнат по време на процеса на разработка.

С JavaScript бихме могли да направим следното:

const add = (left, right) => {
  return left + right;
}

add(2, 3) // 5
add(2, "3"); // "5"

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

Нека разгледаме същия код, написан с Typescript:

const add = (left: number, right: number): number => {
  return left + right;
}

add(2, 3) // 5
add(2, "3"); // error!

Тук можем да видим компилатора в действие, който ни предпазва от такива основни проблеми, разбира се, много повече е възможно със статично типизиран подход към разработването, но това трябва да ви даде същината на това защо е полезно да се използва.

Референтна прозрачност

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

Референтно прозрачните функции разчитат само на своите входове и по този начин са тясно свързани с чистите функции и концепцията за неизменност.

Казва се, че един израз е референтно прозрачен, ако може да бъде заменен със съответната стойност, без да се променя поведението на програмата.

За да се постигне референтна прозрачност, функцията трябва да бъде чиста. Това има предимства по отношение на четимост и скорост. Компилаторите често са в състояние да оптимизират код, който показва референтна прозрачност.

const two = () => {
  return 2;
}

const four = two() + two(); // 4
// or
const four = two() + 2; // 4
// or
const four = 2 + two(); // 4
// or
const four = 2 + 2; // 4

Изводи

Функционалното програмиране ни дава някои принципи, които правят нашия код по-четим, предвидим и тестван.

Това ни позволява да имаме код, който съдържа по-малко грешки, по-лесно навлизане и като цяло по-хубава кодова база от моя опит.

Ако харесвате тази публикация, можете да ме почерпите с кафе.

Освен това, за да получавате известия за моите нови статии и истории: Последвайте ме в Средно.

Абонирайте се за моя Канал в YouTube за образователно съдържание по подобни теми

Следвайте ме в Medium и GitHub, за бързо свързване

Можете да ме намерите в LinkedIn, тъй като това е професионална мрежа за хора като мен и вас.

Наздраве!!!!