Много упорито въведение във функционалното програмиране в Javascript с помощта на Ramda. Ползите от използването на функционално програмиране в Javascript и първи поглед върху състава на функциите.

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

Говорейки за Javascript по време на работа, да, аз работя в Ryanair като водещ фронтенд разработчик, ние вече забранихме отдавна използването на for и while цикли (където производителността не е проблем), използвайки само map/filter/reduce вместо останалите.

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

Първо искам да започна, като отговоря на лесните въпроси, така че:

  • Какво е функционален език за програмиране?
  • Защо Javascript е функционален език за програмиране?

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

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

В javascript функциите са функции от първи клас, всъщност можем да присвоим функция на променлива като елемент от масив.

const foo = () => 'bar';

const list = [foo];

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

const higher = f => f('bar');

const another = () => {  
  return () => 'bar';
};

Защо

След като представихме малко какво е функционално програмиране и защо javascript е функционален език за програмиране, следващият въпрос е защо трябва да се тревожим за изучаването и писането в тази нова парадигма?

Внимание, следните твърдения са много субективни и лични.

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

В Ryanair използвахме различни рамки за различни проекти, започнахме с Angular 1 и сега преминаваме към Angular 2, докато тези две рамки са много различни, парадигмата, която следваме, е същата.

Рамките ще идват и ще си отиват, вчера беше Backbone, днес е React, утре кой знае.

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

Всеки глупак може да напише код, който компютърът може да разбере. Добрите програмисти пишат код, който хората могат да разберат.

Защо не обектно ориентирани

Следвахме обектно ориентиран подход в началото на новия уеб сайт и вероятно се натъкваме на всички проблеми, които идват с него:

Наследство

Един от стълбовете на обектно-ориентираната парадигма за повторно използване на код. Когато използвате наследяване, вие създавате таксономия, йерархия за понятията, които трябва да оформите. Принуждавайки ви да наследите от един прародител, в голям проект доста скоро ще имате известния проблем с горила/банан

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

Джо Армстронг

Интерфейсите могат да ви спасят тук, вместо да разчитате директно на наследяването.

Капсулиране

Капсулирането е един от другите стълбове на обектно-ориентираното програмиране, променливите са защитени от достъп, защото са капсулирани вътре в обекта. В javascript това е вярно за примитивните типове, които се предават по стойност. Но обектите винаги се предават чрез референция, което означава, че предаваният обект изобщо не е безопасен.

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

Добре, спрете да чатите и сега нека се потопим в него. Ние избираме Ramda като помощна библиотека, за да започнем това пътуване във функционалното програмиране.

Състав

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

Ако разгледаме Ramda, той излага различни функции за извършване на това, започваме с compose

const compose = (f, g) => x => f(g(x));

R.compose в Ramda вземат n функции и според името на функцията ги съставят заедно. Едно нещо, което трябва да се отбележи при композирането е, че ние дефинираме функциите, които да се изпълняват отдясно наляво.

Ние ги дефинираме отдясно наляво, защото следваме математическата композиция, която се прилага и отдясно наляво, по този начин нашето compose ще следва същите закони, които има математическото: асоциативност

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

Като се има предвид тази структура от данни за полет и три функции a b c:

const flight = {  
  options: {
    1: {
      globalCode: 'foo'
    }
  }
};

const a = data => data.options[1].globalCode;

const b = data => data.toUpperCase();

const c = data => data + '-BAR';

И ние искаме да ги съставим заедно

R.compose(  
  R.compose(c, b),
  a
)(flight) //=> 'FOO-BAR'

е равно на

R.compose(  
  c,
  R.compose(b, a)
)(flight) //=> 'FOO-BAR'

И така, как можем да използваме композицията в javascript и какво позволява тя?

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

const getDiscountFlights = flights => {  
  return flights.filter(flight => {
    if (flight && flight.options && flight.options[1] &&
        flight.options[1].globalCode) {
      return flight.options[1].globalCode.indexOf('ESDSC') !== -1;
    }
    return false;
  })
};

Сега нека се опитаме да разделим нашите функции на по-малки и да ги съставим заедно:

const hasDiscountCode = R.contains('ESDSC');  
const getGlobalCode = R.pathOr(  
  '',
  ['options', '1', 'globalCode']
);
const getDiscountFlights = R.filter(  
  R.compose(
    hasDiscountCode,
    getGlobalCode
  )
);

Този код е повече или по-малко еквивалентен, кой мислите, че е по-четим и поддържаем?

Въведохме някои други функции на Ramda тук:

  • R.contains ще провери дали нашият globalCode съдържа кода ESDSC и можете да го използвате и върху масиви или обекти.
  • R.pathOr връща стойността по този път за дадения обект, в противен случай връща предоставената стойност по подразбиране.

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

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

//util.js
export const hasDiscount = R.compose(  
  R.contains('ESDSC'),
  R.pathOr(
    '',
    ['options', '1', 'globalCode']
  )
);

//main.js
import { hasDiscount } from './utils';

const getDiscountFlights = flights => {  
  return R.filter(
    hasDiscount
  )(flights);
};

Обратно композиране

Ако вместо да прилагате функции отдясно наляво, предпочитате да прилагате функциите отляво надясно, Ramda вече има функция за нея, която се нарича pipe.

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

В следващата публикация ще говоря за чисти функции.

Първоначално публикувано в izifortune.com на 15 февруари 2017 г.