基础知识

柯里化:函数式编程的核心技术

深入理解柯里化的原理与实现,掌握函数式编程中参数复用与函数组合的艺术

2024年12月8日12 分钟1373 阅读
JavaScript函数式编程设计模式
柯里化:函数式编程的核心技术

柯里化:函数式编程的核心技术

柯里化(Currying)是函数式编程中最重要的概念之一,它将一个接受多个参数的函数转换为一系列接受单一参数的函数。本文将深入剖析柯里化的原理、实现方式及实际应用场景。

一、核心概念

柯里化(Currying) 是将一个接受多个参数的函数转换为一系列接受单一参数的函数的技术。这种技术以逻辑学家 Haskell Curry 的名字命名。

原始函数: f(a, b, c) → result
柯里化后: f(a)(b)(c) → result

┌─────────────────────────────────────────────────┐
│                  柯里化转换                       │
├─────────────────────────────────────────────────┤
│  add(1, 2, 3)  ──────►  curriedAdd(1)(2)(3)    │
│       ↓                        ↓                │
│    一次调用              链式调用                 │
│    传递所有参数           每次传递一个参数         │
└─────────────────────────────────────────────────┘

柯里化的核心优势

特性描述应用场景
参数复用固定部分参数,生成新函数配置化函数
延迟执行收集参数后统一执行批量处理
函数组合便于 compose/pipe 操作数据流处理
代码简洁减少重复代码工具函数封装

二、基础实现

简单累加柯里化

javascript
function currying(...initialArgs) {
  // 收集初始参数
  const args = [...initialArgs]
  
  // 内部函数用于收集后续参数
  const inner = (...newArgs) => {
    if (newArgs.length === 0) {
      // 如果没有新参数,则返回当前参数的总和
      return args.reduce((a, b) => a + b, 0)
    }
    args.push(...newArgs) // 收集新参数
    return inner // 返回自身以支持链式调用
  }

  return inner // 返回内部函数
}

// 使用示例
const sum = currying(1, 2)
sum(3)(4)
sum(5)
console.log(sum()) // 输出: 15

通用柯里化实现

typescript
/**
 * 通用柯里化函数
 * 支持任意多参数函数的柯里化转换
 */
function curry<T extends (...args: any[]) => any>(
  fn: T
): CurriedFunction<T> {
  const arity = fn.length // 获取原函数的参数个数
  
  return function curried(...args: any[]): any {
    // 如果收集的参数足够,执行原函数
    if (args.length >= arity) {
      return fn(...args)
    }
    
    // 否则返回新函数继续收集参数
    return (...moreArgs: any[]) => {
      return curried(...args, ...moreArgs)
    }
  } as CurriedFunction<T>
}

// 类型定义
type CurriedFunction<T extends (...args: any[]) => any> = 
  T extends (a: infer A) => infer R
    ? (a: A) => R
    : T extends (a: infer A, b: infer B) => infer R
      ? (a: A) => (b: B) => R
      : T extends (a: infer A, b: infer B, c: infer C) => infer R
        ? (a: A) => (b: B) => (c: C) => R
        : never

// 使用示例
const add = (a: number, b: number, c: number) => a + b + c
const curriedAdd = curry(add)

console.log(curriedAdd(1)(2)(3))  // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6

三、实现原理详解

柯里化执行流程:

curry(add)
    │
    ▼
┌─────────────────────────────────────┐
│  curried 函数 (闭包保存 fn, arity)   │
└─────────────────────────────────────┘
    │
    │ curriedAdd(1)
    ▼
┌─────────────────────────────────────┐
│  args = [1]                         │
│  args.length (1) < arity (3)        │
│  返回新函数继续收集                   │
└─────────────────────────────────────┘
    │
    │ (2)
    ▼
┌─────────────────────────────────────┐
│  args = [1, 2]                      │
│  args.length (2) < arity (3)        │
│  返回新函数继续收集                   │
└─────────────────────────────────────┘
    │
    │ (3)
    ▼
┌─────────────────────────────────────┐
│  args = [1, 2, 3]                   │
│  args.length (3) >= arity (3)       │
│  执行 fn(1, 2, 3) → 返回 6          │
└─────────────────────────────────────┘

四、应用场景

1. 创建带有默认参数的计算函数

javascript
// 基础计算函数
function calculate(operator, a, b) {
  switch (operator) {
    case 'add':
      return a + b
    case 'subtract':
      return a - b
    case 'multiply':
      return a * b
    case 'divide':
      return a / b
    default:
      return null
  }
}

// 柯里化函数
function curryCalculate(operator) {
  return function (a) {
    return function (b) {
      return calculate(operator, a, b)
    }
  }
}

// 创建特定运算的计算器
const add = curryCalculate('add')
const subtract = curryCalculate('subtract')
const multiply = curryCalculate('multiply')

// 使用示例
console.log(add(5)(3))      // 输出: 8
console.log(subtract(5)(3)) // 输出: 2
console.log(multiply(4)(5)) // 输出: 20

// 进一步复用:创建加 10 的函数
const addTen = add(10)
console.log(addTen(5))  // 15
console.log(addTen(20)) // 30

2. 事件处理器

javascript
// 基础事件处理函数
function handleEvent(eventType, element, callback) {
  element.addEventListener(eventType, callback)
  return () => element.removeEventListener(eventType, callback)
}

// 柯里化版本
const bindEvent = curry(handleEvent)

// 创建特定事件的绑定函数
const onClick = bindEvent('click')
const onHover = bindEvent('mouseenter')
const onScroll = bindEvent('scroll')

// 使用示例
const button = document.querySelector('#myButton')
const unbindClick = onClick(button)(() => {
  console.log('Button clicked!')
})

