Skip to content

函子与 Monad

函子(Functor)

定义: 实现了 map 方法的对象,可以在内部值上应用函数。

接口:

typescript
interface Functor<T> {
  map<U>(fn: (value: T) => U): Functor<U>
}

Maybe 函子

作用: 处理可能为空的值,避免空指针异常。

javascript
class Maybe {
  constructor(value) {
    this.value = value
  }

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

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

  map(fn) {
    return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this.value))
  }

  // 提取值
  getOrElse(defaultValue) {
    return this.isNothing() ? defaultValue : this.value
  }
}

// 使用
const maybeName = Maybe.of('Alice')
maybeName.map(name => name.toUpperCase())  // Maybe('ALICE')

const maybeNull = Maybe.of(null)
maybeNull.map(x => x.toUpperCase())        // Maybe(null)
maybeNull.getOrElse('Unknown')             // 'Unknown'

// 链式调用
Maybe.of(user)
  .map(u => u.address)
  .map(a => a.city)
  .getOrElse('Unknown')

Either 函子

作用: 处理错误,分为 Left(错误)和 Right(成功)。

javascript
class Either {
  constructor(value) {
    this.value = value
  }

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

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

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

class Left extends Either {
  map(fn) {
    return this  // Left 不执行 map
  }

  getOrElse(defaultValue) {
    return defaultValue
  }

  orElse(fn) {
    return fn(this.value)
  }
}

class Right extends Either {
  map(fn) {
    return Either.of(fn(this.value))
  }

  getOrElse(defaultValue) {
    return this.value
  }

  orElse(fn) {
    return this
  }
}

// 使用
const parseJSON = (json) => {
  try {
    return Either.right(JSON.parse(json))
  } catch (e) {
    return Either.left(e.message)
  }
}

parseJSON('{"name": "Alice"}')
  .map(obj => obj.name)
  .map(name => name.toUpperCase())
  .getOrElse('error')

parseJSON('invalid')
  .map(obj => obj.name)  // 不执行
  .getOrElse('Parse error')  // 'Parse error'

IO 函子

作用: 延迟执行副作用操作。

javascript
class IO {
  constructor(fn) {
    this.fn = fn
  }

  static of(value) {
    return new IO(() => value)
  }

  static from(fn) {
    return new IO(fn)
  }

  map(fn) {
    return new IO(() => fn(this.fn()))
  }

  chain(fn) {
    return new IO(() => fn(this.fn()).fn())
  }

  run() {
    return this.fn()
  }
}

// 使用
const readLocalStorage = key => IO.of(localStorage.getItem(key))
const log = message => new IO(() => {
  console.log(message)
  return message
})

const getUser = readLocalStorage('user')
  .map(str => JSON.parse(str))
  .map(user => user.name)
  .chain(name => log(`Hello ${name}`))

getUser.run()  // 执行所有 IO 操作

Monad

定义: 实现了 flatMap(或 chain)方法的函子,用于嵌套处理。

接口:

typescript
interface Monad<T> extends Functor<T> {
  flatMap<U>(fn: (value: T) => Monad<U>): Monad<U>
}

Monad 定律

javascript
// 1. 左单位律
// Monad.of(a).flatMap(f) === f(a)
Either.of(5).flatMap(x => Either.of(x * 2))  // === Either.of(10)

// 2. 右单位律
// m.flatMap(Monad.of) === m
Either.of(5).flatMap(Either.of)  // === Either.of(5)

// 3. 结合律
// m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
const f = x => Either.of(x + 1)
const g = x => Either.of(x * 2)

Either.of(5).flatMap(f).flatMap(g)
  // === Either.of(5).flatMap(x => f(x).flatMap(g))

Promise 就是 Monad

javascript
// Promise 符合 Monad 接口
const promise = Promise.resolve(5)

promise.then(x => x * 2)              // map
promise.then(x => Promise.resolve(x * 2))  // flatMap/chain

常用函子组合

Either 与 Maybe 组合

javascript
class Maybe {
  constructor(value) { this.value = value }

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

  map(fn) {
    return this.value === null || this.value === undefined
      ? Maybe.nothing()
      : Maybe.of(fn(this.value))
  }

  flatMap(fn) {
    return this.map(fn).value || Maybe.nothing()
  }
}

const safeDiv = (a, b) =>
  b === 0 ? Either.left('Division by zero') : Either.right(a / b)

const getUserEmail = user =>
  Maybe.of(user)
    .map(u => u.profile)
    .map(p => p.email)
    .value

Task 函子(异步)

javascript
// 简化的 Task 实现
class Task {
  constructor(fork) {
    this.fork = fork
  }

  static of(value) {
    return new Task(resolve => resolve(value))
  }

  static rejected(reason) {
    return new Task((_, reject) => reject(reason))
  }

  map(fn) {
    return new Task((resolve, reject) =>
      this.fork(
        value => resolve(fn(value)),
        reject
      )
    )
  }

  flatMap(fn) {
    return new Task((resolve, reject) =>
      this.fork(
        value => fn(value).fork(resolve, reject),
        reject
      )
    )
  }
}

// 使用
const fetchUser = id => Task.of(
  new Promise((resolve) => {
    setTimeout(() => resolve({ id, name: 'Alice' }), 100)
  })
)

fetchUser(1)
  .map(user => user.name)
  .flatMap(name => fetchUser(2).map(u => `${name} & ${u.name}`))
  .fork(
    console.log,  // 成功
    console.error // 失败
  )

实际应用:验证器

javascript
class Validator {
  constructor(validations) {
    this.validations = validations
  }

  static of(validations) {
    return new Validator(validations)
  }

  static lift(validations) {
    return value => Either.valid(validations.every(v => v.predicate(value)))
  }

  validate(value) {
    const errors = this.validations
      .filter(v => !v.predicate(value))
      .map(v => v.message)

    return errors.length === 0
      ? Either.right(value)
      : Either.left(errors)
  }
}

// 定义验证规则
const validateUser = Validator.of([
  { predicate: u => u.name, message: 'Name is required' },
  { predicate: u => u.name.length >= 2, message: 'Name too short' },
  { predicate: u => u.email, message: 'Email is required' },
  { predicate: u => u.email.includes('@'), message: 'Invalid email' }
])

validateUser({ name: 'A', email: 'invalid' })
  .map(user => saveUser(user))
  .leftMap(errors => console.log('Errors:', errors))

面试高频题

Q1: Functor 和 Monad 的区别?

答案:

  • Functor:只实现了 map,可以在值上应用函数
  • Monad:实现了 flatMap/chain,可以处理嵌套的函子
javascript
// Functor:map 返回 Functor
Maybe.of(5).map(x => x * 2)  // Maybe(10)

// Monad:flatMap 返回 Monad,且会扁平化
Maybe.of(Maybe.of(5)).flatMap(x => x)  // Maybe(5)

Q2: Maybe 和 Either 的适用场景?

答案:

  • Maybe:表示值可能存在或不存在(null/undefined),不关心错误原因
  • Either:表示两种互斥情况(成功/失败),可以携带错误信息

Q3: 什么是函子定律?

答案:

  1. 身份律functor.map(x => x) === functor
  2. 组合律functor.map(f).map(g) === functor.map(x => g(f(x)))

前端面试知识库