Make A Fully Typed Curry Function
Refs: https://medium.com/free-code-camp/typescript-curry-ramda-types-f747e99744ab (opens in a new tab)
TL;RD:本文是从 TS 角度,用体操构建出 curry function 的类型,包含完整的参数推导
Curry Function
关于函数 currify,在平时使用函数式编程都很熟悉了
Utilities Types
Head Utility
type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
Tail Utility
和我们之前经常看到的要获取最后一个的 Last
不太一样(如下)
type Last<T extends any[]> = T extends [...infer I, infer L] ? L : never;
这个 Tail
的作用是获取 Head 之后的剩余部分,和 Haskell 的 tail 方法一样
给出我的代码:
type Tail<T extends any[]> = T extends [any, ...(infer R extends any[])] ? R : [];
文中代码:
type Tail2<T extends any[]> = ((...t: T) => any) extends (
_: any,
...tail: infer R
) => any
? R
: [];
HasTail Utility
检查是否含有 Tail 部分
type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
当然我们还得熟悉 TS 中的 extends
infer
type
等关键字的作用,这里不细说,文中有一些对于 infer
的练习
Curry V0
热身结束,开始递归
type CurryV0<P extends any[], R> = (
arg0: Head<P>
) => HasTail<P> extends true ? CurryV0<Tail<P>, R> : R;
type c1 = CurryV0<[1, 2, 3, 4, 5], boolean>; // type c1 = (arg0: 1) => CurryV0<[2, 3, 4, 5], boolean>
测试下
declare function curryV0<P extends any[], R>(
f: (...args: P) => R
): CurryV0<P, R>;
const toCurry02 = (name: string, age: number, single: boolean) => true;
const curried02 = curryV0(toCurry02);
const test23 = curried02("Jane")(26)(true); // boolean
但是目前有个问题是,只能接受一个参数 curried02('Jane')(26, true)
会报错,需要追踪每一个函数参数是否被使用。
Curry FInal
还需要工具 type 正是前文提到的 Last,文中的实现,略复杂:
type Last<T extends any[]> = {
0: Last<Tail<T>>;
1: Head<T>;
}[HasTail<T> extends true ? 0 : 1];
length
type Length<T extends any[]> = T["length"];
Prepend,将多个参数合并到一个数组?这样就能来追踪
type Prepend<E, T extends any[]> = ((head: E, ...args: T) => any) extends (
...args: infer U
) => any
? U
: T;
效果:
type testP = Prepend<number, [1, 2]>; // [number, 1, 2]
drop,将一个 tuple 的前 N 个元素抛弃,得到后面的 tuple
type Drop<N extends number, T extends any[], I extends any[] = []> = {
O: Drop<N, Tail<T>, Prepend<any, I>>;
1: T;
}[Length<I> extends N ? 1 : 0];
// 不过在 playground 的里面编译有问题,改写成如下
type Drop<N extends number, T extends any[], I extends any[] = []> = Length<
I
> extends N
? T
: Drop<N, Tail<T>, Prepend<any, I>>;
测试下:
type dd = Drop<3, [1, 2, 3, 4, 5]>; // [4, 5]
这样我们就能把已经被消费的参数,从总体列表剔除。
还需要一些工具类型。。
包括高级特性 placeholders
的 ts 实现,酷,这个 gap 是直接用的 ramda.R.__
f(1, 2, 3);
f(_, 2, 3)(1);
f(_, _, 3)(1)(2);
f(_, 2, _)(1, 3);
f(_, 2)(1)(3);
f(_, 2)(1, 3);
f(_, 2)(_, 3)(1);
/* It requires TS to **re-check** a type `X` against a type `Y`, and type `Y` will only be enforced if it fails.
* e.g. Cast<[string], any> // [string]
* e.g. Cast<[string], number> // number
**/
type Cast<X, Y> = X extends Y ? X : Y;
// position
type Pos<I extends any[]> = Length<I>;
// 下一个 postion 通过 加数组长度
type Next<I extends any[]> = Prepend<any, I>;
type Prev<I extends any[]> = Tail<I>;
// 构造出迭代器 Iterator<2> => [any, any], Iterator<2, Iterator<2>> => [any, any, any, any]
type Iterator<
Index extends number = 0,
From extends any[] = [],
I extends any[] = []
> = {
0: Iterator<Index, Next<From>, Next<I>>; // Next 来递增
1: From;
}[Pos<I> extends Index ? 1 : 0];
//
type Reverse<T extends any[], R extends any[] = [], I extends any[] = []> = {
0: Reverse<T, Prepend<T[Pos<I>], R>, Next<I>>;
1: R;
}[Pos<I> extends Length<T> ? 1 : 0];
type Concat<T1 extends any[], T2 extends any[]> = Reverse<
Reverse<T1> extends infer R ? Cast<R, any[]> : never,
T2
>;
type Append<E, T extends any[]> = Concat<T, [E]>;
看不下去了。。直接出结论吧:最终 ramda.js 已经支持了 (opens in a new tab)
包括 ts-toolbelt (opens in a new tab)(最大的 ts 工具库)也支持 (opens in a new tab)
所以这个类型配合上代码的实现,就可以是完美了
有空再看看 ramda.js 这个函数编程库 (opens in a new tab)吧!