我们能否使函数类型依赖于(派生自?)其函数类型(调用时)?
Can we make functions type dependant (derived from?) on its function types (when called)?
假设我们有一组复杂的函数:
interface Succes<a> {
kind: 'succes'
value: a
}
interface Failure<e> {
kind: 'failure'
error: e
}
type Result<a, e> = Succes<a> | Failure<e>
const unit = <a>(a:a): Succes<a> => ({kind: 'succes', value: a})
const fail = <e>(e:e): Failure<e> => ({kind: 'failure', error: e})
interface fun<a, b> { (a: a): b }
const map = <a,b, e>(f: fun<a, b>): fun<Result<a,e>, Result<b,e>> => r =>
r.kind == 'succes' ? unit(f(r.value)) : r
const join = <a, e>(r:Result<Result<a, e>, e>): Result<a, e> =>
r.kind == 'failure' ? r : r.value
const then = <a, b, e>(f:fun<a, Result<b, e>>) => (r:Result<a, e>) =>
join(map(f)(r))
const railRoad = <a, e>(r: Result<a, e>) => ({
map: <b>(f: (a:a) => b) => railRoad<b, e>(map<a, b, e>(f)(r)),
then: <b>(f: (a:a) => Result<b,e>) => railRoad(then(f)(r))
})
突出显示的位是最后 railRoad
位。
我们可以这样使用:
railRoad<User, *somePossibleErrors*>(getUser())
.then(formatName)
.map(greet)
这有一些不错的可能性,它允许我们处理任意长度的函数管道中的所有错误,但在此 - 我们必须指定我们必须处理的错误集。我希望 railRoad
中的 e
从 map
函数派生,然后是函数 e
值。
这是我们能做的吗?因此,当我们使用 .then
或 .map
调用函数时,它们的签名会添加到原始函数中的类型 (railRoad
)?
此代码的 TS 游乐场是 here
在接下来的内容中,我将使用 TS 类型参数命名约定(大写字母),并且我已将所有地方的 "succes" 更改为 "success"。如果您愿意,可以忽略这些外观变化。我保留了将 "success" 类型称为 A
和 B
的惯例,并将 "failure" 类型称为 E
和 F
。我承认 F
对于错误类型来说不是一个好名字,但是与 A
/B
平行的字母对我来说太诱人了。
主要问题可能是你的then()
函数和你的railRoad()
函数没有扩展错误类型的概念。这是使用 then()
的一种方法:
const then = <A, B, F>(f: fun<A, Result<B, F>>) => <E>(r: Result<A, E>) =>
join(map<A, Result<B, E | F>, E>(f)(r))
这里的初始函数采用将 A
变成 Result<B, F>
的东西,然后它采用 Result<A, E>
,并产生 Result<B, E | F>
。 E
和 F
合并为 E | F
是让您的推理在以后发挥作用的关键因素。
第二个问题是您的 railRoad()
函数使用了 TypeScript 3.4 中添加的 higher order generic type inference support,但特定的公式导致编译器性能很差。函数的推断类型是递归类型,如 {map: () => {map: () => ....
。为了防止我创建了一个接口 RailRoaded<A, E>
来表示 return 类型的 railRoad()
:
interface RailRoaded<A, E> {
map<B>(f: (some: A) => B): RailRoaded<B, E>;
then<B, F>(f: (some: A) => Result<B, F>): RailRoaded<B, E | F>;
}
const railRoad = <A, E>(r: Result<A, E>): RailRoaded<A, E> => ({
map: <B>(f: (some: A) => B) => railRoad<B, E>(map<A, B, E>(f)(r)),
then: <B, F>(f: (some: A) => Result<B, F>) => railRoad(then(f)(r))
})
将 railRoad
的 return 类型注释为 RailRoaded<A, E>
可以显着提高性能,因为编译器只需要 验证 该函数是兼容并且不会尝试为其合成新的 return 类型。无论如何,您还可以看到 RailRoaded<A, E>
的 then()
方法如何生成联合类型的失败类型。
差不多就这些了。调用它时会发生以下情况:
const chooChoo = railRoad(getUser()).then(formatName).map(greet);
// const chooChoo: RailRoaded<string, "no-user" | "no-name">
看起来不错,我觉得。只是为了确定,让我们将其分开以查看每个步骤的推论:
const engine = railRoad(getUser());
// const engine: RailRoaded<User, "no-user">
const car = engine.then(formatName);
// const car: RailRoaded<string, "no-user" | "no-name">
const caboose = car.map(greet);
// const caboose: RailRoaded<string, "no-user" | "no-name">
还不错。好的,希望对您有所帮助;祝你好运!
假设我们有一组复杂的函数:
interface Succes<a> {
kind: 'succes'
value: a
}
interface Failure<e> {
kind: 'failure'
error: e
}
type Result<a, e> = Succes<a> | Failure<e>
const unit = <a>(a:a): Succes<a> => ({kind: 'succes', value: a})
const fail = <e>(e:e): Failure<e> => ({kind: 'failure', error: e})
interface fun<a, b> { (a: a): b }
const map = <a,b, e>(f: fun<a, b>): fun<Result<a,e>, Result<b,e>> => r =>
r.kind == 'succes' ? unit(f(r.value)) : r
const join = <a, e>(r:Result<Result<a, e>, e>): Result<a, e> =>
r.kind == 'failure' ? r : r.value
const then = <a, b, e>(f:fun<a, Result<b, e>>) => (r:Result<a, e>) =>
join(map(f)(r))
const railRoad = <a, e>(r: Result<a, e>) => ({
map: <b>(f: (a:a) => b) => railRoad<b, e>(map<a, b, e>(f)(r)),
then: <b>(f: (a:a) => Result<b,e>) => railRoad(then(f)(r))
})
突出显示的位是最后 railRoad
位。
我们可以这样使用:
railRoad<User, *somePossibleErrors*>(getUser())
.then(formatName)
.map(greet)
这有一些不错的可能性,它允许我们处理任意长度的函数管道中的所有错误,但在此 - 我们必须指定我们必须处理的错误集。我希望 railRoad
中的 e
从 map
函数派生,然后是函数 e
值。
这是我们能做的吗?因此,当我们使用 .then
或 .map
调用函数时,它们的签名会添加到原始函数中的类型 (railRoad
)?
此代码的 TS 游乐场是 here
在接下来的内容中,我将使用 TS 类型参数命名约定(大写字母),并且我已将所有地方的 "succes" 更改为 "success"。如果您愿意,可以忽略这些外观变化。我保留了将 "success" 类型称为 A
和 B
的惯例,并将 "failure" 类型称为 E
和 F
。我承认 F
对于错误类型来说不是一个好名字,但是与 A
/B
平行的字母对我来说太诱人了。
主要问题可能是你的then()
函数和你的railRoad()
函数没有扩展错误类型的概念。这是使用 then()
的一种方法:
const then = <A, B, F>(f: fun<A, Result<B, F>>) => <E>(r: Result<A, E>) =>
join(map<A, Result<B, E | F>, E>(f)(r))
这里的初始函数采用将 A
变成 Result<B, F>
的东西,然后它采用 Result<A, E>
,并产生 Result<B, E | F>
。 E
和 F
合并为 E | F
是让您的推理在以后发挥作用的关键因素。
第二个问题是您的 railRoad()
函数使用了 TypeScript 3.4 中添加的 higher order generic type inference support,但特定的公式导致编译器性能很差。函数的推断类型是递归类型,如 {map: () => {map: () => ....
。为了防止我创建了一个接口 RailRoaded<A, E>
来表示 return 类型的 railRoad()
:
interface RailRoaded<A, E> {
map<B>(f: (some: A) => B): RailRoaded<B, E>;
then<B, F>(f: (some: A) => Result<B, F>): RailRoaded<B, E | F>;
}
const railRoad = <A, E>(r: Result<A, E>): RailRoaded<A, E> => ({
map: <B>(f: (some: A) => B) => railRoad<B, E>(map<A, B, E>(f)(r)),
then: <B, F>(f: (some: A) => Result<B, F>) => railRoad(then(f)(r))
})
将 railRoad
的 return 类型注释为 RailRoaded<A, E>
可以显着提高性能,因为编译器只需要 验证 该函数是兼容并且不会尝试为其合成新的 return 类型。无论如何,您还可以看到 RailRoaded<A, E>
的 then()
方法如何生成联合类型的失败类型。
差不多就这些了。调用它时会发生以下情况:
const chooChoo = railRoad(getUser()).then(formatName).map(greet);
// const chooChoo: RailRoaded<string, "no-user" | "no-name">
看起来不错,我觉得。只是为了确定,让我们将其分开以查看每个步骤的推论:
const engine = railRoad(getUser());
// const engine: RailRoaded<User, "no-user">
const car = engine.then(formatName);
// const car: RailRoaded<string, "no-user" | "no-name">
const caboose = car.map(greet);
// const caboose: RailRoaded<string, "no-user" | "no-name">
还不错。好的,希望对您有所帮助;祝你好运!