zoukankan      html  css  js  c++  java
  • Promise是Monad吗?

    const composeCPS = (g, f) => {
      return (x, cb) => {//1
        g(x, y => {//2
          f(y, z => {//3
            cb(z)
          })
        })
      }
    }

    //1 返回一个函数,函数的输入是x,cb;
    //2//3 g的输入参数有两个 1、x;2、
    y => {//2
          f(y, z => {//3
            cb(z)
          })
        })

    2、一个函数,函数的输入参数为y;

    //3一个函数,函数的一个输入参数为y,另一个为函数,函数的输入为z,实现为最外层函数传入的回掉。

    译者按: 近年来,函数式语言的特性都被其它语言学过去了。

    原文: Functional Computational Thinking — What is a monad?

    译者: Fundebug

    为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

    如果你使用函数式编程,不管有没有用过函数式语言,在某总程度上已经使用过Monad。可能大多数人都不知道什么叫做Monad。在这篇文章中,我不会用数学公式来解释什么是Moand,也不使用Haskell,而是用JavaScript直接写Monad。

    作为一个函数式程序员,我首先来介绍一下基础的复合函数:

    const add1 = x => x + 1
    const mul3 = x => x * 3
    
    const composeF = (f, g) => {
      return x => f(g(x))
    }
    
    const addOneThenMul3 = composeF(mul3, add1)
    console.log(addOneThenMul3(4)) // 打印 15

    复合函数composeF接收fg两个参数,然后返回值是一个函数。该函数接收一个参数x, 先将函数g作用到x, 其返回值作为另一个函数f的输入。

    addOneThenMul3是我们通过composeF定义的一个新的函数:由mul3add1复合而成。

    接下来看另一个实际的例子:我们有两个文件,第一个文件存储了第二个文件的路径,第二个文件包含了我们想要取出来的内容。使用刚刚定义的复合函数composeF, 我们可以简单的搞定:

    const readFileSync = path => {
      return fs.readFileSync(path.trim()).toString()
    }
    
    const readFileContentSync = composeF(readFileSync, readFileSync)
    console.log(readFileContentSync('./file1'))

    readFileSync是一个阻塞函数,接收一个参数path,并返回文件中的内容。我们使用composeF函数将两个readFileSync复合起来,就达到我们的目的。是不是很简洁?

    但如果readFile函数是异步的呢?如果你用Node.js 写过代码的话,应该对回调很熟悉。在函数式语言里面,有一个更加正式的名字:continuation-passing style 或则 CPS。

    我们通过如下函数读取文件内容:

    const readFileCPS = (path, cb) => {
      fs.readFile(
        path.trim(),
        (err, data) => {
          const result = data.toString()
          cb(result)
        }
      )
    }

    但是有一个问题:我们不能使用composeF了。因为readCPS函数本身不在返回任何东西。 我们可以重新定义一个复合函数composeCPS,如下:

    const composeCPS = (g, f) => {
      return (x, cb) => {
        g(x, y => {
          f(y, z => {
            cb(z)
          })
        })
      }
    }
    
    const readFileContentCPS = composeCPS(readFileCPS, readFileCPS)
    readFileContentCPS('./file1', result => console.log(result))

    注意:在composeCPS中,我交换了参数的顺序。composeCPS会首先调用函数g,在g的回调函数中,再调用f, 最终通过cb返回值。

    接下来,我们来一步一步改进我们定义的函数。

    第一步,我们稍微改写一下readFIleCPS函数:

    const readFileHOF = path => cb => {
      readFileCPS(path, cb)
    }

    HOF是 High Order Function (高阶函数)的缩写。我们可以这样理解readFileHOF: 接收一个为path的参数,返回一个新的函数。该函数接收cb作为参数,并调用readFileCPS函数。

    并且,定义一个新的复合函数:

    const composeHOF = (g, f) => {
      return x => cb => {
        g(x)(y => {
          f(y)(cb)
        })
      }
    }
    
    const readFileContentHOF = composeHOF(readFileHOF, readFileHOF)
    readFileContentHOF('./file1')(result => console.log(result))

    第二步,我们接着改进readFileHOF函数:

    const readFileEXEC = path => {
      return {
        exec: cb => {
          readFileCPS(path, cb)
        }
      }
    }

    readFileEXEC函数返回一个对象,对象中包含一个exec属性,而且exec是一个函数。

    同样,我们再改进复合函数:

    const composeEXEC = (g, f) => {
      return x => {
        return {
          exec: cb => {
            g(x).exec(y => {
              f(y).exec(cb)
            })
          }
        }
      }
    }
    
    const readFileContentEXEC = composeEXEC(readFileEXEC, readFileEXEC)
    readFileContentEXEC('./file1').exec(result => console.log(result))

    现在我们来定义一个帮助函数:

    const createExecObj = exec => ({exec})

    该函数返回一个对象,包含一个exec属性。 我们使用该函数来优化readFileEXEC函数:

    const readFileEXEC2 = path => {
      return createExecObj(cb => {
        readFileCPS(path, cb)
      })
    }

    readFileEXEC2接收一个path参数,返回一个exec对象。

    接下来,我们要做出重大改进,请注意! 迄今为止,所以的复合函数的两个参数都是huan’hnh函数,接下来我们把第一个参数改成exec对象。

    const bindExec = (execObj, f) => {
      return createExecObj(cb => {
        execObj.exec(y => {
          f(y).exec(cb)
        })
      })
    }

    bindExec函数返回一个新的exec对象。

    我们使用bindExec来定义读写文件的函数:

    const readFile2EXEC2 = bindExec(
      readFileEXEC2('./file1'),
      readFileEXEC2
    )
    readFile2EXEC2.exec(result => console.log(result))

    如果不是很清楚,我们可以这样写:

    bindExec(
      readFileEXEC2('./file1'),
      readFileEXEC2
    )
    .exec(result => console.log(result))

    我们接下来把bindExec函数放入exec对象中:

    const createExecObj = exec => ({
      exec,
      bind(f) {
        return createExecObj(cb => {
          this.exec(y => {
            f(y).exec(cb)
          })
        })
      }
    })

    如何使用呢?

    readFileEXEC2('./file1')
    .bind(readFileEXEC2)
    .exec(result => console.log(result))

    这已经和在函数式语言Haskell里面使用Monad几乎一模一样了。

    我们来做点重命名:

    • readFileEXEC2 -> readFileAsync
    • bind -> then
    • exec -> done
    readFileAsync('./file1')
    .then(readFileAsync)
    .done(result => console.log(result))

    发现了吗?竟然是Promise!

    Monad在哪里呢?

    composeCPS开始,都是Monad.

    • readFIleCPS是Monad。事实上,它在Haskell里面被称作Cont Monad
    • exec 对象是一个Monad。事实上,它在Haskell里面被称作IO Monad

    Monad 有什么性质呢?

    1. 它有一个环境;
    2. 这个环境里面不一定有值;
    3. 提供一个获取该值的方法;
    4. 有一个bind函数可以把值从第一个参数Monad中取出来,并调用第二个参数函数。第二个函数要返回一个Monad。并且该返回的Monad类型要和第一个参数相同。

    数组也可以成为Monad

    Array.prototype.flatMap = function(f) {
      const r = []
      for (var i = 0; i < this.length; i++) {
        f(this[i]).forEach(v => {
          r.push(v)
        })
      }
      return r
    }
    
    const arr = [1, 2, 3]
    const addOneToThree = a => [a, a + 1, a + 2]
    
    console.log(arr.map(addOneToThree))
    // [ [ 1, 2, 3 ], [ 2, 3, 4 ], [ 3, 4, 5 ] ]
    
    console.log(arr.flatMap(addOneToThree))
    // [ 1, 2, 3, 2, 3, 4, 3, 4, 5 ]

    我们可以验证:

    1. [] 是环境
    2. []可以为空,值不一定存在;
    3. 通过forEach可以获取;
    4. 我们定义了flatMap来作为bind函数。

    结论

    • Monad是回调函数 ? 根据性质3,是的。

    • 回调函数式Monad? 不是,除非有定义bind函数。

    http://cnodejs.org/topic/594a1823325c502917ef0ca0

  • 相关阅读:
    c++中的peek函数
    的坑
    Haroopad 写 markdown文本
    《剑指offer》第五十题(字符流中第一个只出现一次的字符)
    《剑指offer》第五十题(字符串中第一个只出现一次的字符)
    《剑指offer》第四十九题(丑数)
    《剑指offer》第四十八题(最长不含重复字符的子字符串)
    《剑指offer》第四十七题(礼物的最大价值)
    《剑指offer》第四十六题(把数字翻译成字符串)
    《剑指offer》第四十五题(把数组排成最小的数)
  • 原文地址:https://www.cnblogs.com/feng9exe/p/8630374.html
Copyright © 2011-2022 走看看