Как трубы и монады работают вместе в JavaScript?

Я посмотрел на похожие вопросы и ответы и не нашел ответа, который непосредственно касается моего вопроса. Я изо всех сил пытаюсь понять, как использовать Maybe или же Eitherили же Monads в сочетании с функциями трубопровода. Я хочу соединить функции вместе, но я хочу, чтобы канал остановился и возвратил ошибку, если она возникает на каком-либо этапе. Я пытаюсь реализовать концепции функционального программирования в приложении node.js, и это действительно мое первое серьезное исследование, так что ни один ответ не будет настолько простым, чтобы оскорбить мой интеллект по этому вопросу.

Я написал функцию канала следующим образом:

const _pipe = (f, g) => async (...args) => await g( await f(...args))

module.exports = {arguments.
    pipeAsync: async (...fns) => {
        return await fns.reduce(_pipe)
    }, 
...

Я называю это так:

    const token = await utils.pipeAsync(makeACall, parseAuthenticatedUser, syncUserWithCore, managejwt.maketoken)(x, y)  

2 ответа

Решение

крюк, леска и грузило

Я не могу не подчеркнуть, насколько важно, чтобы вы не цеплялись за все новые термины, которые, как вам кажется, нужно выучить - функциональное программирование связано с функциями - и, возможно, единственное, что вам нужно понять о функции, это то, что она позволяет вам абстрагировать часть вашей программы с помощью параметра; или несколько параметров, если необходимо (это не так) и поддерживается вашим языком (обычно это так)

Почему я говорю тебе это? Ну, JavaScript уже имеет очень хороший API для упорядочения асинхронных функций с помощью встроенного, Promise.prototype.then

// never reinvent the wheel
const _pipe = (f, g) => async (...args) => await g( await f(...args))
myPromise.then (f) .then (g) .then (h) ...

Но вы хотите писать функциональные программы, верно? Это не проблема для функционального программиста. Изолируйте поведение, которое вы хотите абстрагировать (скрыть), и просто оберните его в параметризованную функцию - теперь, когда у вас есть функция, возобновите написание вашей программы в функциональном стиле...

После того, как вы сделаете это некоторое время, вы начнете замечать шаблоны абстракции - эти шаблоны будут служить вариантами использования для всех других вещей (функторы, аппликативы, монады и т. Д.), О которых вы узнаете позже - но сохраните их для дальнейшего - для теперь, функции...

Ниже мы покажем композицию асинхронных функций слева направо через comp, Для целей этой программы delay включен как создатель Обещаний, и sq а также add1 примерные асинхронные функции

const delay = (ms, x) =>
  new Promise (r => setTimeout (r, ms, x))

const sq = async x =>
  delay (1000, x * x)
  
const add1 = async x =>
  delay (1000, x + 1)

// just make a function  
const comp = (f,g) =>
  // abstract away the sickness
  x => f (x) .then (g)

// resume functional programming  
comp (sq, add1) (10)

  // this effect added for demo purposes
  .then (console.log, console.error)

  // 2 seconds later...
  // 101

придумать собственное удобство

Вы можете сделать вариа compose который принимает любое количество функций - также обратите внимание, как это позволяет вам смешивать функции синхронизации и асинхронности в одной композиции - преимущество подключения прямо в .then, который автоматически продвигает не обещание возврата значений в обещание

const delay = (ms, x) =>
  new Promise (r => setTimeout (r, ms, x))

const sq = async x =>
  delay (1000, x * x)
  
const add1 = async x =>
  delay (1000, x + 1)

// make all sorts of functions
const effect = f => x =>
  (f (x), x)

// invent your own convenience
const log =
  effect (console.log)
  
const comp = (f,g) =>
  x => f (x) .then (g)

const compose = (...fs) =>
  fs.reduce (comp, x => Promise.resolve (x))
  
// your ritual is complete
compose (log, add1, log, sq, log, add1, log, sq) (10)

  // effect added for demo
  .then (console.log, console.error)

  // 10
  // 1 second later ...
  // 11
  // 1 second later ...
  // 121
  // 1 second later ...
  // 122
  // 1 second later ...
  // 14884

Работай умом, а не силой

comp а также compose это простые в освоении функции, которые практически не требовали написания. Потому что мы использовали встроенный .then все средства обработки ошибок подключаются к нам автоматически. Вам не нужно беспокоиться о вручную await или try/catch или же .catch '- еще одно преимущество написания наших функций таким образом

нет ничего постыдного в абстракции

Это не значит, что каждый раз, когда вы пишете абстракцию, это делается для того, чтобы скрыть что-то плохое, но это может быть очень полезно для множества задач - например, "скрытие" императивного стиля. while,

const append = (xs, x) =>
  xs.concat ([x])

const fibseq = n => {
  let seq = []
  let a = 0
  let b = 1
  while (n >= 0) {
    n = n - 1
    seq = append (seq, a)
    a = a + b
    b = a - b
  }
  return seq
}

console.log (fibseq (500))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...  ]

Но вы хотите писать функциональные программы, верно? Это не проблема для функционального программиста. Мы можем создать собственный механизм зацикливания, но на этот раз он будет использовать функции и выражения вместо операторов и побочных эффектов - и все это без ущерба для скорости, читаемости или безопасности стека.

Вот, loop постоянно применяет функцию с помощью нашего recur контейнер значений. Когда функция возвращает не recur значение, вычисление завершено, и возвращается окончательное значение.

