08-Functor(函子)

为什么要学函子

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

什么是 Functor

  • 容器:包含值和值的变形关系(这个变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
class Container {
  constructor(value) {
      // 函子值不向外提供,所有以_开头的,都是私有成员
    this._value = value;
  }

  // 对外提供map方法,map方法要对外提供一个处理值的函数fn,最后返回新函子对象
  map(fn) {
    return new Container(fn(this._value));
  }
}

// 创建一个函子对象,然后调用map方法 ,map方法里面传递了一个函数,然后接着调用函子里面的map方法
const a = new Container(5)
  .map(x => x+1)
  .map(x => x+1)

  console.log(a);


  // 改造
  class Container {
    // 静态方法,直接通过类名调用
    static of(value){
      return new Container(value)
    }
    constructor(value) {
      this._value = value;
    }
  
    map(fn) {
      return new Container(fn(this._value));
    }
  }
  const r = Container.of(5)
          .map(x => x+2) //传函数
          .map(x => x+4) // 继续处理上一个函子返回的对象的值
  
console.log(r); // 不是值,是函子对象,想打印里面的值,调用静态方法里的map方法打印
// Container { _value: 11 }

  // 传入null和undefined  会发生副作用,异常, 使用MayBe函子
  Container.of(null).map(x=>x.toUpperCase())

总结

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

MayBe函子

  • 我们在编程中可能会遇到很多错误,需要对这些错误做相对应的处理
  • MayBe函子的作用就是可以对外部的空值情况做处理,(空值副作用在允许的范围)
  • 如果多次调用,无法确定哪里发生异常(Either函子可以解决)
// MayBe函子
class MayBe {
  static of(value) {
    return new MayBe(value);
  }

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

  map(fn) {
    // 解决异常
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
  }

  // 辅助函数,先判断数据
  isNothing() {
    return this._value === null || this._value === undefined;
  }
}

const a = MayBe.of("hello world").map((x) => x.toUpperCase());
const b = MayBe.of(null).map((x) => x.toUpperCase());

console.log(a); // MayBe { _value: 'HELLO WORLD' } 
console.log(b); // MayBe { _value: null }


// 无法确定哪里发生异常
const a = MayBe.of("hello world")
               .map((x) => x.toUpperCase())
               .map(x => null)
               .map(x => x.split(','));

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 = Left.of(12).map((x) => x + 1);

let r2 = Right.of(13).map((x) => x + 1);

console.log(r1);
console.log(r2);

function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str));
  } catch (error) {
    return Left.of({ error: error.message });
  }
}

// 这样就能记录报错信息了
let r3 = parseJSON("{ name: zx }");
console.log(r3); //Left { _value: { error: 'Unexpected token n in JSON at position 2' } }

let r4 = parseJSON('{ "name": "zx" }');
console.log(r4); // Right { _value: { name: 'zx' } }

let r5 = parseJSON('{ "name": "zx" }').map(x => x.name.toUpperCase());
console.log(r5);  // Right { _value: 'ZX' }

IO函子

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

class IO{
  // 通过of方法,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 rr = IO.of(process).map(p => p.execPath)
console.log(rr); // IO { _value: [Function (anonymous)] }
console.log(rr._value());// D:\nodejs\node.exe

Task异步执行

  • 异步任务的实现过于复杂,我们使用folktale中的Task来演示
  • folktale一个标准的函数式编程库
    • 和lodash、ramda不同的是,他没有提供很多功能函数
    • 只提供了一些函数式处理的操作,例如compose(合并)、curry(柯里化)等,一些函子Task、Either、MayBe等
  • folktake(2.3.2)2.x中的Task和1.0中的Task区别很大,1.0中的用法更接近我们现在演示的函子
// folktale中的compose curry
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')

// 柯里化curry
let f = curry(2,(x,y) => {
    return x + y
})

console.log(f(1,2));
console.log(f(1)(2));

let f1 = compose(toUpper,first)
console.log(f1(['one','two']));
  • folktake(2.3.2)2.x演示
const { compose, curry } = require("folktale/core/lambda");
const { toUpper, first ,split, find} = require("lodash/fp");
// Task处理异步任务
const fs = require("fs");
const { task } = require("folktale/concurrency/task");

function readFile(filename) {
  return task((resolver) => {
    fs.readFile(filename, "utf-8", (err, data) => {
      if (err) resolver.reject(err);

      resolver.resolve(data);
    });
  });
}

// readFile返回的是一个task函子
// run运行  listen监听
readFile("package.json")// 读取文件,执行map,会把文件内容传递给map函数
  .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{
  static of(value){
    return new Container(value)// 上下文
  }
......
}
Container.of(22).map(x => x + 5)// 在上下文中处理数据

Monad(单子)

  • 问题:嵌套函子中的函子,需要多次.value()
  • Monad函子是可以变扁的Pointed函子,IO(IO(x))
  • 一个函子如果具有join和of两个方法并遵守一些法律就是一个Monad
  • 什么时候使用,当一个函数返回一个函子的时候
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); // readfile2返回的函子
    return x;
  });
};

let cat = fp.flowRight(print, readFile);

// _value()指的 new  IO的函子
let r = cat("package.json")._value()._value()
// IO(IO(x)) 嵌套函子中的函子,.value().value()
console.log(r);
===========================
let r = readFile('package.json')
        //.map(x => x.toUpperCase())// 处理数据
        .map(fp.toUpper)
        .flatMap(print)
        .join()// 相当于调用_value
cosole.log(r)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数式编程中的 `Maybe`, `Either`, `Functor` 和 `Monad` 是常见的一些概念。 `Maybe` 表示一个可能为空的值,它可以用来避免程序中的空指针异常。在 Haskell 中,`Maybe` 是一个数据类型,它有两个值:`Just a` 表示存在一个值,值为 `a`;`Nothing` 表示不存在值。在其他语言中,可以通过自定义一个 `Maybe` 类型来实现类似的功能。 `Either` 表示一个值可能是两种类型中的一种。在 Haskell 中,`Either` 是一个数据类型,它有两个参数:`Either a b` 表示值可以是类型 `a` 或 `b` 中的一种。在其他语言中,可以通过自定义一个 `Either` 类型来实现类似的功能。 `Functor` 是一个抽象概念,它表示一个能够被映射(map)的数据结构。在 Haskell 中,`Functor` 是一个类型类,它要求实现一个 `fmap` 函数,它可以将一个函数作用于一个 Functor 上。在其他语言中,可以通过实现一个 `map` 函数来实现类似的功能。 `Monad` 是一个用于处理副作用的抽象概念。在函数式编程中,副作用是指函数执行过程中对程序状态进行修改,比如 I/O 操作、异常处理等。为了避免副作用对程序的影响,函数式编程中引入了 Monad 的概念。Monad 要求实现一个 `bind` 函数,它可以将一个 Monad 的值传递给一个函数,并返回一个新的 Monad。在 Haskell 中,`Monad` 是一个类型类,它要求实现 `return` 和 `>>=` 函数。在其他语言中,可以通过自定义一个 Monad 类型来实现类似的功能。 综上所述,`Maybe`, `Either`, `Functor` 和 `Monad` 都是函数式编程中常见的一些概念,它们可以帮助程序员编写更加健壮、可维护的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值