Skip to content

2797. 带有占位符的部分函数

2797. 带有占位符的部分函数

ts
type JSONValue =
  | null
  | boolean
  | number
  | string
  | JSONValue[]
  | { [key: string]: JSONValue }
type Fn = (...args: JSONValue[]) => JSONValue

function partial(fn: Fn, args: JSONValue[]): Fn {
  return function (...restArgs) {
    let _args = args.map((item) => {
      if (restArgs.length > 0 && item === '_') {
        return restArgs.shift()
      }
      return item
    })
    if (restArgs.length > 0) {
      _args = _args.concat(restArgs)
    }
    return fn(...(_args as JSONValue[]))
  }
}

偏函数 vs 柯里化

偏函数(partial application)和柯里化(currying)容易被混淆:

  • 柯里化:将 f(a, b, c) 转成 f(a)(b)(c),每次只接受一个参数
  • 偏函数:预先固定部分参数,返回接受剩余参数的新函数

这道题进一步支持了占位符 _,允许在任意位置留空,后续调用时填充。

占位符的处理

args 中值为 '_' 的位置代表占位符。返回的函数收到新参数时,按顺序替换占位符:遇到一个 _ 就从 restArgs 头部取一个参数替换。剩余的 restArgs(如果有)追加到参数列表末尾。

shift() 直接修改原数组,所以每次替换后 restArgs 变短,符合「按顺序填充」的语义。

潜在问题

  • 如果 restArgs 的参数少于占位符数量,未替换的 _ 会作为字面字符串传进原函数——这通常不是期望行为
  • shift() 有副作用,如果在 map 之外还需要 restArgs,这里会丢数据
  • 占位符使用字符串 '_' 而非 Symbol,如果原参数本身就是 '_' 字符串会造成歧义。Rambda 等函数式库使用 R.__(一个特殊对象)作为占位符来避免这个问题