const recur = (...values) =>
  ({ type: recur, values })

// break the rules sometimes; reinvent a better wheel
const loop = f =>
  {
    let acc = f ()
    while (acc && acc.type === recur)
      acc = f (...acc.values)
    return acc
  }
      
const fibseq = x =>
  loop ((n = x, seq = [], a = 0, b = 1) =>
    n === 0
      ? seq.concat ([a])
      : recur (n - 1, seq.concat ([a]), a + b, a))

console.time ('loop/recur')
console.log (fibseq (500))
console.timeEnd ('loop/recur')

// [ 0,
//   1,
//   1,
//   2,
//   3,
//   5,
//   8,
//   13,
//   21,
//   34,
//   ... 490 more items ]
// loop/recur: 5ms

ничто не является священным

И помните, вы можете делать все, что хотите. В этом нет ничего волшебного then - кто-то где-то решил это сделать. Вы можете быть кем-то в каком-то месте и просто сделать свой собственный then - Вот then это своего рода функция прямой композиции - так же, как Promise.prototype.then, это автоматически применяется then не- then возвращаемые значения; мы добавляем это не потому, что это особенно хорошая идея, а чтобы показать, что мы можем вести себя подобным образом, если бы захотели

Когда понимаешь then, вы поймете мать всех монад - не забудьте сосредоточиться на механике, а не на условиях

const then = x =>
  x && x.type === then
    ? x
    : Object.assign (f => then (f (x)), { type: then })
  
const sq = x =>
  then (x * x)
  
const add1 = x =>
  x + 1
  
const effect = f => x =>
  (f (x), x)
  
const log =
  effect (console.log)
  
then (10) (log) (sq) (log) (add1) (add1) (add1) (log)
// 10
// 100
// 101

sq (2) (sq) (sq) (sq) (log)
// 65536

что это за язык?

Это даже больше не похоже на JavaScript - кого это волнует? Это ваша программа, и вы решаете, как она будет выглядеть. Хороший язык не встанет у вас на пути и не заставит вас писать свою программу в каком-либо конкретном стиле; функциональный или иной

Это на самом деле JavaScript, просто не поддавшийся ошибочным представлениям о том, что он способен выразить

then (10) (log) (sq) (log) (add1) (add1) (add1) (log)
// 10
// 100
// 103
// => { Then: 103 }

sq (5) (log) (add1) (log)
// 25
// 26
// => { Then: 26 }

отправим его

Мы просто использовали имена comp а также compose в наших локальных фрагментах, но когда вы упаковываете свою программу, вы должны выбрать имена, которые имеют смысл с учетом вашего конкретного контекста - см. комментарий Берги для рекомендации

Ответ Наомик очень интересен, но, похоже, она не удосужилась ответить на ваш вопрос.

Короткий ответ: ваш _pipe Функция распространяет ошибки просто отлично. И прекращает запуск функций, как только выдает ошибку.

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

Вот почему вы не можете сделать это, потому что каждый раз выдает ошибку:

const result = await pipeAsync(func1, func2)(a, b);

Для того, чтобы использовать pipeAsync в его текущем состоянии вам понадобится два await s: один, чтобы получить результат pipeAsync и один, чтобы получить результат вызова этого результата:

const result = await (await pipeAsync(func1, func2))(a, b);

Решение

Удалить ненужное async а также await из определения pipeAsync, Составление ряда функций, даже асинхронных, не является асинхронной операцией:

module.exports = {
    pipeAsync: (...fns) => fns.reduce(_pipe),

После того, как вы это сделали, все работает хорошо:

const _pipe = (f, g) => async(...args) => await g(await f(...args))
const pipeAsync = (...fns) => fns.reduce(_pipe);

const makeACall = async(a, b) => a + b;
const parseAuthenticatedUser = async(x) => x * 2;
const syncUserWithCore = async(x) => {
  throw new Error("NOOOOOO!!!!");
};
const makeToken = async(x) => x - 3;

(async() => {
  const x = 9;
  const y = 7;

  try {
    // works up to parseAuthenticatedUser and completes successfully
    const token1 = await pipeAsync(
      makeACall,
      parseAuthenticatedUser
    )(x, y);
    console.log(token1);

    // throws at syncUserWithCore
    const token2 = await pipeAsync(
      makeACall,
      parseAuthenticatedUser,
      syncUserWithCore,
      makeToken
    )(x, y);
    console.log(token2);
  } catch (e) {
    console.error(e);
  }
})();

Это также может быть написано без использования async совсем:

const _pipe = (f, g) => (...args) => Promise.resolve().then(() => f(...args)).then(g);
const pipeAsync = (...fns) => fns.reduce(_pipe);

const makeACall = (a, b) => Promise.resolve(a + b);
const parseAuthenticatedUser = (x) => Promise.resolve(x * 2);
const syncUserWithCore = (x) => {
  throw new Error("NOOOOOO!!!!");
};
const makeToken = (x) => Promise.resolve(x - 3);

const x = 9;
const y = 7;

// works up to parseAuthenticatedUser and completes successfully
pipeAsync(
  makeACall,
  parseAuthenticatedUser
)(x, y).then(r => console.log(r), e => console.error(e));

// throws at syncUserWithCore
pipeAsync(
  makeACall,
  parseAuthenticatedUser,
  syncUserWithCore,
  makeToken
)(x, y).then(r => console.log(r), e => console.error(e))

Другие вопросы по тегам