Ленивые функции в JavaScript

в 15:26, , рубрики: functional programming, future, javascript, sanctuary, функциональное программирование

Привет!

Подумал я тут рассказать вам о том, как в JavaScript с помощью библиотеки Fluture можно создавать и использовать ленивые функции. Это будет краткий обзор на то, как создавать функции, как обрабатывать ошибки и чуть-чуть про параллелизм. Функциональным программированием мозги парить не буду! Обещаю!

Fluture

Fluture — библиотека, разработанная разработчиком Aldwin Vlasblom, реализующая Future. Future — альтернатива Promise, имеющая куда более мощный API, позволяющий реализовать отмену выполнения (cancellation), безопасную "рекурсию", "безошибочное" выполнение (используя Either) и ещё маленькую тележку крутых возможностей.

Думаю, стоит рассказать вам про методы (монады), которые я буду использовать в примерах ниже

  • .of(Any) — создает Future из переданного значения
  • .map(Function) — нет, это не Array.map, это функция трансформации, аналогичная Promise.then
  • .chainRej(Function) — аналогично Promise.catch ловит ошибку
  • .fork(Function, Function) — запускает выполнение Future

Создание ленивой функции

Для себя я выделил два основных подхода к созданию ленивых функций в Fluture. Первый подход заключается в том, что мы создаем функцию, которая принимает исходные данные и возвращает готовую к выполнению Future. Второй подход заключается в том, что мы создаем Future со всеми описанными трансформациями, а затем передаем ей данные.

Непонятно? Давайте на примере! Есть у нас вот такая функция

const multiply10 = x => x * 10;

Теперь сделаем её ленивой, используя первый подход

const multiply10 = x => x * 10;

const lazyMultiply10 = (x) =>
  Future
    .of(x)            // Создаем Future из значения
    .map(multiply10); // Теперь наша функция тут

lazyMultiply10(2).fork(console.error, console.log);
// -> 20

Слишком громоздко, не правда ли? Попробуем записать более лаконично, используя второй подход.

const multiply10 = x => x * 10;
const lazyMultiply10 = Future.map(multiply10);
const value = Future.of(2); // Оборачиваем наше значение в Future

lazyMultiply10(value).fork(console.error, console.log);
// -> 20

Уже лучше, но все еще громоздко. Надо компактнее!

const lazyMultiply10 = Future.map(x => x * 10);

lazyMultiply10(Future.of(2)).fork(console.error, console.log);
// -> 20

На самом деле, эти подходы не являются взаимоисключающими и могут быть использоваться вместе.

const lazyMultiply10 = Future.map(x => x * 10);

const someCalculation = a =>
  Future
    .of(a)
    .map(v => v + 1)
    .chain(v => lazyMultiply10(Future.of(v));

someCalculation(10).fork(console.error, console.log);
// -> 110

Обработка ошибок

Обработка ошибок в Future практически не отличается от обработки ошибов в Promise. Давайте вспомним представим функцию, которая делает запрос к стороннему, не очень стабильному, API.

const requestToUnstableAPI = query =>
    request({
        method: 'get',
        uri: `http://unstable-site.com/?${query}`
    })
    .then(res => res.data.value)
    .catch(errorHandler);

Та же функция, но обернутая в Future

const lazyRequestToUnstableAPI = query =>
  Future
    .tryP(() => request({
        method: 'get',
        uri: `http://unstable-site.com/?${query}`
    }))
    .map(v => v.data.value)
    .chainRej(err => Future.of(errorHandler(err));

На самом деле, обработку ошибок можно сделать более гибкой. Для этого нам понадобится структура Either, а это малость выходит за рамки моего обзора.

Параллелизм

Для работы с параллелизмом в Future реализованы два метода race(Futures[]) (аналогичен Promise.race), parallel(n, Futures[]) и both(Future, Future), но он является частным случаем parallel.

Метод parallel принимает два аргумента, количество параллельно выполняемых Future и массив с Future. Чтобы сделать поведение parallel таким же как метод Promise.all, нужно количество выполняемых установить как Infinity.

Тут тоже без примеров не обойдемся

const requestF = o => Future.tryP(() => request(o));
const parallel1 = Future.parallel(1);
const lazyReqs = parallel1(
  [
    'http://site.com',
    'http://another-site.com',
    'http://one-more-site.com',
  ]
  .map(requestF)
);

lazyReqs.fork(console.error, console.log);
// -> [Result1, Result2, Result3]

Совместимость с Promise

В JavaScript от Promise никуда не деться, да и вряд ли кто-то будет рад, если ваш метод будет возвращать какую-то непонятную Future. Для этого у Future есть метод .promise(), который, запустит выполнение Future и обернет её в Promise.

Future
  .of(10)
  .promise();
// -> Promise{value=10}

Ссылки

Вот, пожалуй, и все, что я вам хотел рассказать. Если тема интересна, дайте знать, расскажу подробнее про обработку ошибок. И да, сильно меня не ругайте, это мой первый пост на Хабре.

Автор: Artur Aralin

Источник

* - обязательные к заполнению поля