// 解绑事件
unbindClick()

3. API 请求封装

typescript
// 基础 API 请求函数
async function apiRequest(
  method: string,
  baseURL: string,
  endpoint: string,
  data?: object
) {
  const url = `${baseURL}${endpoint}`
  const options: RequestInit = {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: data ? JSON.stringify(data) : undefined
  }
  
  const response = await fetch(url, options)
  return response.json()
}

// 柯里化版本
const curriedRequest = curry(apiRequest)

// 创建特定方法的请求函数
const get = curriedRequest('GET')
const post = curriedRequest('POST')
const put = curriedRequest('PUT')
const del = curriedRequest('DELETE')

// 创建针对特定 API 的请求函数
const apiV1 = get('https://api.example.com/v1')
const apiV2 = get('https://api.example.com/v2')

// 使用示例
const getUsers = apiV1('/users')
const getPosts = apiV1('/posts')

// 调用
const users = await getUsers()
const posts = await getPosts()

4. 日志系统

typescript
// 柯里化日志函数
const log = curry((level: string, module: string, message: string) => {
  const timestamp = new Date().toISOString()
  console.log(`[${timestamp}] [${level}] [${module}] ${message}`)
})

// 创建不同级别的日志函数
const info = log('INFO')
const warn = log('WARN')
const error = log('ERROR')

// 创建模块专用日志
const authInfo = info('Auth')
const authError = error('Auth')
const dbInfo = info('Database')
const dbError = error('Database')

// 使用示例
authInfo('User logged in successfully')
// [2025-12-06T10:30:00.000Z] [INFO] [Auth] User logged in successfully

dbError('Connection timeout')
// [2025-12-06T10:30:00.000Z] [ERROR] [Database] Connection timeout

5. 数据验证

typescript
// 验证规则柯里化
const validate = curry((
  rule: (value: any) => boolean,
  errorMessage: string,
  value: any
): { valid: boolean; error?: string } => {
  const valid = rule(value)
  return valid ? { valid: true } : { valid: false, error: errorMessage }
})

// 创建常用验证器
const isRequired = validate(
  (v: any) => v !== null && v !== undefined && v !== '',
  '此字段为必填项'
)

const minLength = (min: number) => validate(
  (v: string) => v.length >= min,
  `最少需要 ${min} 个字符`
)

const maxLength = (max: number) => validate(
  (v: string) => v.length <= max,
  `最多允许 ${max} 个字符`
)

const isEmail = validate(
  (v: string) => /^[^s@]+@[^s@]+.[^s@]+$/.test(v),
  '请输入有效的邮箱地址'
)

// 使用示例
console.log(isRequired(''))           // { valid: false, error: '此字段为必填项' }
console.log(minLength(6)('abc'))      // { valid: false, error: '最少需要 6 个字符' }
console.log(isEmail('test@example.com')) // { valid: true }

五、es-toolkit 库的实现

typescript
// es-toolkit 的 curry 实现
// 从 es-toolkit/compat 中导入 curry 以实现与 lodash 的完全兼容

export function curry(
  func: (...args: any[]) => any
): (...args: any[]) => any {
  // 处理边界情况:0 或 1 个参数的函数无需柯里化
  if (func.length === 0 || func.length === 1) {
    return func
  }
  
  return function (arg: any) {
    return makeCurry(func, func.length, [arg])
  } as any
}

function makeCurry<F extends (...args: any) => any>(
  origin: F,
  argsLength: number,
  args: any[]
) {
  // 参数收集完毕,执行原函数
  if (args.length === argsLength) {
    return origin(...args)
  }
  
  // 继续收集参数
  const next = function (arg: Parameters<F>[0]) {
    return makeCurry(origin, argsLength, [...args, arg])
  }
  
  return next as any
}

六、高级应用:函数组合

柯里化与函数组合(compose/pipe)结合使用,可以构建强大的数据处理管道:

typescript
// pipe 函数:从左到右执行
const pipe = (...fns: Function[]) => (x: any) =>
  fns.reduce((acc, fn) => fn(acc), x)

// compose 函数:从右到左执行
const compose = (...fns: Function[]) => (x: any) =>
  fns.reduceRight((acc, fn) => fn(acc), x)

// 柯里化的工具函数
const add = curry((a: number, b: number) => a + b)
const multiply = curry((a: number, b: number) => a * b)
const subtract = curry((a: number, b: number) => a - b)

// 构建数据处理管道
const calculate = pipe(
  add(10),      // x + 10
  multiply(2),  // (x + 10) * 2
  subtract(5)   // ((x + 10) * 2) - 5
)

console.log(calculate(5))  // ((5 + 10) * 2) - 5 = 25

// 实际应用:数据转换管道
const processUser = pipe(
  (user: any) => ({ ...user, name: user.name.trim() }),
  (user: any) => ({ ...user, email: user.email.toLowerCase() }),
  (user: any) => ({ ...user, createdAt: new Date() })
)

const user = processUser({
  name: '  John Doe  ',
  email: 'JOHN@EXAMPLE.COM'
})

七、总结

"

柯里化是函数式编程的基石,它通过将多参数函数转换为单参数函数链,实现了参数复用、延迟执行和函数组合等强大特性。

柯里化的核心价值:

特性说明
代码简洁函数调用更加清晰和简洁
重用性轻松创建不同的函数变体,避免重复代码
可维护性修改逻辑时只需在一处进行更改
灵活性根据需要动态创建不同功能的函数
可组合性便于与 compose/pipe 结合构建数据管道

掌握柯里化技术,将帮助你写出更加优雅、可维护的函数式代码。

文章标签

# JavaScript# 函数式编程# 设计模式
返回首页