mja*_*kic 1 functional-programming currying composition typescript
在经历了可变参数类型之后,我想做这个,但我想知道如何使用函数数组。
这是我的第一次尝试:
function curry<T extends any[]>(fn: (...args: T) => any) {
return function(...args: T) {
return args.length >= fn.length
? fn(...args)
: curry(fn.bind(undefined, ...args));
}
}
Run Code Online (Sandbox Code Playgroud)
但是因为fn.bind
我得到“'(...args: T) => any' 类型的'this' 上下文不能分配给'(this: undefined, ...args: any[ ]) => 任何'。”
有任何想法吗?
您并没有真正在代码中使用可变参数元组 tpes。curry()
您正在实现的特定风格是采用部分参数列表,然后可能返回另一个采用列表的部分剩余部分的函数。这意味着参数的初始元组T
可能被分成许多不同的部分,因此这不会partialCall()
像文档中的函数那样从上下文中自动推断出来。
相反,您需要明确地将T
元组分解为可能的子元组。让我们表示curry()
需要一个函数的所需输出的类型,该函数的参数是元组A
,其返回类型是R
:
type Curried<A extends any[], R> =
<P extends Partial<A>>(...args: P) => P extends A ? R :
A extends [...SameLength<P>, ...infer S] ? S extends any[] ? Curried<S, R>
: never : never;
type SameLength<T extends any[]> = Extract<{ [K in keyof T]: any }, any[]>
Run Code Online (Sandbox Code Playgroud)
让我把它翻译成英文。ACurried<A, R>
是一个泛型函数,其参数必须是某种P
限制为 的元组类型Partial<A>
。
对于元组,Partial<A>
最终意味着您可以省略元组的任何后缀(从某个地方到最后)。So[1, 2, 3]
可分配给Partial<[1,2,3,4,5,6,7]>
,但[1, 2, 4]
不是。有一点麻烦undefined
,因为[1, undefined, 3]
它也可以分配给Partial<[1,2,3,4,5,6,7]>
,但我将忽略它,如果它变得重要,它可以解决。无论如何,这意味着参数Curried<A, R>
必须是A
元组的某个前缀(初始块)。
的返回类型Curried<A, R>
取决于P
传入的前缀。如果P
是整个 tuple A
,则返回类型是 just R
(当您最终为函数提供所有参数时会发生这种情况)。否则,您将拆分A
为前缀P
及其后缀S
,并返回一个新的柯里化函数类型Curried<S, R>
。
拆分A
为[...SameLength<P>, ...infer S]
使用可变参数元组类型。请注意,这SameLength<P>
只是一个与 长度相同P
但元素类型为的元组any
。这避免了一个问题,P
被推断为很窄(说A
是[number, number, number]
,然后P
是[0, 0]
,你不能分割[number, number, string]
成[0, 0, ...infer S]
因为number
是不能分配给0
。但是我们关心的是长在这里,所以我们分裂[number, number, string]
成[any, any, ...infer S]
和作品,并推断S
是string
)。
好的,使用并实现它:
function curry<A extends any[], R>(fn: (...args: A) => R): Curried<A, R> {
return (...args: any[]): any =>
args.length >= fn.length ? fn(...args as any) : curry((fn as any).bind(undefined, ...args));
}
Run Code Online (Sandbox Code Playgroud)
我在 的实现中使用了大量类型断言curry()
,因为编译器在尝试验证返回的函数是否可赋值给Curried<A, R>
. 而不是担心它,我只是告诉编译器不要费心验证安全性,并自己负责使实现正确。(如果错了,我会责怪自己,而不是编译器)。
好的,我们开始了。它有效吗?
const fn = (a: string, b: number, c: boolean) => (a.length <= b) === c ? "yep" : "nope";
const cFn = curry(fn);
const val1 = cFn("")(1)(true);
console.log(val1); // yep
const val2 = cFn("", 1, true);
console.log(val2); // yep
const val3 = cFn()()()()("", 1)()()(true); // yep
Run Code Online (Sandbox Code Playgroud)
在我看来很好。请注意,根据我的定义,如果您Curried<A, R>
不带参数调用 a ,您只会返回 a Curried<A, R>
。以下是一些故意错误,因此您可以看到编译器捕获了它们:
// errors
cFn(1, 1, true); // error!
// ~ <-- not a string
cFn("", 1, true, false); // error!
// ~~~~~ <-- Expected 0-3 arguments, but got 4
cFn("")(1)(false)(true); // error!
//~~~~~~~~~~~~~~~ <-- This expression is not callable.
Run Code Online (Sandbox Code Playgroud)
这些在我看来是正确的错误。
归档时间: |
|
查看次数: |
1006 次 |
最近记录: |