Функциональное программирование на 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)

Функтор:

  1. Имеет метод карты, который ожидает функцию.
  2. Функция карты вернет другой функтор того же типа (как исходный функтор).

Можно сделать вывод, что стандартные типы 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)

Https://codeburst.io/alternative-to-javascripts-switch-statement-with-a-functional-twist-3f572787ba1c

Это также может заменить блок if / else.

Использование стрелочных функций делает код кратким и понятным: только одна инструкция за строкой.

8. Заключение:

Написание функции кода похоже на написание математической функции:

  • Нет побочных эффектов
  • Чистый
  • Единственная ответственность
  • Неизменность
  • Нет общих данных
  • Автономный
  • Декларативная
  • Разложить, составить, связать

Функции должны действовать как независимые и автономные микросервисы.

Это может помочь повысить производительность выполнения за счет использования таких приемов, как:

  • ленивая оценка.
  • мемоизация.
  • распараллеливание.

Спасибо, что прочитали мою историю.

Вы можете найти меня по адресу:

Twitter: https://twitter.com/b_k_hela

Github: https://github.com/helabenkhalfallah