Въведение на функционален програмист към JavaScript (съставяне на софтуер)

Димни арт кубчета за пушене - MattysFlicks - (CC BY 2.0)
Забележка: Това е част от серията „Compose Software“ (сега книга!) За изучаване на функционални програми и техники за композиционен софтуер в JavaScriptES6 + от самото начало. Продължавайте да се настройвате. Има много повече от това, което предстои!
Купете книгата | Индекс | <Предишна | Напред>

За непознатите с JavaScript или ES6 + това е предназначено като кратко въведение. Независимо дали сте начинаещ или опитен разработчик на JavaScript, можете да научите нещо ново. Следното е предназначено само да надраскате повърхността и да ви развълнуват. Ако искате да знаете повече, просто ще трябва да проучите по-дълбоко. Предстои много повече.

Най-добрият начин да се научите да кодирате е да кодирате. Препоръчвам ви да следвате, като използвате интерактивна среда за програмиране на JavaScript като CodePen или Babel REPL.

Като алтернатива можете да се измъкнете с помощта на REPL на конзолата на възела или на браузъра.

Изражения и стойности

Изразът е парче код, който се оценява на стойност.

Следните са всички валидни изрази в JavaScript:

7;
7 + 1; // 8
7 * 2; // 14
'Здравейте'; // Здравейте

Стойността на израза може да се даде име. Когато го направите, изразът се оценява първо и получената стойност се присвоява на името. За това ще използваме ключовата дума const. Това не е единственият начин, но той е този, който ще използвате най-много, така че засега ще се придържаме към const:

const hello = 'Здравей';
Здравейте; // Здравейте

var, нека и const

JavaScript поддържа още две ключови думи с променлива декларация: var и let. Обичам да ги мисля по отношение на реда на подбор. По подразбиране избирам най-строгата декларация: const. Променлива, декларирана с ключовата дума const, не може да бъде преназначена. Крайната стойност трябва да бъде определена по време на деклариране. Това може да звучи строго, но ограничението е добро нещо. Това е сигнал, който ви казва, „стойността, присвоена на това име, няма да се промени“. Помага ви да разберете напълно какво означава името веднага, без да е необходимо да четете цялата функция или да блокирате обхвата.

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

Тъй като var ви казва най-малко за променливата, това е най-слабият сигнал. Откакто започнах да използвам ES6, никога умишлено не съм декларирал вар в истински софтуер.

Имайте предвид, че щом променлива е декларирана с let или const, всеки опит за деклариране отново ще доведе до грешка. Ако предпочитате по-голяма експериментална гъвкавост в средата REPL (Read, Eval, Print Loop), можете да използвате var вместо const за деклариране на променливи. Допуска се повторно маркиране на var.

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

Видове

Досега видяхме два типа: числа и низове. JavaScript също има булеви (верни или неверни), масиви, обекти и др. Ще стигнем до други видове по-късно.

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

[1, 2, 3];

Разбира се, това е израз, на който може да се даде име:

const arr = [1, 2, 3];

Обект в JavaScript е съвкупност от двойки ключ: стойност. Той също така има буквално обозначение:

{
  ключ: 'стойност'
}

И разбира се, можете да зададете обект на име:

const foo = {
  бар: 'бар'
}

Ако искате да присвоите съществуващи променливи на обектните ключове за собственост със същото име, има пряк път за това. Можете просто да въведете името на променливата, вместо да предоставите и ключ, и стойност:

const a = 'a';
const oldA = {a: a}; // дълъг, излишен начин
const oA = {a}; // късо сладко!

Само за забавление, нека да направим това отново:

const b = 'b';
const oB = {b};

Обектите могат лесно да се комбинират заедно в нови обекти:

const c = {... oA, ... oB}; // {a: 'a', b: 'b'}

Тези точки са операторът за разпространение на обекти. Той итератира над свойствата в oA и ги присвоява на новия обект, след това прави същото за oB, като отменя всички ключове, които вече съществуват в новия обект. Към това писане, разпръскването на обекти е нова, експериментална функция, която все още може да не е налична във всички популярни браузъри, но ако не работи за вас, има заместител: Object.assign ():

