Така че искате да бъдете функционален програмист (част 4)

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

Предишни части: част 1, част 2, част 3

козина

Ако си спомняте от част 3, причината, че имахме проблеми със съставянето на mult5 и добавянето (в) е, защото mult5 взема 1 параметър, а добавянето отнема 2.

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

Вярвай ми. Не е толкова лошо, колкото звучи.

Просто пишем функция за добавяне, която използва 2 параметъра, но взима само 1 параметър наведнъж. Изкривените функции ни позволяват да правим това.

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

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

В Javascript можем да постигнем това чрез пренаписване на добавяне:

var add = x => y => x + y

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

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

Сега можем да използваме тази версия на add за изграждане на работеща версия на mult5AfterAdd10:

var compose = (f, g) => x => f (g (x));
var mult5AfterAdd10 = композиране (mult5, добавяне (10));

Функцията за композиране приема 2 параметъра, f и g. След това връща функция, която приема 1 параметър, x, който при извикване ще приложи f след g до x.

И така, какво направихме точно? Е, ние преобразихме нашата обикновена стара функция за добавяне в изкривена версия. Това направи добавянето по-гъвкаво, тъй като първият параметър 10 може да бъде предаден към него отпред и крайният параметър ще бъде предаден, когато се извика mult5AfterAdd10.

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

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

добавете x y =
    x + y

Ето как трябва да бъде написано mult5AfterAdd10 в част 3:

mult5AfterAdd10 =
    (mult5 << добавете 10)

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

Къринг и рефакторинг

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

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

скоба str =
    "{" ++ str ++ "}"
doubleBracket str =
    "{{" ++ str ++ "}}"

Ето как ще го използваме:

bracketedJoe =
    скоба "Джо"
doubleBracketedJoe =
    двойна скоба "Джо"

Можем да обобщим скоба и doubleBracket:

generalBracket префикс str суфикс =
    префикс ++ str ++ суфикс

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

bracketedJoe =
    generalBracket "{" "Joe" "}"
doubleBracketedJoe =
    generalBracket "{{" "Joe" "}}"

Това, което наистина искаме, е най-доброто от двата свята.

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

generalBracket префикс суфикс str =
    префикс ++ str ++ суфикс
скоба =
    generalBracket "{" "}"
doubleBracket =
    generalBracket "{{" "}}"

Забележете, че поставяйки параметрите, които най-вероятно са първо статични, т.е. префикс и суфикс, и поставяме параметрите, които е най-вероятно да се променят последни, т.е. str, лесно можем да създадем специализирани версии на generalBracket.

Редът на параметрите е важен, за да се използва изцяло къринга.

Освен това забележете, че скобата и doubleBracket се записват в нотация без точка, т.е. параметърът str се подразбира. И скобата, и doubleBracket са функции, които чакат своя краен параметър.

Сега можем да го използваме точно както преди:

bracketedJoe =
    скоба "Джо"
doubleBracketedJoe =
    двойна скоба "Джо"

Но този път използваме обобщена функция за извиване, generalBracket.

Общи функционални функции

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

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

за (var i = 0; i 

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

Ако кодирате на императивни езици като Java, C #, Javascript, PHP, Python и др., Ще намерите това, че пишете този код на котлона повече от всеки друг.

Това не е наред с него.

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

Да започнем с промяна на масив, наречен неща:

вар неща = [1, 2, 3, 4];
за (var i = 0; i 

Уф !! Изменчивост!

Нека опитаме отново. Този път няма да мутираме нещата:

вар неща = [1, 2, 3, 4];
var newThings = [];
за (var i = 0; i 

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

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

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

var map = (f, масив) => {
    var newArray = [];
    за (var i = 0; i 

Забележете, че функцията, f, се предава, така че нашата карта функция може да направи всичко, което искаме, за всеки елемент от масива.

Сега можем да се обадим да пренапишем предишния си код, за да използваме map:

вар неща = [1, 2, 3, 4];
var newThings = map (v => v * 10, неща);

Виж мамо. Не за контури. И много по-лесно за четене и следователно разсъждение за.

Е, технически, във функцията на картата има for-loops. Но поне не е нужно да пишем този код на котела.

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

var filter = (pred, array) => {
    var newArray = [];
за (var i = 0; i 

Забележете как функцията предикат, pred, връща ИСТИНСКО, ако запазим елемента или ЛЕЖНО, ако го хвърлим.

Ето как да използвате филтър за филтриране на нечетни номера:

var isOdd = x => x% 2! == 0;
var числа = [1, 2, 3, 4, 5];
var oddNumbers = филтър (isOdd, числа);
console.log (oddNumbers); // [1, 3, 5]

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

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

Тази функция обикновено се нарича сгъване във функционалните езици.

var Redu = (f, старт, масив) => {
    var ac = старт;
    за (var i = 0; i 

Функцията за намаляване приема функция за намаляване, f, начална стойност на старта и масив.

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

Един пример ще ни помогне да разберем как работи:

var add = (x, y) => x + y;
var стойности = [1, 2, 3, 4, 5];
var sumOfValues ​​= намаление (добавяне, 0, стойности);
console.log (sumOfValues); // 15

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

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

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

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

Моят мозък!!!!

Стига засега.

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

Напред: Част 5

Ако това ви е харесало, щракнете върху по-долу, така че другите хора да виждат това тук на Medium.

Ако искате да се присъедините към общност на уеб разработчици, които се учат и си помагат взаимно да разработят уеб приложения с помощта на функционално програмиране в Elm, моля, разгледайте моята група във Facebook, Learn Elm Programming https://www.facebook.com/groups/learnelm/

Моят Twitter: @cscalfani