Функциональное программирование на Javascript: шаблоны и лучшие практики
Я делюсь тем, что открыл, изучил и использовал во время моего небольшого опыта использования JS (ES6 +) и функционального программирования
Перед запуском: настраиваем линтер
Самый важный шаг при написании JS - использование линтера.
Использование такой конфигурации линтера, как Airbnb, может помочь:
- чтобы избежать изменчивости (изменение и переназначение аргументов функции)
- использовать const и позволить var
- использовать новые функции JS (такие как распространение, деструктуризация, стрелочные функции ...)
- для лучшего именования переменных и функций
Линтер-машина также помогает улучшить код: пробелы, отступы, точки с запятой, запятые и т. Д.
Https://www.npmjs.com/package/eslint
Https://github.com/airbnb/javascript
1. Функциональное программирование
а. Почему я предпочитаю заниматься функциональным программированием (ФП)?
Проблемы, с которыми я сталкивался при использовании JS в прошлом:
- Изменчивость
- Примесь
- Побочные эффекты
- Глобальные переменные
- Большая функция или функция как процедура
- Императивный код
Математический фон:
Что я узнал из математики: создавайте короткие, чистые, автономные и лаконичные функции, декомпозируйте и компонуйте, все сводится к единой, общей и полной формуле.
Математические функции действуют как независимые микросервисы, которые дают возможность параллельного выполнения или многопоточности. Никаких мутаций, никаких побочных эффектов, никаких зависимостей, которые приводят к стабильной и предсказуемой системе. Наши программы должны быть такими.
Я хочу, чтобы мои функции действовали как математические функции.
Функциональное программирование дает программированию правильные значения и ведет его на правильный путь: математический путь. Потому что математика чиста, неизменна и декларативна. Математика описывает общие варианты, используя общую короткую формулу. Наша программа должна больше вдохновлять математикой: краткой, лаконичной, общей и чистой.
В контексте JavaScript мышление FP можно использовать для формирования невероятно выразительной природы языка и помощи в написании кода, который является чистым, модульным, тестируемым и кратким, чтобы вы могли быть более продуктивными на работе . - Функциональное программирование на Javascript, Луис Антенсио -
б. Функция просмотра:
Математическая функция:
Функция - это отношение набора входов к набору возможных выходов, где каждый вход связан ровно с одним выходом, а один и тот же ввод всегда дает один и тот же результат.
Абстракция поведения в единой формуле: декларативная.
Математические характеристики функции:
В функциональном программировании ваша главная цель: превратить вашу логику в автономные функции, чтобы уничтожить как можно больше зависимостей. - Объяснение концепций, лежащих в основе парадигмы функционального программирования -
x = ›2 * x =› зависит только от x.
Если x = 3, мы всегда имеем (2 * 3) = 6.
Почему ?
- Детерминированный = ›легко предсказать =› легко проверить и ожидать.
- Если функция сохраняет тот же результат для одного и того же ввода, его можно запоминать: вместо того, чтобы вычислять каждый раз вывод, мы запоминаем уже вычисленный вывод для следующего использования (кеширование).
- Сложная задача делится на небольшие задачи, и в результате получается композиция.
Функция должна возвращать значение:
Поскольку FP работает во многом как математика, функции имеют смысл только тогда, когда они дают полезный результат (не null или undefined), в противном случае, предполагается, что они изменяют внешние данные и вызывают побочные эффекты для происходят. Мы можем различать выражения (функции, производящие значения) и утверждения (функции, которые не производят). Императивное и процедурное программирование в основном состоит из упорядоченных последовательностей операторов, но FP полностью выражен, поэтому пустые функции не служат цели в этой парадигме. - Функциональное программирование на JS - Луис Атенсио -
Одиночная роль и состав:
(a * b) * (c + d) * (e-f) = compose (mul, add, sub)
Функции высшего порядка:
Что мы узнали из математики:
- Делайте короткие, чистые, автономные и лаконичные функции.
- Разложить и составить.
- Декларативный: все сводится к единой, общей и полной формуле.
- Математические функции действуют как независимые микросервисы, которые дают возможность параллельного выполнения или многопоточности.
- Никаких мутаций, никаких побочных эффектов, никаких зависимостей, которые приводят к стабильной и предсказуемой системе.
Принципы FP:
- Декларативная, а не императивная
- Чистые функции
- Неизменность
- Ссылочная прозрачность
Декларативное и императивное:
Сосредоточьтесь на что делать больше, чем как делать!
Чистый:
Чистая функция обладает следующими качествами:
- Это зависит только от предоставленных входных данных, а не от какого-либо скрытого или внешнего состояния, которое может измениться во время его оценки или между вызовами.
- Это не влечет изменений, выходящих за рамки их возможностей, таких как изменение глобального объекта или параметра, переданного по ссылке.
Интуитивно понятно, что любая функция, не отвечающая этим требованиям, считается «нечистой».
Почему Pure?
- Распараллеливание.
- Воспоминание.
- Ленивая оценка.
- Легче отлаживать, изолировать и тестировать.
2. Стрелочные функции
Лямбда-выражения обеспечивают огромное синтаксическое преимущество перед обычными обозначениями функций, поскольку они сокращают структуру вызова функции до наиболее важных частей. Это лямбда-выражение ES6:
num => Math.pow(num, 2)
эквивалентен следующей функции:
function(num) { return Math.pow(num, 2); }
- Функциональное программирование на JS - Луис Атенсио -
Стрелочные функции ⇒ аналогично математическим функциям ⇒ они должны быть краткими, чистыми, простыми, одиночными и чистыми.
3. Распространение / разрушение / отдых
Распространение:
Массив:
// combine/concat arrays const tab1 = [1, 2, 3, 4, 5]; const tab2 = [7, 8, 9, 10, 11]; const tab3 = [...tab1, ...tab2]; // [ 1, 2, 3, 4, 5, 7, 8, 9, 10, 11 ] // add at the end const tab4 = ['a', 'b', 'c']; tab3.push(...tab4); // [ 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 'a', 'b', 'c' ] : like arr3.push.apply(arr3, arr4) // add at the begin const tab5 = ['A', 'B', 'C']; tab3.unshift(...tab5); // [ 'A','B','C',1,2, 3, 4, 5, 7, 8, 9, 10, 11, 'a', 'b', 'c' ] // spread function arguments const spreadFunctionArgs = (x, y, ...args) => { console.log('x : ', x) console.log('y : ', y) console.log('args : ', args) }; const simpleArray = ['a', 'b', 'c', 'd', 'e']; spreadFunctionArgs(1, 2, simpleArray); // args : [ [ 'a', 'b', 'c', 'd', 'e' ] ] spreadFunctionArgs(1, 2, ...simpleArray); // args : [ 'a', 'b', 'c', 'd', 'e' ] spreadFunctionArgs(1, 2, 3, 4, 5, 6); // args : [ 3, 4, 5, 6 ] // get the max & min of an array const numbers = [1, 25, 3, 4, 5] const max = Math.max(numbers) // undefined const min = Math.min(numbers) // undefined const maxNumber = Math.max(...numbers) // 25 : like Math.max.apply(Math, [1, 25, 3, 4, 5]) const minNumer = Math.min(...numbers) // 1
Копия массива (ссылочный тип):
Если вы назначаете таблицу другой переменной и изменяете ее элементы, исходная таблица также изменяется.
Если вы назначаете таблицу переменной или передаете таблицу функции, это будет ссылка на исходную таблицу, скопированную или переданную, а не ее значение.
let array1 = [1, 2, 3]; let array2 = array1; array2[1] = 25; // => this line affect the two tables (1 et 2) array1 = [1, 25, 3]; array2 = [1, 25, 3];
Решение:
const sequence = [1,2,3]; // method 1 const sequenceCopy = sequence.slice(0); // method 2 const sequenceCopy = [].concat(sequence); // method 3 const sequenceCopy = [ ...sequence];
Объект:
// simplify Object#assign // clone const obj = { a: 1 } const copy = Object.assign({}, obj); console.log('copy : ', copy); // copy : { a: 1 } const clone = { ...obj }; console.log('clone : ', clone); // clone : { a: 1 } // immutability of origin const objOrigin = { age: 4 }; const objCopy = { ...objOrigin }; objCopy.age = 1; // mutate the copy console.log('objCopy : ', objCopy.age); // 1 <-- changed console.log('objOrigin : ', objOrigin.age); // 4 <-- not changed (immutable) !
Объединение объектов:
// Object Concat // the spread operator can be used // to shallow copy the properties // from one or more objects into another object. const obj1 = { a: 1, b: 2, c: 3, }; const obj2 = { p: 4, q: 5, r: 6, }; const obj3 = { ...obj1, ...obj2, }; console.log('obj3 : ', obj3); // { a: 1, b: 2, c: 3, p: 4, q: 5, r: 6 }
Изменение объекта:
// state mutate const state = { isFavorite: true, isWishList: true, }; const mutateState = { ...state, isFavorite: false, }; console.log('state : ', state); // state : { isFavorite: true, isWishList: true } console.log('mutateState : ', mutateState); // mutateState : { isFavorite: false, isWishList: true } : mutate only isFavorite
Изменение и расширение объекта:
// original state const state = { isFavorite: true, isWishList: true, }; // mutate and extends const mutateExtendState = { ...state, isFavorite: true, isLogged: false, }; console.log('mutateExtendState : ', mutateExtendState); // mutateExtendState : { isFavorite: true, isWishList: true, isLogged: false }
Состояние реакции и распространение:
// default state this.state = { person: { firstName: '', secondName: '', }, }; this.setState((prevState) => ({ person: { ...prevState.person, firstName: 'Tom', secondName: 'Jerry', }, }));
Деструктуризация:
Деструктурируем и выбираем:
const [elm1, elm2] = ['a', 'b', 'c', 'f', 's']; console.log('elm1 : ', elm1); // a console.log('elm2 : ', elm2); // b // this code is equivalent to : // const elm1 = tab[0]; // const elm2 = tab[1];
Разрушение и пропуск:
const [, , elm3, elm4] = ['a', 'b', 'c', 'd', 'f', 's']; console.log('elm3 : ', elm3); // c console.log('elm4 : ', elm4); // d
Разрушение, пропуск и отдых:
const [, , ...rest] = ['a', 'b', 'c', 'd', 'f', 's']; console.log('rest : ', rest); // [ 'c', 'd', 'f', 's']
Безопасное разрушение:
const [val1, val2, ...val3] = ['a']; console.log('val1 : ', val1); // a console.log('val2 : ', val2); // undefined console.log('val3 : ', val3); // []
Объект:
// Destructing & Rest // Pick what you need : const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4, }; console.log('x : ', x); // 1 console.log('y : ', y); // 2 console.log('z : ', z); // { a: 3, b: 4 } // new object // automatically map x, y and z const m = { x, y, z }; console.log('m : ', m); // m : { x: 1, y: 2, z: { a: 3, b: 4 } } // Spread z const n = { x, y, ...z }; console.log('n : ', n); // n : { x: 1, y: 2, a: 3, b: 4 }
Вложенная деструктуризация объекта:
// nested destructing // define your pattern const options = { size: { width: 100, height: 200, }, items: ['Cake', 'Donut'], extra: true, }; const { size: { // put size here width, height, }, items: [item1, item2], // assign items here title = 'Menu', // not present in the object (default value is used) } = options; console.log('title : ', title); // Menu // console.log(size) // undefined console.log('width : ', width); // 100 console.log('height : ', height);// 200 // console.log(items) // undefined console.log('item1 : ', item1); // Cake console.log('item2 : ', item2); // Donut
Разрушение свойств:
import React from "react"; const Row = ({ firstName, lastName, email, doSomethingAmazing, }) => ( <div> <div> <span> First Name: {firstName} </span> </div> <div> <span> Last Name: {lastName} </span> </div> <div> <span> Email: {email} </span> </div> <button onClick={doSomethingAmazing}>Click me</button> </div> );
4. Карта / Фильтр / Уменьшение
а. карта
карта: преобразовать массив
const sequence = [1, 2, 3, 4, 5]; const doubleSequence = sequence.map((item) => item * 2); // [2,4,6,8,10]
map = ›возвращает новый массив, не затрагивая исходный массив (чистый и без побочных эффектов).
const foods = ['bananas', 'apples', 'orange', 'grape', 'pizza', 'coffee', 'soup']; const upperFoods = foods.map((food) => food.toUpperCase()); console.log(upperFoods); // ["BANANAS", "APPLES", "ORANGE", "GRAPE", "PIZZA", "COFFEE", "SOUP"]
Когда не использовать map ()
Поскольку
map
создает новый массив, использование его, когда вы не используете возвращенный массив, является анти-шаблоном; используйте вместо этогоforEach
илиfor-of
.
Вам не следует использовать
map
, если:
- вы не используете возвращаемый массив; и / или
- вы не возвращаете значение из обратного вызова.
б. фильтр
filter: фильтровать массив и возвращать элементы, удовлетворяющие предикату.
const words = ['one', 'hello', 'yes']; const helloMatched = words.filter((item) => item === 'hello'); // ["hello"]
filter = ›возвращает новый массив, не затрагивая исходный массив (чистый и без побочных эффектов).
filter = ›вернуть undefined, если ни один элемент не удовлетворяет предикату.
// find item const findItem = (tab, query) => tab.filter((item) => item === query); // compare two array and return differences // symetric difference // return elements from tab1 that don't exist // on tab2 and elements from tab2 // that don't exist on tab1 const arrayDifference = (tab1, tab2) => [ ...tab1.filter((item) => findItem(tab2, item).length === 0), ...tab2.filter((item) => findItem(tab1, item).length === 0), ]; const tab1 = [1, 2, 3, 12, 4, 5, 'A', 'B', 'Qwerty']; const tab2 = [1, 2, 78, 3, 189]; console.log('Differences : ', arrayDifference(tab1, tab2)); // [ 12, 4, 5, 'A', 78, 189 ] // array intersections // find common elements const arrayIntersection = (tab1, tab2) => tab1.filter((item) => findItem(tab2, item).length > 0); // Union : merge arrays // and eliminate duplication const arrayUnion = (tab1, tab2) => [ ...new Set([...tab1, ...tab2]), ]; console.log('Intersection : ', arrayIntersection(tab1, tab2)); // [ 1, 2, 3 ] console.log('Union : ', arrayUnion(tab1, tab2)); // [ 1, 2, 3, 12, 4, 5, 'A', 78, 189 ]
Глубоко найди предмет:
// deep arrays const tabObject1 = [{ name: 'Héla', lastName: 'Ben Khalfallah', }, { name: 'Manuel', lastName: 'Job', }, { name: 'Pablo', lastName: 'Picasso', }]; // deep find item const deepFindItem = (tab, searchedItemName) => tab.filter((item) => item.name === searchedItemName); deepFindItem(tabObject1, 'Manuel')
c. уменьшать
Уменьшите до единственного значения.
const authors = ['Lewis', 'Wilson', 'Verne', 'Tolstoy', 'Twain']; const result = authors.reduce((prev, current) => `${prev}, ${current}`); console.log(result); // "Lewis, Wilson, Verne, Tolstoy, Twain" // count number of elements that are not string const totalNotString = (tab) => tab.reduce((total, amount) => ((typeof amount !== 'string') ? total + 1 : total), 0); const tab1 = [1, 2, 3, 12, 4, 5, 'A', 'B', 'Qwerty']; const tab2 = [1, 2, 78, 3, 189]; console.log('Count number of elements that are not string (tab1): ', totalNotString(tab1)); // 6 console.log('Count number of elements that are not string (tab2): ', totalNotString(tab2)); // 5 const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; const flatData = data.reduce((accumulator, next) => accumulator.concat(next), []); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] const fruitBasket = [ 'banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig', ]; const transformFruit = fruitBasket.reduce((accumulator, next) => { accumulator[next] = (accumulator[next] || 0) + 1; return accumulator; }, {}); // { banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1 }
d. Резюме:
map = ›вернуть новый массив =› повторить и изменить массив = ›вернуть новый массив, не изменяя исходный!
filter = ›возвращает новый массив, отфильтрованный по условию, не изменяет исходный!
уменьшить = ›уменьшить массив до значения.
5. Цепочка / Труба / Составить
а. цепочка
const tabObject2 = [{ name: 'Héla', lastName: 'Ben Khalfallah', note: 15, }, { name: 'Dan', lastName: 'Abramov', note: 17, }, { name: 'Steve', lastName: 'Jobs', note: 16, }, { name: 'Albert', lastName: 'Einsten', note: 18, }, { name: 'Kyle', lastName: 'Simpson', note: 19, }]; // chaining operations const filterSortChain = tabObject2 .filter((item) => item.name.toLowerCase().includes('e')) .sort((item, next) => { if (item.name < next.name) { return -1; } if (item.name > next.name) { return 1; } return 0; }); console.log('Sort Chain : ', filterSortChain); // [ { name: 'Albert', lastName: 'Einsten', note: 18 }, { name: 'Kyle', lastName: 'Simpson', note: 19 }, { name: 'Steve', lastName: 'Jobs', note: 16 }, ] const totalChain = tabObject2 .filter((item) => item.name.toLowerCase().includes('a')) .reduce((total, item) => total + item.note, 0); console.log('Total Chain : ', totalChain); // 49 = 18 + 14 + 17
Результат фильтрации будет входом sort.
Результат фильтра будет входом уменьшения.
Мы можем создать цепочку, соблюдая арность функции (количество аргументов) и типы аргументов.
б. трубка
const pipe = x = ›y =› f = ›g =› g (f (x) (y))
const pipe = (x) => (y) => (f) => (g) => g(f(x)(y)); // check if value match pattern // ["99988", index: 0, input: "99988", groups: undefined] => match // null : not match const match = (pattern) => (value) => value.match(pattern); // return an array // get value length const length = (value) => (value ? value.length : 0); // pattern matcher const matcher = (pattern) => (value) => pipe(pattern)(value)(match)(length) > 0 || false; // number matcher const numberRegx = /^[0-9]+$/; console.log('19009.2 is number : ', matcher(numberRegx)('19009.2')); // false console.log('99988 is number : ', match(numberRegx)('99988')); // true console.log('89A is number : ', matcher(numberRegx)('89A')); // false
Конвейеры в JS похожи на конвейеры Unix / Linux. Выход каждой функции становится входом для следующей.
c. сочинять
const compose = (f, g) = ›x =› y = ›f (g (x, y))
// f and g are functions and // x is the value // being "piped" through them const compose = (f, g) => (x) => f(g(x)); const toUpperCase = (x) => x.toUpperCase(); const exclaim = (x) => `${x}!`; const shout = compose(exclaim, toUpperCase); console.log('shout : ', shout('send in the clowns')); // shout : SEND IN THE CLOWNS!
6. Паттерны проектирования FP
а. Каррирование и частичное приложение (работает как шаблон)
Вызов обычной функции JS разрешен для выполнения с отсутствующими аргументами (до завершения).
const somme = (a, b, c) = ›a + b + c
somme (5) = ›somme (5, не определено, не определено)
Каррированная функция выполняется только после завершения аргументов:
const somme = a => b => c => a + b + c const oneSomme = somme(2)(3)(4); console.log(oneSomme); // 9
Почему?
- Ленивая оценка.
- Функция как шаблон.
Как?
Каррирование работает за счет естественного закрытия. Замыкание, созданное вложенными функциями для сохранения доступа к каждому из аргументов. Таким образом, внутренняя функция имеет доступ ко всем аргументам.
Контекст сохраняется между закрытием!
Разделение функции с 3 аргументами на 3 функции с одним аргументом для каждой:
Частичное применение аргументов!
Каррирование - это метод оценки функции с несколькими аргументами в последовательности функций с одним аргументом.
Другими словами, когда функция вместо того, чтобы принимать все аргументы за один раз, принимает первый и возвращает новую функцию, которая принимает вторую и возвращает новую функцию, которая принимает третью, и так далее, пока все аргументы не будут выполнено. - Википедия -
Функция как шаблон:
Универсальный сопоставитель:
const match = pattern => value => (value.match(pattern) && value.match(pattern).length > 0) || false; // return an array const numberRegx = /^[0-9]+$/; const isNumber = match(numberRegx); const floatRegx = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$/; const isFloat = match(floatRegx); const alphanumeric = /^[a-z0-9]+$/i; const isAlphanumeric = match(alphanumeric); console.log('is number : ', isNumber('12')); console.log('is number : ', isNumber('15,34')); console.log('is float : ', isFloat('0.32')); console.log('is float : ', isFloat('AA')); console.log('is alphanumeric : ', isAlphanumeric('AA')); console.log('is alphanumeric : ', isAlphanumeric('BB,12'));
Общий регистратор:
const log = level => (message, ...optionals) => { switch (level) { case 'INFO': console.info(message, optionals) break case 'WARN': console.warn(message, optionals) break case 'ERROR': console.error(message, optionals) break default: break } }; const infoLogger = log('INFO') // not executed const errorLogger = log('ERROR') // not executed const warnLogger = log('WARN') // not executed const Logger = { infoLogger, errorLogger, warnLogger, }; export default Logger;
Никогда не дублируйте код, а создавайте универсальные элементы многократного использования:
// sort by criteria const sortBy = (property) => (a, b) => { if (a[property] < b[property]) { return -1; } if (a[property] > b[property]) { return 1; } return 0; }; const users = [ { name: 'aaName', lastname: 'cclastName', note: 12 }, { name: 'ccName', lastname: 'aalastName', note: 13 }, { name: 'bbName', lastname: 'bblastName', note: 7 }, ]; const sortByName = sortBy('name'); const sortedByNameUsers = users.sort(sortByName); infoLogger('sort by name: ', sortedByNameUsers); const sortByNote = sortBy('note'); const sortedByNoteUsers = users.sort(sortByNote); infoLogger('sort by note: ', sortedByNoteUsers);
Конвертер общих единиц:
const converter = (toUnit) => (factor) => (offset) => (input) => { const converterOffset = offset || 0; return [((converterOffset + input) * factor).toFixed(2), toUnit].join(' '); }; const milesToKm = converter('km')(1.60936)(undefined); const poundsToKg = converter('kg')(0.45460)(undefined); const farenheitToCelsius = converter('degrees C')(0.5556)(-32); console.log(milesToKm(10)); // "16.09 km" console.log(poundsToKg(2.5)); // "1.14 kg" console.log(farenheitToCelsius(98)); // "36.67 degrees C"
б. Функторы (обработка ошибок в FP)
Блок безопасного увеличения:
Generic NumberBox:
const NumberBox = (number) => ({ applyFunction: (fn) => { if (typeof number !== 'number') { return NumberBox(NaN); } return NumberBox(fn(number)); }, value: number, }); NumberBox(5) .applyFunction((v) => v * 2) .applyFunction((v) => v + 1).value; // 11 NumberBox({ v: 5 }) .applyFunction((v) => v * 2) .applyFunction((v) => v + 1).value; // NaN
Смело выполняйте операцию по данному номеру.
Безопасное соединение.
Вместо того, чтобы передавать значение функциям, мы передали функции в значение (или функцию-заполнитель).
Их практическая цель - создать контекст, который позволяет безопасно манипулировать значениями и применять операции к ним без изменения исходного значения. Это очевидно по тому, как map преобразует один массив в другой без изменения исходного массива; эта концепция одинаково применима к любому типу контейнера. - Луис Атенсио - Объяснение функций Javascript -
map :: (A - ›B) -› Wrapper (A) - ›Wrapper (B) // # A
Мы переименовываем applyFunction в map:
Любая реализация карты должна иметь следующие свойства: объединить и составить.
карта не должна иметь побочных эффектов. Я мне нужно изменить только значение функтора и ничего больше:
Закон тождества: отображение функции тождества (v = ›v) возвращает точно такой же функтор:
NumberBox (5) .map (v = ›v)) == NumberBox (5)
Функтор:
- Имеет метод карты, который ожидает функцию.
- Функция карты вернет другой функтор того же типа (как исходный функтор).
Можно сделать вывод, что стандартные типы Scala, такие как List, Option и другие, которые определяют метод карты, являются функторами.
(Любой тип f с такой функцией (map) является функтором с одним дополнительным ограничением: функция map должна сохранять «структуру» значения, которое она отображает.) - Шаблоны проектирования Scala - Иван Николов -
Функторы для безопасного управления значениями и применения операций к ним.
c. Монады (обработка исключений в FP)
Может быть :
Решение:
if (user1 && user1.adresse && user1.adresse.city && user1.adresse.city.departement) { const { adresse } = user1 const { city } = adresse const { departement } = city // or : const { adresse: { city: { departement} } } = user1; console.log('user departement : ', departement) }
Несколько условий :(
Может быть, Монада:
const isNothing = value => value === null || typeof value === 'undefined'; const Maybe = value => ({ map: fn => isNothing(value) ? Maybe(null) : Maybe(fn(value)), value, });
Монада - это функтор!
Он сохраняет контекст: результат карты передается автоматически (как цепочка).
Чистая и декларативная обработка ошибок!
const isNothing = value => value === null || typeof value === 'undefined'; const Maybe = value => ({ map: fn => isNothing(value) ? Maybe(null) : Maybe(fn(value)), value, }); const simpleMayBe = Maybe('George') .map(x => x.toUpperCase()) .map(x => `Mr. ${x}`) .value console.log('simpleMayBe : ', simpleMayBe) // simpleMayBe : Mr. GEORGE
Может быть, значение по умолчанию или запасной вариант:
const isNothing = value => value === null || typeof value === 'undefined'; const Maybe = (value) => ({ map: (fn) => (isNothing(value) ? Maybe(null) : Maybe(fn(value))), getOrElse: (defaultValue) => (isNothing(value) ? defaultValue : value), value, }); const mayBeValue = Maybe(user.adresse) .map((adresse) => adresse.city) .map((city) => city.departement) .getOrElse('unknown address'); console.log('user mayBeValue : ', mayBeValue); // user mayBeValue : Paris const mayBeFailValue = Maybe(user.adresse1) .map((adresse1) => adresse1.city) .map((city) => city.departement) .getOrElse('unknown address'); console.log('user mayBeFailValue : ', mayFailBeValue); // user mayBeFailValue : unknown address
Контекст сохранен, результат передан через карту:
const get = key => value => value[key] const getStreet = user => Maybe(user) // user .map(get('address')) // adresse .map(get('street')) // street .getOrElse('unknown address') getStreet({ name: 'Hela', firstname: ‘Ben Khalfallah', address: { street: 'Fontenay Sous Bois', number: '94120', } }) // 'Fontenay Sous Bois' getStreet({ name: 'Paris', }) // 'unknown address' getStreet() // 'unknown address'
Может быть против функтора:
Либо (обработать исключение в FP):
Либо (Ошибка (возможно), Успех (возможно))
Либо (слева (возможно), справа (возможно))
Парсер ответа сервера try / catch:
Многоразовый API:
const ServerSuccess = (value) => ({ map: (fn) => ServerSuccess(fn(value)), catch: () => ServerSuccess(value), value, }); const ServerError = (value) => ({ map: () => ServerError(value), catch: (fn) => ServerSuccess(fn(value)), value, }); const serverTryCatch = (fn) => (value) => { try { return ServerSuccess(fn(value)); } catch (error) { return ServerError(error.message); } }; const ResponseFailReason = () => {...} const ResponseSuccessData = () => {...} const ResponseParser = serverTryCatch((response) => { const { error, } = ResponseFailReason(response); if (error) { throw new Error(error); } return response; }); const ResponseFormatter = (response) => ResponseParser(response) .map((value) => ( { data: ResponseSuccessData(value), error: null, } )) .catch((error) => ( { data: null, error: { status: 'NOK', failReason: error, }, } )) .value;
Пример вызова:
const { data, error, } = ResponseFormatter(Backend.Call());
d. Замена переключателя / корпуса функциональным кодом:
Переключатель констант / регистр (с использованием Dico или Object):
const dogSwitch = (breed) => ({ border: 'Border Collies are good boys and girls.', pitbull: 'Pit Bulls are good boys and girls.', german: 'German Shepherds are good boys and girls.', })[breed]; dogSwitch('border'); // "Border Collies are good boys and girls."
Https://medium.com/chrisburgin/rewriting-javascript-replacing-the-switch-statement-cfff707cf045
Переключатель переменных / регистр:
const matched = (x) => ({ on: () => matched(x), otherwise: () => x, }); const match = (x) => ({ on: (pred, fn) => (pred(x) ? matched(fn(x)) : match(x)), otherwise: (fn) => fn(x), }); match(50) .on((x) => x < 0, () => 0) .on((x) => x >= 0 && x <= 1, () => 1) .otherwise((x) => x * 10); // => 500 // we can return a Maybe and continue chain with map // which is equivalent to (switch + staff after switch)
Это также может заменить блок if / else.
Использование стрелочных функций делает код кратким и понятным: только одна инструкция за строкой.
8. Заключение:
Написание функции кода похоже на написание математической функции:
- Нет побочных эффектов
- Чистый
- Единственная ответственность
- Неизменность
- Нет общих данных
- Автономный
- Декларативная
- Разложить, составить, связать
Функции должны действовать как независимые и автономные микросервисы.
Это может помочь повысить производительность выполнения за счет использования таких приемов, как:
- ленивая оценка.
- мемоизация.
- распараллеливание.
Спасибо, что прочитали мою историю.
Вы можете найти меня по адресу:
Twitter: https://twitter.com/b_k_hela