const d = Object.assign ({}, oA, oB); // {a: 'a', b: 'b'}

Само малко повече пишете в примера Object.assign () и ако съставяте много обекти, това може дори да ви спести някои печатни. Обърнете внимание, че когато използвате Object.assign (), трябва да подадете обект на местоназначение като първи параметър. Това е обектът, в който свойствата ще бъдат копирани. Ако забравите и пропуснете обекта на местоназначение, обектът, който преминавате в първия аргумент, ще бъде мутиран.

Според моя опит мутирането на съществуващ обект, а не създаването на нов обект, обикновено е грешка. Най-малкото е податлив на грешки. Внимавайте с Object.assign ().

разградено

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

const [t, u] = ['a', 'b'];
T; // 'a'
ф; // 'b'
const blep = {
  blop: 'blop'
};

// Следното е еквивалентно на:
// const blop = blep.blop;
const {blop} = blep;
Blop; // „блокиране“

Както при горния пример на масив, можете да унищожите до няколко задания наведнъж. Ето ред, който ще видите в много проекти на Redux:

const {тип, полезен товар} = действие;

Ето как се използва в контекста на редуктор (много повече по тази тема идва по-късно):

const myReducer = (състояние = {}, действие = {}) => {
  const {тип, полезен товар} = действие;
  превключвател (тип) {
    случай 'FOO': връщане Object.assign ({}, състояние, полезен товар);
    по подразбиране: състояние на връщане;
  }
};

Ако не искате да използвате различно име за новото обвързване, можете да зададете ново име:

const {blop: bloop} = blep;
bloop; // „блокиране“

Прочетете: Присвойте blep.blop като bloop.

Сравнения и тернари

Можете да сравните стойностите с оператора на строго равенство (понякога наричан „тройни равни“):

3 + 1 === 4; // вярно

Има и оператор на помия за равенство. Той е официално известен като оператор „Равен“. Неофициално „двойни равни“. Double equals има валиден случай на използване или два, но вместо това е почти винаги по-добре да се зададе по подразбиране оператора ===.

Други оператори за сравнение включват:

  • > По-голямо от
  • <По-малко от
  • > = По-голям или равен на
  • <= По-малко или равно на
  • ! = Не е равно
  • ! == Не е строго равно
  • && Логически и
  • || Логически или

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

14 - 7 === 7? "Да!" : 'Nope.'; // Да!

Функции

JavaScript има функционални изрази, които могат да бъдат присвоени на имена:

const двойно = x => x * 2;

Това означава същото нещо като математическата функция f (x) = 2x. Изговорена на глас, тази функция чете f от х е равно на 2x. Тази функция е интересна само когато я приложите към определена стойност на x. За да използвате функцията в други уравнения, ще напишете f (2), което има същото значение като 4.

С други думи, f (2) = 4. Можете да мислите за математическа функция като съпоставяне от входове към изходи. f (x) в този случай е картографиране на входните стойности за x към съответните изходни стойности, равни на произведението на входната стойност и 2.

В JavaScript стойността на функционалния израз е самата функция:

двойно; // [Функция: двойна]

Можете да видите определението на функцията, използвайки метода .toString ():

double.toString (); // 'x => x * 2'

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

Можете да извикате функция, като използвате (argument1, argument2, ... rest). Например, за да извикаме нашата двойна функция, просто добавете скоби и прехвърлете стойност, която да се удвои:

двойно (2); // 4

За разлика от някои функционални езици, тези скоби имат смисъл. Без тях функцията няма да бъде извикана:

двоен 4; // SyntaxError: Неочаквано число

Подписи

Функциите имат подписи, които се състоят от:

  1. Незадължително име на функция.
  2. Списък от типове параметри в скоби. Параметрите могат по избор да бъдат именувани.
  3. Видът на връщащата се стойност.

Подписите на типа не е необходимо да се посочват в JavaScript. JavaScript двигателят ще разбере типовете по време на изпълнение. Ако предоставите достатъчно улики, подписът може да се направи и от инструменти за разработчици като IDE (Integrated Environment Environment) и Tern.js, като се използва анализ на потока на данни.

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

