Appearance
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.__(一个特殊对象)作为占位符来避免这个问题
