原始(并且仍然推荐)答案
遗憾的是,目前这在 Typescript 中是不可能的,除非你准备好为你可能想要的每个长度定义 pipe,这似乎不是很有趣。
但你可以靠近!
此示例使用 Promise-inspired then 链接函数,但您可以根据需要重命名。
// Alias because TS function types get tedious fast
type Fn<A, B> = (_: A) => B;
// Describe the shape of Pipe. We can't actually use `class` because while TS
// supports application syntax in types, it doesn't in object literals or classes.
interface Pipe<A, B> extends Fn<A, B> {
// More idiomatic in the land of FP where `pipe` has its origins would be
// `map` / `fmap`, but this feels more familiar to the average JS/TS-er.
then<C>(g: Fn<B, C>): Pipe<A, C>
}
// Builds the `id` function as a Pipe.
function pipe<A>(): Pipe<A, A> {
// Accept a function, and promise to augment it.
function _pipe<A, B>(f: Fn<A, B>): Pipe<A, B> {
// Take our function and start adding stuff.
return Object.assign(f, {
// Accept a function to tack on, also with augmentations.
then<C>(g: Fn<B, C>): Pipe<A, C> {
// Compose the functions!
return _pipe<A, C>(a => g(f(a)));
}
});
}
// Return an augmented `id`
return _pipe(a => a);
}
const foo = pipe<number>()
.then(x => x + 1)
.then(x => `hey look ${x * 2} a string!`)
.then(x => x.substr(0, x.length) + Array(5).join(x.substring(x.length - 1)))
.then(console.log);
foo(3); // "hey look 8 a string!!!!!"
Check it out on Typescript Playground
编辑:危险区域
这是一个灵活大小定义的示例,它的容量有限,但对于大多数应用程序来说应该足够大,您始终可以按照该模式对其进行扩展。我不建议使用它,因为它非常混乱,但我想我会把它放在一起来娱乐并展示这个概念。
在底层它使用你的 JS 实现(以类型安全的方式实现它是可能的,但很费力),在现实世界中你可能只是把它放在一个 JS 文件中,将此签名更改为 declare function ,并删除实现。 TS 不会让你在一个文件中这样做而不抱怨,所以我只是手动连接它作为示例。
注意:
- 为避免推理问题,您需要将参数类型注释到链中的第一个函数或返回的函数。后者对我来说似乎更整洁,所以这就是我在示例中使用的内容
- 我的条件类型留下了一些错误空间。如果您在第一个和最后一个定义的参数之间提供任何未定义的参数,我断言的类型可能不正确。应该可以解决这个问题,我现在无法面对它?
type Fn<A, B> = (_: A) => B;
const Marker: unique symbol = Symbol();
type Z = typeof Marker;
function pipe<
A,
B,
C = Z,
D = Z,
E = Z,
F = Z,
G = Z,
H = Z,
I = Z,
J = Z,
K = Z
>(
f: Fn<A, B>,
g?: Fn<B, C>,
h?: Fn<C, D>,
i?: Fn<D, E>,
j?: Fn<E, F>,
k?: Fn<F, G>,
l?: Fn<G, H>,
m?: Fn<H, I>,
n?: Fn<I, J>,
o?: Fn<J, K>
): Fn<
A,
K extends Z
? J extends Z
? I extends Z
? H extends Z
? G extends Z
? F extends Z
? E extends Z
? D extends Z
? C extends Z
? B
: C
: D
: E
: F
: G
: H
: I
: J
: K
> {
// @ts-ignore
const pipe = (f, ...fs) => x => f === undefined ? x : pipe(...fs)(f(x));
return pipe(f, g, h, i, j, k, l, m, n, o);
}
// Typechecks fine.
const foo: Fn<number, void> = pipe(
x => x + 1,
x => `hey look ${x * 2} a string!`,
x => x.substr(0, x.length) + Array(5).join(x.substring(x.length - 1)),
console.log
)
foo(3) // hey look 8 a string!!!!!
// Typechecks fine with fewer params.
const bar: Fn<string, Date> = pipe(
x => x + 1,
_ => new Date()
);
console.log(bar("This string is ignored, but we'll put a date in console."));
Check out this monstrosity on TS Playground