функціональний JavaScript

  1. змінні
  2. оголошення функцій
  3. виклик функцій
  4. контекст
  5. Каррінг і часткове застосування
  6. сигнатура функцій
  7. композиція
  8. функтор
  9. Будівник обчислень aka "Монада"
  10. Maybe
  11. Either
  12. Future

Draft !!!

Обережно! Із слабкими нервами, холерикам і вагітним не читати - виносить мозок, змінює свідомість!

змінні

function func (fn, arg1, arg2) {return function () {return fn.call (null, arg1, arg2); }; }

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

З появою змінної з'являється залежність у часі до і після присвоювання. Проблема в тому що змінні приносять в систему поняття стану. Як правило кожне стан приносить нам по два методи для роботи з ним, класичний приклад, malloc і free, open і close, getCanvas і freeCanvas і т.д.



Кожну з перерахованих функцій треба викликати в правильному порядку і при тому, якщо не викликати функцію звільнення, то з'являються проблеми часто складно вловимі. Тому в світі стільки жахів з витоком пам'яті і безліч методів боротьби з ним. З виділенням пам'яті ціла епопея, тому люди придумали збирачі сміття які вирішують створену проблему замість програмістів, місцями не дуже добре, привіт stop world. Але ось проблема, збирача сміття немає для файлів, немає для полотна ... для багатьох речей немає.

Приклад з зміною стану:

