JavaScript函数式编程之函子

更新日期: 2022-10-24阅读: 1k标签: 函数作者: 开水泡饭

函子(Functor)

函子是一个特殊的容器,通过一个普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系),容器包含值和值变形关系(这个变形关系就是函数)。函数式编程中解决副作用的存在

  • 函数式编程的运算不直接操作值,,而是由函子完成
  • 函子就是一个实现了map契约的对象
  • 我们可以把函子想象成一个盒子,盒子里面封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
  • 最终map方法返回一个包含新值所在的盒子(函子)

根据函子的定义我们创建一个函子

// functor 函子
class Container {
  constructor (value) {
    // 函子内部保存这个值。下划线是不想外部访问
    this._value = value
  }

  // map 方法接收一个处理值的函数
  map (fn) {
    return new Container(fn(this._value))
  }
}

此时就已经创建了一个函子但是这是面向对象的方式来创建的,换成用函数式编程来写一个函子

class Container {
  constructor (value) {
    this._value = value
  }

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

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

let x = Container.of(5).map(x => x + 1).map(x => x - 1)

但是这个函子还是存在一些问题,比如空值的时候就会报错, 会让我们的函子变的不纯,我们需要去拦截空值错误,我们创建一个方法去判断是否为空值,如果是控制我们直接返回一个空值的函子,如果有值再去处理,这个时候就需要使用MayBe函子

let x = Container.of(null).map(x => x + 1).map(x => x - 1)

MayBe 函子

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

// MayBe 函子
class MayBe {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }

  isNothing () {
    return this._value === undefined || this._value === null
  }

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

let x = MayBe.of(null)
  .map(x => x + 1)
  .map(x => x - 1)
console.log(x)

这个时候我们已经能正常执行了,但是现在出现了空值的函子,但是我们不知道那个地方出现了空值,所以我们创建两个函子一个是正常的处理一个是出现错误情况处理,正常的就按照正常的方式创建,错误的是是否我们把map方法改造一下让她不再处理回调函数,直接返回一个空值的MayBe函子,这样就记录下了错误信息Eitcher 函子就是来处理这种情况的

Either函子

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

// 因为是二选一,所以定义两个类 Left 和 Right

// 记录错误信息的
class Left {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this
  }

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

// 正常处理
class Rgiht {
  constructor (value) {
    this._value = value
  }

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

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

function parseJson (str) {
  try {
    return Rgiht.of(JSON.parse(str))
  } catch (err) {
    return Left.of({ message: err.message })
  }
}

// 故意传入错误的数据
let r = parseJson('{ name: "2" }')
r.map(x => x.name.toUpperCase())
console.log(r)

IO 函子

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

const fp = require('lodash/fp')

// IO 函子
class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    // 把当前的value 和传入的fn 函数组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

console.log(r)
console.log(r._value())

IO 函子内部帮我们包装了一些函数,当我们传递函数的时候有可能这个函数是一个不纯的操作,不管这个函数纯与不纯,IO这个函子在执行的过程中它返回的这个结果始终是一个纯的操作,我们调用map的时候始终返回的是一个函子,但是IO函子这个_value属性他里面要去合并很多函数,所以他里面可能是不纯的,把这些不纯的操作延迟到了调用的时候,也就是我们通过IO函子控制了副作用的在可控的范围内发生

实现 liunx 下 cat 命令
const fp = require('lodash/fp')

// IO 函子
class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    // 把当前的value 和传入的fn 函数组合成一个新的函数
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

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

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

let cat = fp.flowRight(print, readFile)

console.log(cat('package.json')._value()._value())

此时IO函子出现了嵌套的问题,导致调用嵌套函子中的方法就必须要要._value()._value() 这样来执了,嵌套了几层就需要几层调用

Folktale

Folktale 是一个标准的函数式编程库,和lodash不同的是,他没有提供很多功能函数,只提供了一些函数式处理的操作,例如:compose、curry等,一些函子 Task、Either、MayBe等,

Folktale 中的curry 与compose的简单使用

const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')

// 与lodash区别,第一个参数指明后面参数的个数
let f = curry(2, (n1, n2) => n1 + n2)

console.log(f(1, 2))

// compose 就是函数组合 lodash 中的函数组合是 flowRight
let f2 = compose(toUpper, first)

console.log(f2(['one', 'two']))

Folktale 中的 task 函子

函子可以处理异步任务,在异步任务中会通往地狱之门的回调,而使用task 函子可以避免回调的嵌套,详细请看官方文档

// Task 异步任务
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
const fs = require('fs')

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方法把值放到上下文Context(把值放到容器中,使用map 来处理值)

class Container {
  constructor (value) {
    this._value = value
  }
	static of () {
    return new Container(value)
  }
  map (fn) {
    return new Container(fn(this._value))
  }
}

Monad函子

解决函子嵌套的问题,Monad 函子是可以变扁的 Pointed 函子 IO(IO),一个函子如果具有join和of两个方法并遵循一些定律就是一个Monad

class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    return new IO(fp.flowRight(fn, this._value))
  }

  join () {
    return this._value()
  }

  // 同时调用 join 和 map
  flatMap (fn) {
    return this.map(fn).join()
  }
}

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

function print (x) {
  return new IO(() => {
    return x
  })
}

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

console.log(r)

当我们想要去调用一个方法,这个方法返回一值的时候我们去调用map方法,当我们想要去调用一个方法,这个方法返回一个函子的时候我们去调用flatMap方法

原文地址:https://kspf.xyz/archives/17

链接: https://fly63.com/article/detial/12214

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!