В момента TypeScript и Flow са най-големите претенденти. Не съм сигурен как да изразя всичко, което ми трябва в нито едно от тях, затова използвам Rtype само за целите на документацията. Някои хора попадат отново на типовете Hindley – Milner на Haskell. Бих искал да видя добра система за нотация, стандартизирана за JavaScript, дори само за целите на документацията, но не мисля, че в момента никое от настоящите решения не отговаря на задачата. Засега присвийте и направете всичко възможно да сте в крак с странните подписи от типа, които вероятно изглеждат малко по-различни от това, което използвате.

functionName (param1: Type, param2: Type) => Тип

Подписът за двойно е:

двоен (x: n) => Брой

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

Стойности на параметри по подразбиране

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

const orZero = (n = 0) => n;

За да зададете по подразбиране, просто го задайте на параметъра с оператора = в подписа на функцията, както в n = 0, по-горе. Когато присвоявате стойности по подразбиране по този начин, въведете инструменти за извод като Tern.js, Flow или TypeScript автоматично да изведе типовия подпис на вашата функция, дори ако не декларирате изрично поясненията за типа.

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

Забележка: Параметрите с по подразбиране не се отчитат към свойството .length на функцията, което ще изхвърли помощни програми, като например автоматична верига, която зависи от стойността на .length. Някои утилити за къри (като lodash / къри) ви позволяват да преминете по поръчка arity, за да заобиколите това ограничение, ако се натъкнете на него.

Именни аргументи

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

const createUser = ({
  name = 'Анонимен',
  avatarThumbnail = '/avatars/anonymous.png'
}) => ({
  име,
  avatarThumbnail
});
const george = createUser ({
  име: „Джордж“,
  avatarThumbnail: 'аватари / нюанси-emoji.png'
});
Джордж;
/ *
{
  име: „Джордж“,
  avatarThumbnail: 'аватари / нюанси-emoji.png'
}
* /

Почивка и разстилане

Обща характеристика на функциите в JavaScript е възможността да се съберат група от останали аргументи в подписа на функциите с помощта на оператора за почивка: ...

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

const aTail = (глава, ... опашка) => опашка;
aTail (1, 2, 3); // [2, 3]

Останалите събират отделни елементи заедно в масив. Spread прави обратното: разпространява елементите от масив към отделни елементи. Помислете за това:

const shiftToLast = (глава, ... опашка) => [... опашка, глава];
shiftToLast (1, 2, 3); // [2, 3, 1]

Масивите в JavaScript имат итератор, който се извиква, когато се използва оператора за разпространение. За всеки елемент от масива итераторът предоставя стойност. В израза, [... опашка, глава], итераторът копира всеки елемент от порядъка на опашния масив в новия масив, създаден от заобикалящата буквална нотация. Тъй като главата вече е индивидуален елемент, просто я поставяме в края на масива и сме готови.

козина

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

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

const highpass = cutoff => n => n> = cutoff;
const gt4 = highpass (4); // highpass () връща нова функция

Не е необходимо да използвате функции със стрелки. JavaScript също има ключова дума за функция. Използваме функции със стрелки, тъй като функционалната ключова дума е много повече пишеща. Това е еквивалентно на дефиницията highPass () по-горе:

const highpass = функция highpass (cutoff) {
  функция за връщане (n) {
    връщане n> = прекъсване;
  };
};

Стрелката в JavaScript приблизително означава „функция“. Има някои важни разлики в поведението на функциите в зависимост от това какъв тип функция използвате (=> липсва собствената му тази и не може да се използва като конструктор), но ще стигнем до тези разлики, когато стигнем до там. Засега, когато видите x => x, помислете "функция, която приема x и връща x". Така че можете да прочетете const highpass = cutoff => n => n> = cutoff; като:

„Highpass е функция, която отрязва и връща функция, която приема n и връща резултата от n> = cutoff“.

Тъй като highpass () връща функция, можете да я използвате за създаване на по-специализирана функция:

