函数式编程的 Monad

Monad 是函数式编程中的一种概念,它为数据提供了一种结构化的处理方式。

简单来说就是封装一组方法给传入数据使用,但与对象不同,Monad 是不可变的,每次进行修改都会返回一个新的 Monad。

在函数式编程中,我们通常需要避免副作用。但在一些常见的,例如网络请求,输入输出和文件读写等场景,网络会有波动,输入输出不可控,文件内容可能会变,这种情况下,副作用是不可避免的。

通过 Monad,可以将这些副作用封装在一处。使得副作用可控,乃至于在纯函数中使用。

创建 Monad 的三个条件

如果想创建一个 Monad,那么首先需要满足以下三个条件:

包装数据:Unit(构造函数)

提供一个函数,用于将数据包装成一个对象,并赋予方法。

const unit = v => ({ 
  get: ()=> v, 
  set: v => unit(v) 
})

绑定方法:Bind(绑定操作)

允许提供自定义方法来处理封装的对象,并返回新的 Monad。

const unit = v => ({ 
  get: ()=> v, 
  set: v => unit(v),
  bind: fn => unit(fn(v)),
  flatBind: fn => fn(v)
})

const a = unit(1)
const b = a.bind(v => v * 2)

a.get() // 1
b.get() // 2

遵循三定律

为了确保 Monad 的行为符合预期,它需要遵循以下三个定律:

左单位律:unit(x).bind(f) === f(x)

  • 包装的数据通过函数 f 处理的结果,永远等于直接通过函数 f 处理包装的数据。

    const unit = v => ({ 
      get: ()=> v, 
      set: v => unit(v),
      bind: fn => unit(fn(v)),
      flatBind: fn => fn(v)
    })
    
    const f = x => x * 2
    
    const r1 = unit(5).bind(f)
    const r2 = f(5)
    
    console.log(r1.get() === r2) // true

右单位律:m.bind(unit) === m

  • 包装的数据重新包装后,永远等于包装的数据。

    const unit = v => ({ 
      get: ()=> v, 
      set: v => unit(v),
      bind: fn => unit(fn(v)),
      flatBind: fn => fn(v)
    })
    
    const r1 = unit(5)
    const r2 = r1.flatBind(unit)
    
    console.log(r1.get() === r2.get()) // true

结合律:(m.bind(f)).bind(g) === m.bind(x => f(x).bind(g))

  • 包装的数据先通过函数 f 处理,再通过函数 g 处理,永远等于在函数 f 中处理包装数据后再通过函数 g 处理。

    const unit = v => ({ 
      get: ()=> v, 
      set: v => unit(v),
      bind: fn => unit(fn(v)),
      flatBind: fn => fn(v)
    })
    
    const f = x => unit(x * 5)
    const g = x => unit(x / 2)
    
    const r1 = unit(5).bind(f).bind(g)
    const r2 = unit(5).bind(v => g(f(v)))
    
    console.log(r1.get() === r2.get()) // true


函数式编程的 Monad
http://www.inksha.com/archives/han-shu-shi-bian-cheng-de-monad
作者
inksha
发布于
2025年03月21日
许可协议