函数式编程 - Functor (函子)

467 阅读3分钟

Functor (函子)

  • 为什么要学函子

到目前为止已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。

  • 什么是Functor

    • 容器:包含值和值的变形关系(这个变形关系就是函数)
    • 函子:是一个特殊的容器,通过一个普通 的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)

// Functor 函子
class Container {
  constructor(value) {
    this._value = value;
  }

  map(fn) {
    return new Container(fn(this._value));
  }
}

MayBe函子

  • 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理
  • MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
class Maybe {

  static of(value) {
    return new Maybe(value)
  }

  constructor(value) {
    this._value = value
  }

  // 如果对宿舍变形的话直接返回 值为 null 的函子
  map(fn) {
    return this.isNothing() ? Maybe.off(null) : Maybe.of(fn(this._value))
  }

  isNothing() {
    return this._value === null || this._value === undefined
  }
}
// 传入具体值
// let r = Maybe.of('Hello World')
//           .map(x => x.toUpperCase())
// console.log(r)

let r = Maybe.of(null)
          .map(x => x.toUpperCase())
console.log(r)

Either函子

  • Either两者中的任何一个,类似于if...else...的处理
  • 异常会让函数变的不纯,Either函子可以用来做异常处理
class Left {

  static of(value) {
    return new Left(value)
  }

  constructor(value) {
    this._value = value
  }

  map(fn) {
    return this
  }
}



class Right {

  static of(value) {
    return new Right(value)
  }

  constructor(value) {
    this._value = value
  }

  map(fn) {
    return Right.of(fn(this._value))
  }
}


// let r1 = Right.of(12).map(x => x + 2)
// let r2 = Left.of(12).map(x => x + 2)

// console.log(r1)
// console.log(r2)

function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str))
  } catch(e) {
    return Left.of({ error: e.message })
  }
}
// let r = parseJSON('{ name: zs }')
let r = parseJSON('{ "name": "zs" }').map(x => x.name.toUpperCase())
console.log(r)

IO函子

  • IO函子中的_value是一个函数,这里是把函数作为值来处理
  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作纯
  • 把不纯属的操作交给调用者来处理

const fp = require('lodash/fp')

class IO {

  static of(value) {
    return new IO(function() {
      return value;
    })
  }

  constructor(fn) {
    this._value = fn;
  }

  map(fn) {
    return new IO(fp.flowRight(fn, this._value));
  }
}

// 调用
const r = IO.of(process).map(p => p.execPath);

console.log(r._value());

folktale

  • Task异步执行

    • 异步任务的实现过于复杂,我们使用folkatle中的Task来演示

    • folktale一个标准的函数式编程库

      • 和lodash、remda不同的是,他没有提供很多功能函数
      • 只提供了一些函数式处理的操作,例如:compose, curry等,一些函数Task、Either、MayBe等
// folktale 中的 compose、curry
const { compose, curry } = require('folktale/core/lamba')
const { toUpper, first } = require('lodash/fp')

// 第一个参数是传入函数的参数个数
let f = curry(2, function(x, y) {
  console.log(x + y)
})
f(3, 4)
f(3)(4)

// 函数组合
let f = compose(toUpper, first);
f(['one', 'two'])
Task函子
  • folktale(2.3.2)2.x中的TaskT 1.0中的Task区别很大,1.0中的用法更接近我们现在演示的函子
  • 这里以2.3.2来演示
const fs = require('fs')
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')

function readFile(filename) {
    return task(resolver => {
      fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}
readFile('package.json')
    .map(split('\n'))
    .map(find(x => x.includes('version')))
    .run()
    .listen({
      onRejected: err => {
        console.log(err)
      },
      onResolved: value => {
        console.log(value)
      }
    })
Pointed函子
  • Pointed函子是实现了of静态方法的函子
  • of方法是为了避免使用new来创建对象,更深层的含义是of方法用来把值放到上下文Content(把值放到窗口中,使用map来处理值)
// 就是of函数 
class Container {

  static of(value) {
    return new Container(value)
  }

  constructor(value) {
    this._value = value;
  }

  map(fn) {
    return new Container(fn(this._value));
  }
}
Container.of(2)
  .map(x => x + 5)
IO函子的问题
  • Monad (单子)

    • 在使用IO函子的时候,如果我们写出如下代码
var fs = require('fs');
const fp = require('lodash/fp')

class IO {

  static of(value) {
    return new IO(function() {
      return value;
    })
  }

  constructor(fn) {
    this._value = fn;
  }

  map(fn) {
    return new IO(fp.flowRight(fn, this._value));
  }
}

let readFile = function(filename) {
  return new IO(function() {
    return fs.readFileSync(filename, 'utf-8')
  })
}

let print = function(x) {
  return new IO(function() {
    console.log(x)
    return x
  })
}

let cat = fp.flowRight(print, readFile)

// 嵌套问题
let r = cat('package.json')._value()._value();
console.log(r)
  • Monad函子是可以变扁的 Pointed函子,IO(IO(x))
  • 一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
var fs = require('fs');
const fp = require('lodash/fp')

class IO {

  static of(value) {
    return new IO(function () {
      return value;
    })
  }

  constructor(fn) {
    this._value = fn;
  }

  map(fn) {
    return new IO(fp.flowRight(fn, this._value));
  }

  join() {
    return this._value()
  }

  flatMap(fn) {
    return this.map(fn).join()
  }
}

let readFile = function (filename) {
  return new IO(function () {
    return fs.readFileSync(filename, 'utf-8')
  })
}

let print = function (x) {
  return new IO(function () {
    console.log(x)
    return x
  })
}

let r = readFile('package.json')
  .flatMap(print)
  .join()

console.log(r)