var s = 8, q = 3; function badFunction () {// ... s = s - 2; // ... q = 1 / s; // ... return q + s; } BadFunction ();

По-перше, в прикладі видно погані імена змінних 🙂 по-друге, видно, як функція звертається до змінної s і змінює її стан. Так що буде, якщо змінна s матиме значення 2? Так, вірно програма "вилетить" з виключенням, але тільки в поточному event loop. Наступний код, який буде читати значення s і q, отримає значення в НЕ Консистентне стані, тобто інші частини програми можуть не очікувати таких значень і можуть працювати невірно.

Тому бажано не використовувати змінні або використовувати їх в найменшій області видимості, наскільки це можливо - чим менше коду має доступ до змінної, тим краще. В ідеалі час життя змінної повинно бути в межах 5-15 ліній коду. Час життя змінної це "шлях" від її створення до останнього використання. Але так як цей запис про функції, то повернемося до них.

оголошення функцій

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

Оголошення функції споріднене оголошенню звичайної змінної:

var square = function (x) {return x * x; };

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

function square (x) {return x * x; };

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

var square = function square (x) {return x * x; };

виклик функцій

var add = function (x, y) {return x + y; } Add (1, 2);

Маючи функцію, збережену в змінної або просто певну в поточній або доступною області видимості, її можна використовувати власне для чого вона і призначалася, іншими словами викликати її з необхідними параметрами і отримати очікуваний результат. Є кілька способів, як це можна зробити. Перший метод власне класичний: використовуючи дужки funcName (x, y), де funcName ім'я функції а x і y передаються параметри (аргументи). Власне результат своєї роботи функція повертає "лівіше", і якщо його не привласнити змінної або відразу передати як аргумент іншої функції, то він буде втрачено.

var result = add (3, 6); // => result = 9;

В JavaScript функція є по суті об'єктом і має деякі додаткові методи. Перший з них це метод call.

var result = add.call (null, 3, 6); // => result = 9;

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

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

var result = add.apply (null, [3, 6]); // => result = 9;

Є ще один метод, але він не зовсім пов'язаний з викликом функцій, швидше за зі створенням нової функції. Називається цей метод bind і він також дозволяє "мертво" прив'язати контекст і параметри до функції. Якщо параметри такі як - вони будуть додані до кожного виклику нової функції, причому встануть перед тими, які вказані при виклику. наприклад:

var add1 = add.bind (null, 1); var result = add1 (2); // => result = 3;

В даном прикладі видно як за допомогою методу bind була створена нова функція, прив'язана до null об'єкту і також продемонстровано, як можна зробити часткову передачу параметрів. Тобто прив'язали аргумент 1 до функції.

Ще один досить часто використовуваний фокус, коли функція відразу ж після оголошення виповнюється. Це дозволяє приховати змінні і логіку які не потрібні в інших частинах програми, а також створювати об'єкти, в які мають власні "приватні" змінні / методи:

var module = (function (window) {// some logic here return {exported: 1;};}) (window);

контекст

this

У мові Сі є таке поняття як структури (struct) вони зручні тим, що дозволяють з простих типів створити складну структуру з полями, через які можна звернеться до значення.

struct Person {char * name; char * lastName; };

Програмісти досить швидко зрозуміли, що таким способом досить зручно збіднювати пов'язані значення. Ось тільки було складно простежити хто змінює поля структури (щось схоже на глобальні змінні). У зв'язку з цим вирішили структури і функції, які з ними працюють, поміщати в один файл. Стало зручніше, але треба було стежити за тим щоб хтось випадково не змінив стану змінних в структурі, а писати функції, на зразок цієї, не дуже зручно:

void setName (Person * person, const char * name) {person-> name = name; }

До того ж, завжди першим параметром виступала структура (точніше покажчик на неї), яку потрібно змінити. І тоді винайшли "класи". Загалом, додали можливість в структуру додавати і функції (методи), при цьому при виконанні функції їй автоматично передавався покажчик на поточний екземпляр і стали іменувати його this.

void Person :: setName (const char * name) {this-> name = name; }

Класи мають і багато інших булочок, але вони нам тепер не цікаві. Яке це має відношення до JavaScript? Дуже навіть пряме. Автори JavaScript теж вирішили так зробити, тільки ось в JavaScript немає класів (традиційних), зате тут є об'єкти. Ось автори і вирішили, що кожна функція завжди має "змінну" this, яка вказує на поточний об'єкт. Якщо об'єкта немає, то вказує на глобальний (в браузерах window), що іноді призводить до трохи сумних наслідків (тому в нових версіях поведінку трохи змінили, привіт use strict). Ось як це виглядає в JavaScript:

var someObj = {name: "Ivan", lastName: "Petrov", setName: function (name) {this.name = name; }}; someObj.setName ( "Vasil");

Виклик функції яка визначена в класі, робиться приблизно так само як і звернення до значення поля. Якщо дуже по-простому то контекст функції буде рівним тому, що йде до точки перед ім'ям функції. А тепер фокус:

var secondObj = {name: "Elena"}; secondObj.setName = someObj.setName; secondObj.setName ( "Vasil");

Як поведе себе функція скопійована в іншого об'єкта? Вірно! Чи зміниться стан secondObj.name, так як контекст функції буде значення secondObj. Іноді є потреба передати функцію як аргумент в іншу функцію.

function doMagic (setName, name) {setName (name); }; doMagic (secondObj.setName, "Vasil");

Приклад працювати не буде. Проблема в тому, що передається функція як змінна, і у неї немає контексту (немає точки при виклику), точніше є глобальний, але це не те що потрібно. Рішення проблеми описано вище, а саме метод bind. Для того щоб запрацювало потрібно зробити так:

doMagic (secondObj.setName.bind (secondObj), "Vasil");

Або ж є можливість взагалі створити функцію і потім застосовувати її в контексті будь-якого об'єкта. Тут, до речі, метод bind виповнюється в контексті функції setName тому він знає до якої функції прив'язувати контекст.

function setLastName (lastname) {this.lastName = lastname; } SetLastName.bind (someObj) ( "Vasil"); setLastName.bind (secondObj) ( "Vasil")

У прикладі так само продемонстровано, що результат, який повертає функція, можна використовувати відразу. Тобто в даному прикладі метод bind повертає нову функцію, яка відразу ж викликається. Аналогічно можна робити з об'єктами, якщо функція повертає об'єкт можна відразу його використовувати, як власне зроблено в jQuery . Даний підхід називається ланцюжок викликів (chaining) і буває дуже корисний, але у функціональному підході його замінює композиція.

Каррінг і часткове застосування

var fn = curry (function (a, b, c) {// ...});

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

function sum (a, b) {return a + b; }

Що робити, якщо нам часто треба підсумовувати одне число до іншого, і при тому одне число завжди одне й те саме? Треба десь щось запам'ятати це значення. Один з варіантів це використовувати замикання і зробити так:

function sum (a) {return function (b) {return a + b; }; } Var increment = sum (1), decrement = sum (-1); increment (10); // => 11;

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

У заголовку згадані два терміни: карірованіе і часткове застосування, вони трохи схожі, але все ж відрізняються. Часткове застосування це коли у нас є функція, яка очікує кілька параметрів, але перші параметри нам відомі і ми їх застосовуємо, наприклад за допомогою методу bind:

function sum3 (a, b, c) {return a + b + c; } Var sumBase3 = sum3.bind (null, 3); sumBase3 (3, 4); // => 10; sumBase3 (3); // => NaN;

Тепер при виконанні функції sum3 і передачі їй залишкових параметрів буде виконано підсумовування. Тут таїться відміну від каррінг якщо викликати sumBase3 з одним аргументом, то підсумовування буде виконано з помилкою, з тієї причини, що останній аргумент не був переданий. У випадку з карірованіем такого не станеться. Карірованая функція так само чекає всіх аргументів, тільки ось підсумовування НЕ буде викликано до тих пір, поки всі аргументи не будуть отримані:

var sum3 = curry (function (a, b, c) {return a + b + c;}); var sumBase3 = sum3 (3); sumBase3 (3, 4); // => 10; sumBase3 (3) (4); // => 10; sumBase3 (3) () (4); // => 10; sumBase3 () (3, 4); // => 10; sumBase3 (3); // => function;

Ось таким он нехитрим способом можна зменшити кількість параметрів у функціях.

сигнатура функцій

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

Перший і самий не цікавий нам підхід, який використовується в ActionScript (частина сімейства ECMAScript ). У ньому використовується типізація і параметри описуються більш явно, приклад:

function sum3 (a: Int, b: Int, c: Int): Int {return a + b + c; }

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

Наступний, більш JavaScript підхід, являє JSDoc . Підхід більш кращий, тому що дозволять не тільки описати сигнатуру функцій, але і додаткову інформацію, на підставі якої можна згенерувати документацію. Більш того, деякі IDE вміють читати цей формат і використовувати для підказок при розробці, що досить зручно. Основний мінус так це те, що він досить багатослівний, і іноді читати суміш з документації та коди, які не дуже-то і зручно. приклад:

/ ** * сумуються три числа * * @param {Integer} a перший аргумент * @param {Integer} b другий аргумент * @param {Integer} c останній аргумент * @return {Integer} * / function sum3 (a, b, c) {return a + b + c; };

І останній варіант, на якому і зупинимося. Призначений він для опису тільки самих сигнатур і не більше. Чи не багатослівний, і немає підтримки в IDE (поки немає). Почнемо з того, що він "вкрадений" в правильних функціональних мов де каррінг у всіх функцій за замовчуванням. Тому далі будемо розглядати всі функції як каррінг. Приклад сигнатури:

// + sum3 :: Int -> Int -> Int -> Int function sum3 (a, b, c) {return a + b + c; };

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

// + toUpperCase :: String -> String function toUpperCase (s) {return s.toUpperCase (); }

Більш складний приклад:

// + map :: (a -> b) -> [a] -> [b] function map (fn, arr) {return arr.map (fn); };

Думаю, цей випадок треба більш детально розглянути. Типів a і b не існує в природі JavaScript, це просто мнемонічне позначення що тип може бути будь-хто. Квадратні дужки навколо означають, що це масив елементів a. Так само з останнього прикладу видно (a -> b), що перший аргумент функції є функція в яку буде переданий один елемент з масиву [a], і вона повинна повернути значення b, яке згодом буде поміщено в масив елементів [b]. Кілька прикладів для самостійного вивчення:

// + indexOf :: a -> [a] -> Int function indexOf (value, a) {return a.indexOf (value); }; // + reduce :: (b -> a -> b) -> b -> [a] -> b function reduce (fn, acc, a) {return a.reduce (fn, acc); }; // + charAt :: Int -> String -> String function charAt (i, s) {return s.charAt (i); };

композиція

var oneAndSecondFn = compose (secondFn, oneFn);

Композиція - процес застосування однієї функції до результату інший.

fn1 (fn2 (fn3 (value))); // рівносильно compose (fn1, fn2, fn3) (value);

Щось схоже існує вже досить давно в * nix системах і називається конвеєром (pipe).

$ Cat file.txt | grep ^ boo | foo

Сенс тут в тому, що для досягнення кінцевого результату використовується кілька маленьких утиліт, які роблять маленький, але необхідний шматок роботи. Як в цьому прикладі, одна утиліта читає текстовий файл cat file.txt і виводить на екран, але висновок на екран перехоплений і спрямований на вхід іншої утиліти grep ^ boo, яка в свою чергу фільтрує отримані дані і знову виводить на екран, де вони які знову перехоплюються і передаються на вхід утиліти foo, яка робить з ними теж щось світле і гарне, і після знову виведе на екран але на цей раз вже успішно, так як висновок не перенаправлений, тому користувач бачить результат роботи.

Аналогічно працює функція compose: передані їй функції вона зберігає в масиві і повертає нову функцію, і при її виклику буде виконуватися остання функція з переданих в compose і їй же будуть передані аргументи. У свою чергу результат роботи останньої функції буде передано передостанній. І так до самої першої функції, результат якої буде повернений. За допомогою такого підходу можна необхідну логіку декомпозировать (розбити на більш дрібні частини), в результаті у нас буде збільшено перевикористання коду, а значить Багам буде складніше сховатися і розробка буті йти швидше.

Дуже важливо відзначити, що композиція / декомпозиція сприяє більш абстрактному коду. Абстракція в свою чергу сприяє швидкій розробці, (але!) Збільшує час на навчання. Іншими словами менш досвідчені розробники витрачатимуть більше часу на навчання.

функтор

var values ​​= [1, 2, 3, 4, 5] .map (function (item) {return item + 1;}); values; // => [2, 3, 4, 5, 6]

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

Если поглянуті на него з немного більшої висоти, то функтор це метод який знає внутрішній устрій об'єкта (в нашому випадку це Array), вміє витягти звідти кожен елемент, передати в обробну функцію і оброблені елементи помістити назад в об'єкт такого ж типу. Це можна ипользовать, наприклад, для DOM або структури дерева. Визначити метод map, який обходить дерево, застосовує функцію для кожного елемента і знову будує аналогічне дерево з повернутих значень.

lentghTree = namesTree.map (function (name) {return name.length;}); * / \ / \ * "Vova" / \ / \ / \ / \ "Jon" "Nike"

Трансформується в:

* / \ / \ * 4 / \ / \ / \ / \ 3. 4

І ось останній ще більш приземлений приклад:

function Identity (value) {this.value = value; } Identity.prototype.map = function (fn) {// передаємо значення і створюємо новий // екземпляр з таким же класом return new Identity (fn (this.value))};

Будівник обчислень aka "Монада"

Hey Underscore, You're Doing It Wrong!

Дуже цікаву доповідь , Рекомендую подивитися, хоча якщо немає часу і / або бажання дивитися 40 хвилинну доповідь, нижче буде короткий зміст. Так в чому ж проблема самого Underscore ? Underscore позиціонується як бібліотека з функціональним підходом. але проблема тут криється в параметрах, точніше їх послідовності. Вище була описана основа функціонального підходу - каррінг і композиція, так ось в Underscore складно робити композицію через послідовність аргументів. Тому що в ньому при виклику методу треба вказати першим аргументом дані які потрібно обробити і після функцію яка буде обробляти. Це виключає можливість використовувати каррінг для створення необхідних функцій на льоту.

var isOdd = function () {return item% 2; }; var filtredData = _.filter ([1, 3, 4, 5, 6], isOdd);

В цьому і криється проблема. Тут нам потрібна змінна для збереження даних, які будуть оброблені далі. Що якщо ми зробимо функцію такого вигляду:

var filter = curry (function (fn, arr) {return arr.filter (fn);});

Перше що потрібно відзначити ми зробили її каррінг і друге поміняли місцями аргументи. І якщо тепер зробимо так:

var filterOdd = filter (isOdd);

У нас буде функція, якої можна передати будь-який масив (або не дуже масив а все у чого є метод filter) і вона поверне фільтрована. Думаю, її міць поки не дуже видно - тоді трохи ускладнити.

var showResult = curry (function (selector, data) {// приклад для простоти з використанням jQuery var $ el = $ (selector); $ .each (data, $ el.append.bind ($ el));}), processData = function (item) {return item + (item * 42); }, ProcessAndShow = compose (showResult ( '# data_block'), map (processData), // "православний" і каррінг map filterOdd // або filter (isOdd)); processAndShow ([1, 3, 4, 5, 6]);

Невелике нагадування, що функції, згодовані compose, будуть виконані в зворотному порядку. Іноді в бібліотеках для спрощення виконують функцію pipe, в якій аргументи-функції йдуть в прямому порядку.

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

Більш того з таким підходом можна робити і асинхронні операції:

var getJsonByUrl = function (url) {// ... тіло приховано для інтриги}; var getProcessAndShow = compose (showResult ( '# data_block'), map (processData), // "православний" і каррінг map filterOdd, // або filter (isOdd) getJsonByUrl); getProcessAndShow ( '// myserver / getdata.json');

Проміс? Колбекі? Асинхронний код? Ні не чув. Але ось тут є невелика проблема: а що якщо сервер недоступний? Або дані повинен був ввести користувач, а їх немає? Ой, біда ...

Maybe

Хоча якщо подумати не така вже й біда, все можна вирішити в функціональному стилі. Для цього в нас є (будуть?) Монади! Подивимося, як можна визначити Монада maybe:

var Maybe = function (value) {this.value; } // іноді можна зустріти з ім'ям fmap Maybe.prototype.map = function (fn) {if (this.value! == null && typeof (this.value)! == 'undefined') {return new Maybe (fn ( this.value)); } Else {return this; } Return new} // + maybe :: a -> ma var maybe = function (val) {return new Maybe (val); }

Це сильно спрощень варіант, але ідею передає дуже добре. Сенс в тому, що дані ми не будемо передавати самі по собі, а будемо їх обертати в контейнер, в якому визначено метод map. І при використанні цього методу спочатку перевірити, чи є власне, дані, і в разі якщо їх немає, то просто не викликати функцію, і код у нас ставати:

var getProcessAndShow = compose (map (showResult ( '# data_block')), map (map (processData)), // витягаємо масив а з нього елементи map (filterOdd), // витягаємо з монади масив maybe, getJsonByUrl); getProcessAndShow ( '// myserver / getdata.json');

Ой, скільки ж в нас з'явилося mapов, для чого ж їх стільки потрібно? Ну що ж, спробуємо розібратися. Вся справа в тому, що maybe повертає контейнер з даними (в даном випадку нехай буде буде масив), і щоб їх витягти, ми використовуємо метод map. Витягнуті дані фільтруємо. Далі, після вилучення та фільтрації, дані знову запаковуються, і знову отримуємо Монада, тому перед обробкою даних знову витягаємо дані з монади, а після і з масиву. Обробляємо, результат знову запаковується двічі (в масив, і в Монада / контейнер). І остання команда: власне перед виведенням масиву витягаємо з монади і передаємо оброблювачу.

Уявімо випадок, що дані ми не отримали, тобто в maybe нічого не передали, при кожному map для монади просто буде перевірка, чи є дані (а їх немає), тому обробники (filterOdd, map (processData) ...) і не будуть виконані.

Either

Добре, тепер у нас є перевірка на порожні значення maybe, але що якщо в декількох місцях ці значення можуть бути відсутні? Як знайти місце де немає даних? На допомогу приходить містер Propper монада Either. Її сенс дуже схожий на Монада Maybe, тільки з тією різницею, що ми можемо повернути осмислений текст помилки. Так само ця монада зручна у функціях, де може бути виняткова ситуація, і замість викидання помилки можна просто повернути Монада без значення, але з інформацією про помилку, що допоможе локалізувати проблему і не дати коду що ні будь нам зламати.

Приклад реалізації:

var Either = function (lvalue, rvalue) {this.lvalue; this.rvalue; } Either.prototype.map = function (fn) {if (this.rvalue! == null && typeof (this.rvalue)! == 'undefined') {return new Either (this.lvalue, fn (this.rvalue) ); } Else {return new Either (this.lvalue, undefined); } Return new} // + either :: a -> b -> mb | a var either = curry (function (lval, rval) {return new Either (lval, rval);});

Напишемо коротку функцію для демонстрації:

var getElement = function (selector) {return either ( 'getElement :: Element "' + selector + '" not found', document.querySelector (selector)); }; var property = curry (function (name, object) {return object [name];}); var getProcessAndShow = compose (map (showResult ( '# data_block')), map (map (processData)), // витягаємо масив а з нього елементи map (JSON.parse), map (property ( 'innerHtml')), getElement ); getProcessAndShow ( '# elID');

Щоб уникнути повторного написання всюди map, може бути створена функція mcompose, яка автоматично для всіх функцій отримує дані, тобто дописує map.

var processAndShow = mcompose (showResult ( '# data_block'), map (processData), JSON.parse, property ( 'innerHtml'),); processAndShow (getElement ( '# elID'));

Звертаю увагу на те, що тепер метод getElement винесено з композиції, так як йому на вхід потрібно "чисте" значення, а не монада, і повертає він Монада.

Навіть якщо не використовувати функціональний підхід, то використовувати either або maybe в розробці може підняти стабільність на новий уровань. Завжди возаращая Манадо як результат можна прибрати купу умов які перевіряють чи є резульат від функції, а також можна забути про try catch (яким часто зловживають і обертають великі шматки коду, і не обробляють Пойманов виняток). такий собі NullObject патерн. Для зручності монадам можна додати методи для отримання даних, наприклад, getOrElse, де єдиним аргументом буде занчение за замовчуванням, на випадок якщо монада "порожня". Або метод getOr, де аргументом буде функція яка буде виконана в разі відсутності даних в монаді і результат у вигляді результату з цієї функції.

var getProduct = function (id) {if (! id) {return either ( 'Missing product ID', null); } Else {return either ( 'Could not fetch product with ID:' + id, db.getProduct (id)); }} Var getDefaultProduct = function () {return {name: "", // other properties}; } Var product = getProduct ( 'black underpants'); showName (product.getOrElse ({name: ""}). name); showName (product.getOr (getDefaultProduct) .name);

Future

TBD

Так що буде, якщо змінна s матиме значення 2?
Яке це має відношення до JavaScript?
Колбекі?
Асинхронний код?
Але ось тут є невелика проблема: а що якщо сервер недоступний?
Або дані повинен був ввести користувач, а їх немає?
Будуть?
Як знайти місце де немає даних?

Дополнительная информация

rss
Карта