简体   繁体   English

如何专门化歧视工会的类型?

[英]How to specialize type of discriminated union?

Assume I have two discriminated unions and a third type alias which is a union type containing both.假设我有两个可区分的联合和第三种类型的别名,它是包含两者的联合类型。

How do I create a function which can take both the discriminated unions but whose return type depends on the specific type of the value passed?如何创建一个 function 可以同时采用可区分的联合,但其返回类型取决于传递的值的特定类型?

I thought this would work, given that Specialize would return { value: number } if the passed value belongs to First and number if it belongs to Second , but it doesn't.我认为这会起作用,因为如果传递的值属于Firstnumber如果属于Second ,则Specialize将返回{ value: number } ,但事实并非如此。

type First = "a" | "b";
type Second = "c" | "d";
type Letter = First | Second;
type Specialize<R> = R extends First ? { value: number } : number;

const fun = <M extends Letter, R extends Specialize<M>>(letter: M): R => {
    return letter === "a" || letter === "b"
        ? { value: 1 }
        : 2;
}

What am I misunderstanding here?我在这里有什么误解?

Please notice that I do not want to remove the R which constrains the return type.请注意,我不想删除限制返回类型的R Instead, I'd like to constrain the return type to the passed argument.相反,我想将返回类型限制为传递的参数。


Type '{ value: number; } | 2' is not assignable to type 'R'.
  '{ value: number; } | 2' is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint 'number | { value: number; }'.
    Type '{ value: number; }' is not assignable to type 'R'.
      '{ value: number; }' is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint 'number | { value: number; }'.

This is a job for overloads.这是一项超载的工作。 By providing specific overloads for narrowed cases, the return value will also be narrowed.通过为缩小的情况提供特定的重载,返回值也将缩小。

// Overloads
function fun(letter: First): { value: number };
function fun(letter: Second): number;
// General case
function fun(letter: Letter): { value: number } | number {
    return letter === "a" || letter === "b"
        ? { value: 1 }
        : 2;
}

const x = fun("a")   // x: { value: number }
const y = fun("c")   // y: number

Your generics-based approach can't work because the caller gets to decide what R is, and R can be any type that extends Specialize<M> .您的基于泛型的方法无法工作,因为调用者可以决定R是什么,并且R可以是扩展Specialize<M>任何类型。 That's what the errors are telling you.这就是错误告诉你的。 You have no way to return the specific subtype the caller has chosen.您无法返回调用者选择的特定子类型。

(Given TypeScript's structural typing, I'm not aware of particular way to create a subtype of Specialize<R> . But the compiler can't prove that it's impossible, and I'm sure someone more clever with TypeScript than I am can think of one. But by using extends you specifically said that the caller can require a subtype, and that makes returning R impossible.) (鉴于 TypeScript 的结构类型,我不知道创建Specialize<R>子类型的特定方法。但编译器无法证明这是不可能的,而且我敢肯定 TypeScript 比我想象的更聪明一个。但是通过使用extends ,您明确表示调用者可能需要一个子类型,这使得返回R是不可能的。)

The main problem is the polymorphism given by the generic constraint.主要问题是泛型约束给出的多态性。

Defining your function as generic with an argument type which extends from another, you must ensure your return statement actually returns a value which is assignable of type T and its extensions (where T is the type argument).将 function 定义为具有从另一个扩展的参数类型的泛型,您必须确保您的return语句实际上返回一个可分配类型T及其扩展的值(其中T是类型参数)。

Let's suppose you define this type假设您定义了这种类型

type MoreSpecialize<R> = R extends First ? { value: 10 } : 20;

... which extends from Specialize<> . ...从Specialize<>扩展而来。 I could invoke the function fun with this type argument,我可以用这个类型参数调用 function fun

const r: MoreSpecialize<Letter> = fun2<Letter, MoreSpecialize<Letter>>('a');

But, r is not actually of type MoreSpecialize<Letter> because fun returns { value: 1 } | 2但是, r实际上不是MoreSpecialize<Letter>类型,因为fun返回{ value: 1 } | 2 { value: 1 } | 2 , not { value: 10 } | 20 { value: 1 } | 2 ,不是{ value: 10 } | 20 { value: 10 } | 20 . { value: 10 } | 20 . That's why the compiler complains.这就是编译器抱怨的原因。

Solution解决方案

  1. Define your function with concrete types if you are not going to need extensions, indeed.如果您确实不需要扩展,请使用具体类型定义您的 function。
const fun = (letter: Letter): Specialize<Letter> => {
    return letter === "a" || letter === "b"
        ? { value: 1 }
        : 2;
}
  1. Force casting the return value [.].强制转换返回值 [.]。
    Not the best way to go.不是 go 的最佳方式。
const fun2 = <M extends Letter, R extends Specialize<M>>(letter: M): R => {
    return (
        letter === "a" || letter === "b"
            ? { value: 1 }
            : 2
    ) as R;
}
  1. @RobNapier's solution is totally fine. @RobNapier 的解决方案完全没问题。

Hope it helps.希望能帮助到你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM