JavaScript 中管道和 monad 是如何协同工作的? [英] How do pipes and monads work together in JavaScript?

查看:24
本文介绍了JavaScript 中管道和 monad 是如何协同工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我查看了类似的问题和答案,但没有找到直接解决我的问题的答案.我正在努力理解如何将 MaybeEitherMonads 与管道功能结合使用.我想将函数放在一起,但我希望管道停止并在任何步骤发生错误时返回错误.我正在尝试在 node.js 应用程序中实现函数式编程概念,这确实是我第一次认真探索这两者,所以没有任何答案会如此简单以至于侮辱我在这个主题上的智商.

I have looked at similar questions and answers and have not found an answer that directly addresses my question. I am struggling to understand how to use Maybe or Eitheror Monads in conjunction with piping functions. I want to pipe functions together, but I want the pipe to stop and return an error if one occurs at any step. I am trying to implement Functional Programming concepts in a node.js app, and this is really my first serious exploration of either, so no answer will be so simple as to insult my intelligence on the subject.

我写了一个像这样的管道函数:

I have written a pipe function like this:

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)  

推荐答案

钩、线和坠子

我不能强调您不会被所有感觉像是必须学习的新术语所困扰是多么重要——函数式编程是关于函数——也许是唯一的事情您需要了解的功能是它允许您使用参数抽象程序的一部分;或多个参数,如果需要(不是)并且您的语言支持(通常是)

I can't stress how critical it is that you don't get snagged on all the new terms it feels like you have to learn – functional programming is about functions – and perhaps the only thing you need to understand about the function is that it allows you to abstract part of your program using a parameter; or multiple parameters if needed (it's not) and supported by your language (it usually is)

我为什么要告诉你这个?好吧,JavaScript 已经有一个非常好的 API 来使用内置的 Promise.prototype.then

Why am I telling you this? Well JavaScript already has a perfectly good API for sequencing asynchronous functions using the built-in, 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) ...

但是您想编写函数式程序,对吗?这对于函数式程序员来说没有问题.隔离你想要抽象(隐藏)的行为,然后简单地将它包装在一个参数化的函数中——现在你有了一个函数,继续以函数式风格编写你的程序......

But you want to write functional programs, right? This is no problem for the functional programmer. Isolate the behavior you want to abstract (hide), and simply wrap it in a parameterized function – now that you have a function, resume writing your program in a functional style ...

在你这样做一段时间后,你开始注意到抽象的模式——这些模式将作为你学习的所有其他事物(函子、应用程序、单子等)的用例大约稍后 - 但保留那些以稍后 - 现在,功能 ...

After you do this for a while, you start to notice patterns of abstraction – these patterns will serve as the use cases for all the other things (functors, applicatives, monads, etc) you learn about later – but save those for later – for now, functions ...

下面,我们通过comp演示从左到右异步函数的组合.就本程序而言,delay 被包含为 Promises 创建者,sqadd1 是示例异步函数 -

Below, we demonstrate left-to-right composition of asynchronous functions via comp. For the purposes of this program, delay is included as a Promises creator, and sq and add1 are sample async functions -

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  
const main =
  comp (sq, add1)

// print promise to console for demo
const demo = p =>
  p .then (console.log, console.error)

demo (main (10))
// 2 seconds later...
// 101

创造自己的便利

您可以创建一个接受任意数量函数的可变参数 compose – 还要注意这如何允许您在同一组合中混合同步 异步函数 – 一个好处直接插入 .then,它会自动将非 Promise 返回值提升为 Promise -

You can make a variadic compose that accepts any number of functions – also notice how this allows you to mix sync and async functions in the same composition – a benefit of plugging right into .then, which automatically promotes non-Promise return values to a Promise -

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
const main =
  compose (log, add1, log, sq, log, add1, log, sq)

// print promise to console for demo
const demo = p =>
  p .then (console.log, console.error)

demo (main (10))
// 10
// 1 second later ...
// 11
// 1 second later ...
// 121
// 1 second later ...
// 122
// 1 second later ...
// 14884

更聪明地工作,而不是更努力

compcompose 是易于理解的函数,几乎不需要编写任何代码.因为我们使用了内置的 .then,所有错误处理的东西都会自动为我们连接起来.您不必担心手动 await'ing 或 try/catch.catch'ing – 又另一个 以这种方式编写函数的好处 -

comp and compose are easy-to-digest functions that took almost no effort to write. Because we used built-in .then, all the error-handling stuff gets hooked up for us automatically. You don't have to worry about manually await'ing or try/catch or .catch'ing – yet another benefit of writing our functions this way -

抽象无耻

现在,这并不是说每次你编写抽象都是为了隐藏一些不好的,但它对各种任务非常有用——例如隐藏"命令式的 while -

Now, that's not to say that every time you write an abstraction it's for the purposes of hiding something bad, but it can be very useful for a variety of tasks – take for example "hiding" the imperative-style while -

const fibseq = n => // a counter, n
{ let seq = []      // the sequence we will generate
  let a = 0         // the first value in the sequence
  let b = 1         // the second value in the sequence
  while (n > 0)     // when the counter is above zero
  { n = n - 1             // decrement the counter
    seq = [ ...seq, a ]   // update the sequence
    a = a + b             // update the first value
    b = a - b             // update the second value
  }
  return seq        // return the final sequence
}

console .time ('while')
console .log (fibseq (500))
console .timeEnd ('while')
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...  ]
// while: 3ms

但是您想编写函数式程序,对吗?这对于函数式程序员来说没有问题.我们可以创建自己的循环机制,但这次它将使用函数和表达式而不是语句和副作用——所有这些都不会牺牲速度、可读性或 堆栈安全.

But you want to write functional programs, right? This is no problem for the functional programmer. We can make our own looping mechanism but this time it will use functions and expressions instead of statements and side effects – all without sacrificing speed, readability, or stack safety.

在这里,loop 使用我们的 recur 值容器连续应用一个函数.当函数返回一个非recur 值时,计算完成,返回最终值.fibseq 是一个具有无限递归的纯函数表达式.这两个程序都在大约 3 毫秒内计算出结果.不要忘记检查答案是否匹配:D

Here, loop continuously applies a function using our recur value container. When the function returns a non-recur value, the computation is complete, and the final value is returned. fibseq is a pure, functional expression complete with unbounded recursion. Both programs compute the result in just about 3 milliseconds. Don't forget to check the answers match :D

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

// break the rules sometimes; reinvent a better wheel
const loop = f =>
{ let acc = f ()
  while (acc && acc.recur === recur)
    acc = f (...acc.values)
  return acc
}
      
const fibseq = x =>
  loop               // start a loop with vars
    ( ( n = x        // a counter, n, starting at x
      , seq = []     // seq, the sequence we will generate
      , a = 0        // first value of the sequence
      , b = 1        // second value of the sequence
      ) =>
        n === 0      // once our counter reaches zero
          ? seq      // return the sequence
          : recur    // otherwise recur with updated vars
              ( n - 1          // the new counter
              , [ ...seq, a ]  // the new sequence
              , b              // the new first value
              , a + b          // the new second value
              )
    )

console.time ('loop/recur')
console.log (fibseq (500))
console.timeEnd ('loop/recur')
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...  ]
// loop/recur: 3ms

没有什么是神圣的

记住,你可以为所欲为.then 并没有什么神奇之处——某个地方的某个人决定做到这一点.你可以成为某个地方的某个人,然后制作自己的 then——这里的 then 是一种前向组合函数——就像 Promise.prototype.then,它会自动将 then 应用于非 then 返回值;我们添加这个不是因为这是一个特别好的主意,而是为了表明如果我们愿意,我们可以做出这种行为.

And remember, you can do whatever you want. There's nothing magical about then – someone, somewhere decided to make it. You could be somebody in some place and just make your own then – here then is a sort of forward-composition function – just like Promise.prototype.then, it automatically applies then to non-then return values; we add this not because it's a particularly good idea, but to show that we can make that kind of behavior if we wanted to.

const then = x =>
  x && x.then === then
    ? x
    : Object .assign
        ( f => then (f (x))
        , { 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,但谁在乎呢?这是你的程序,决定你想要它的样子.一门好的语言不会妨碍你并强迫你以任何的风格编写你的程序;功能或其他.

It doesn't even look like JavaScript anymore, but who cares? It's your program and you decide what you want it to look like. A good language won't stand in your way and force you to write your program in any particular style; functional or otherwise.

它实际上是 JavaScript,只是不受对其表达能力的误解的束缚 -

It's actually JavaScript, just uninhibited by misconceptions of what its capable of expressing -

const $ = x => k =>
  $ (k (x))
  
const add = x => y =>
  x + y

const mult = x => y =>
  x * y
  
$ (1)           // 1
  (add (2))     // + 2 = 3
  (mult (6))    // * 6 = 18
  (console.log) // 18
  
$ (7)            // 7
  (add (1))      // + 1 = 8
  (mult (8))     // * 8 = 64
  (mult (2))     // * 2 = 128
  (mult (2))     // * 2 = 256
  (console.log)  // 256

当你理解了$,你就会理解所有单子之母.记住要专注于机制并直观地了解它是如何工作的;不用担心条款.

When you understand $, you will have understood the mother of all monads. Remember to focus on the mechanics and get an intuition for how it works; worry less about the terms.

发货

我们只是在本地代码片段中使用了名称 compcompose,但是当您打包程序时,您应该选择对您的特定上下文有意义的名称 - 请参阅Bergi 对推荐的评论.

We just used the names comp and compose in our local snippets, but when you package your program, you should pick names that make sense given your specific context – see Bergi's comment for a recommendation.

这篇关于JavaScript 中管道和 monad 是如何协同工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