const gt4 = highpass (4);
GT4 (6); // вярно
GT4 (3); // невярно

Автоматичното изтегляне ви позволява да извличате функции автоматично, за максимална гъвкавост. Кажете, че имате функция add3 ():

const add3 = къри ((a, b, c) => a + b + c);

С автоматичната верига можете да я използвате по няколко различни начина и тя ще върне правилното нещо в зависимост от това колко аргументи предавате:

add3 (1, 2, 3); // 6
add3 (1, 2) (3); // 6
add3 (1) (2, 3); // 6
add3 (1) (2) (3); // 6

За съжаление на феновете на Haskell, в JavaScript липсва вграден механизъм за автоматично изтегляне, но можете да импортирате такъв от Lodash:

$ npm install - запазване на квартира

След това във вашите модули:

внасят къри от „lodash / къри“;

Или можете да използвате следното вълшебно заклинание:

// Малки, рекурсивни автоцикли
const curry = (
  f, arr = []
) => (... args) => (
  a => a.length === f.length?
    f (... a):
    къри (е, а)
) ([... arr, ... args]);

Функция Състав

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

е. г

Което означава това в JavaScript:

е (г (х))

Оценява се отвътре навън:

  1. x се оценява
  2. g () се прилага към x
  3. f () се прилага към връщащата стойност на g (x)

Например:

const inc = n => n + 1;
Inc (двойна (2)); // 5

Стойността 2 се предава в двойно (), което произвежда 4. 4 се предава в inc (), което оценява на 5.

Можете да предадете всеки израз като аргумент на функция. Изразът ще бъде оценен преди да се приложи функцията:

inc (двойно (2) * двойно (2)); // 17

Тъй като двойното (2) оценява на 4, можете да прочетете това като inc (4 * 4), което оценява на inc (16), което след това оценява на 17.

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

Масивите

Масивите имат някои вградени методи. Методът е функция, свързана с обект: обикновено свойство на асоциирания обект:

const arr = [1, 2, 3];
arr.map (двойно); // [2, 4, 6]

В този случай arr е обектът, .map () е свойство на обекта с функция за стойност. Когато го извикате, функцията се прилага към аргументите, както и към специален параметър, наречен this, който се задава автоматично, когато методът бъде извикан. Тази стойност е как .map () получава достъп до съдържанието на масива.

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

Обърнете внимание, че оригиналната стойност на arr е непроменена:

Пр; // [1, 2, 3]

Метод на вериги

Можете също да веригирате повиквания по метод. Веригирането на метода е процесът на директно извикване на метод на връщащата се стойност на функция, без да е необходимо да се позовавате на връщаната стойност по име:

const arr = [1, 2, 3];
arr.map (двойно) .map (двойно); // [4, 8, 12]

Предикатът е функция, която връща булева стойност (вярна или невярна). Методът .filter () взема предикат и връща нов списък, избирайки само елементите, които предават предиката (return true), за да бъдат включени в новия списък:

[2, 4, 6]. Филтър (gt4); // [4, 6]

Често ще искате да изберете елементи от списък и след това да ги картографирате в нов списък:

[2, 4, 6]. Филтър (gt4). Карта (двоен); [8, 12]

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

заключение

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

Купете книгата | Индекс | <Предишна | Напред>

Научете повече на EricElliottJS.com

Видео уроци с предизвикателства с интерактивен код са на разположение за членове на EricElliottJS.com. Ако не сте член, регистрирайте се днес.

Ерик Елиът е експерт по разпределени системи и автор на книгите „Съставяне на софтуер“ и „Програмиране на JavaScript приложения“. Като съосновател на DevAnywhere.io, той обучава разработчиците на уменията, от които се нуждаят, за да работят отдалечено и да приемат баланс между работа и живот. Той създава и съветва екипи за разработка на крипто проекти и е допринесъл за софтуерни преживявания за Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC и най-добрите звукозаписни артисти, включително Usher, Frank Ocean, Metallica и много други.

Той се радва на отдалечен начин на живот с най-красивата жена в света.