在打字稿中,为什么| &&运算符在功能类型上使用时会翻转其含义?

发布于 2025-01-25 07:57:21 字数 3346 浏览 4 评论 0原文

在此代码中,example1example2使我感到困惑

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;

//   re: example 1 and 2: 
//   After the =, | means "or" and & means "and"
//   Before the =, & means "or" and | means "and"
const example1: F1 & F2 = (a: string | number, b: string | number) => {}
example1("Hello", "World")
example1(1, 2)
// example1("Hello", 2) // Error! number is not assignable to parameter of type string... (and vice versa)

const example2: F1 | F2 = (a: string | number, b: string | number) => {}
// example2("Hello", "World") // Error! Argument of type string is not assignable to parameter of type never
// example2(1, 2) // Error! Argument of type number is not assignable to parameter of type never
// example2("Hello", 2) // Error! Argument of type string is not assignable to parameter of type never

//   re: example 3,4,5:
//   Before the =, | means "or"
// const example3: number | string = true // Error! Type Boolean is not assignable to type number | string
const example4: number | string = 1
const example5: number | string = "foo"

//   re: example 6,7
//   Before the =, & means "and"
// const example6: {a: string} & {b: number} = {a: "foo"} // Error! Type '{ a: string; }' is not assignable to type '{ a: string; } & { b: number; }'. 
// Property 'b' is missing in type '{ a: string; }' but required in type '{ b: number; }'.
const example7: {a: string} & {b: number} = {a: "foo", b: 5}

对我来说似乎就像example1中的操作员example2(在=之前)的行为与其他方式相反。这是我期望这些示例可以正常工作的方式:

const example1: F1 & F2 = (a: string | number, b: string | number) => {}
// example2("Hello", "World") // Error! Argument of type string is not assignable to parameter of type never
// example2(1, 2) // Error! Argument of type number is not assignable to parameter of type never
// example2("Hello", 2) // Error! Argument of type string is not assignable to parameter of type never

const example2: F1 | F2 = (a: string | number, b: string | number) => {}
example1("Hello", "World")
example1(1, 2)
// example1("Hello", 2) // Error! number is not assignable to parameter of type string... (and vice versa)

如果example1甚至没有编译,这也对我来说也很有意义,因为“字符串类型!==数字类型类型”。

为什么这种工作不像预期

In this code, example1 and example2 are confusing me:

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;

//   re: example 1 and 2: 
//   After the =, | means "or" and & means "and"
//   Before the =, & means "or" and | means "and"
const example1: F1 & F2 = (a: string | number, b: string | number) => {}
example1("Hello", "World")
example1(1, 2)
// example1("Hello", 2) // Error! number is not assignable to parameter of type string... (and vice versa)

const example2: F1 | F2 = (a: string | number, b: string | number) => {}
// example2("Hello", "World") // Error! Argument of type string is not assignable to parameter of type never
// example2(1, 2) // Error! Argument of type number is not assignable to parameter of type never
// example2("Hello", 2) // Error! Argument of type string is not assignable to parameter of type never

//   re: example 3,4,5:
//   Before the =, | means "or"
// const example3: number | string = true // Error! Type Boolean is not assignable to type number | string
const example4: number | string = 1
const example5: number | string = "foo"

//   re: example 6,7
//   Before the =, & means "and"
// const example6: {a: string} & {b: number} = {a: "foo"} // Error! Type '{ a: string; }' is not assignable to type '{ a: string; } & { b: number; }'. 
// Property 'b' is missing in type '{ a: string; }' but required in type '{ b: number; }'.
const example7: {a: string} & {b: number} = {a: "foo", b: 5}

To me it seems like the operators in example1 and example2 (before the =) are behaving the opposite way from the others. Here's how I would expect these examples to work:

const example1: F1 & F2 = (a: string | number, b: string | number) => {}
// example2("Hello", "World") // Error! Argument of type string is not assignable to parameter of type never
// example2(1, 2) // Error! Argument of type number is not assignable to parameter of type never
// example2("Hello", 2) // Error! Argument of type string is not assignable to parameter of type never

const example2: F1 | F2 = (a: string | number, b: string | number) => {}
example1("Hello", "World")
example1(1, 2)
// example1("Hello", 2) // Error! number is not assignable to parameter of type string... (and vice versa)

It would also make sense to me if example1 didn't even compile, since "type of string !== type of number".

Why isn't this working as expected?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

你如我软肋 2025-02-01 07:57:21

使用这些类型,

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;

以及example1的此声明,

const example1: F1 & F2 = (a: string | number, b: string | number) => {}

example1已声明了类型f1& f2,因此可以称为两个字符串的函数,也可以作为两个数字的函数。但是,不能用两个参数的混合来调用。您分配的函数值可以,但是f1& F2严格来说是的超级类型(a:string | number,b:string | number)=> void,因此,当我们分配给具有静态supertype的变量时,我们丢失了信息,就像将数字3分配给类型未知的变量一样丢失信息。

const example2: F1 | F2 = (a: string | number, b: string | number) => {}

example2的类型是可以使用String gragonments 来调用的任何一个功能的类型/代码>参数。您分配给它的功能都可以使用任何一个,因此分配很好。

但是我们永远无法称呼此功能。根本。我们将不得不通过两个参数,其中这些参数与f1f2签名兼容。 f1期望字符串f2期望号码,因此我们需要传递既是String and of String and code 一个数字,即字符串&数字。和字符串&编号从不,空类型。

|在第二个功能中变成&的原因是由于一个小东西,称为方差。函数参数是违反的,因此((a:a1)=> b1)& ((a:a2)=> b2)等于(a:a1 | a2)=> B1& B2((A:A1)=> B1)| ((a:a2)=> b2)等于(a:a1& a2)=> B1 | B2。您可以阅读Wikipedia页面以获取有关其背后数学的更多详细信息,或者写出“和”或“类型”的意思并遵循您的直觉。

With these types,

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;

and this declaration of example1,

const example1: F1 & F2 = (a: string | number, b: string | number) => {}

example1 has declared type F1 & F2, so it can be called both as a function of two strings and as a function of two numbers. But it can't be called with a mix of the two arguments. The function value you assigned to it could, but F1 & F2 is strictly a supertype of (a: string | number, b: string | number) => void, so we lost information when we assigned to a variable with a static supertype, in the same way that assigning the number 3 to a variable of type unknown loses information.

const example2: F1 | F2 = (a: string | number, b: string | number) => {}

The type of example2 is the type of either functions which can be called with string arguments or those that can be called with number arguments. The function you're assigning to it can be called with either, so the assignment is fine.

But we can never call this function. At all. We would have to pass it two arguments, where those arguments are compatible with both the F1 and F2 signatures. F1 expects string and F2 expects number, so we need to pass something that's both a string and a number, i.e. string & number. And string & number is never, the empty type.

The reason the | turns into an & in that second function is due to a little thing called variance. Function arguments are contravariant, so ((a: A1) => B1) & ((a: A2) => B2) is equal to (a: A1 | A2) => B1 & B2 and ((a: A1) => B1) | ((a: A2) => B2) is equal to (a: A1 & A2) => B1 | B2. You can read that Wikipedia page for more details on the math behind it, or write out what the "and" and "or" type means and follow your intuition.

终陌 2025-02-01 07:57:21

的确,当将这些功能组合在一起时,情况可能会使函数类型的参数混淆。

@bishwajitjha试图用集合理论解释它并不是完全错误的,即使简单的图可能就足够了。

交叉点f1& f2函数类型的f2

const example1: F1 & F2

​因此,它对应于a

  • 您可以像F1一样执行它(example1(“ Hello”,“ world”)),
  • 或者好像它是F2(example1(1) ,2)),
  • 但是您无法混合(example1(“ Hello”,2)),

更容易查看Typescript如何处理f1& f2交叉点作为超负荷,如果返回类型:

// Overload demonstration
type F1b = (a: string, b:string) => string;
type F2b = (a: number, b:number) => number;

declare const example1b: F1b & F2b;

const r1b1 = example1b("foo", "bar") // string
parseInt(r1b1) // Okay
r1b1.toFixed() // Error! Property 'toFixed' does not exist on type 'string'

const r1b2 = example1b(1, 2) // number
parseInt(r1b2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
r1b2.toFixed() // Okay

declare const r1b3: ReturnType<F1b & F2b> // When TS must choose, it uses the last overload => number
parseInt(r1b3) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
r1b3.toFixed() // Okay

declare const r1b4: ReturnType<F2b & F1b> // When TS must choose, it uses the last overload => string
parseInt(r1b4) // Okay
r1b4.toFixed() // Error! Property 'toFixed' does not exist on type 'string'

联合f1 | f2函数类型的f2

const example2: F1 | F2

示例2可以只是F1,也可以是F2,也可以是两者的过载。因为联盟没有提供任何进一步的信息,所以当我们尝试使用示例2时,我们必须同时考虑所有这些可能性 https://www.typescriptlang.org/docs/handbook/handbook/handbook/2/everyday-day-types 。

  • ​代码>
  • 作为F1&amp; f2,它可以同时使用字符串,也可以是号码(上一节),

因此两个参数必须同时 同时字符串号码,即String&amp;数字。不幸的是,这是 /a>如@silviomayolo所述。

请注意,我们仍然可以对示例2函数进行“有效”调用:

// Using never type...
declare const arg: string & number; // Inferred as never
example2(arg, arg); // Okay!.. but should never be reached

至少,返回类型(如果有一个)遵循直觉:

declare const r2b: ReturnType<F1b | F2b> // string | number
parseInt(r2b) // Error! Argument of type 'string | number' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
r2b.toFixed() // Error! Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.

分配的实际函数呢?

不幸的是,即使我们分配了一个可以接受更多不同参数类型的实际函数(甚至可能是booleannull,一个任意类型等的工会;它可以以一种或另一种方式称为F1,也可以称为F2),我们通过明确指定example2的类型而失去了更宽容的行为。

// Losing information of the actual function
const fn1d = (a: string | number | boolean, b: string | number | {a: number}) => {}
fn1d(true, {a: 1}) // Okay
const example1d: F1 & F2 = fn1d // Okay
example1d(true, {a: 1}) // Error! No overload matches this call. Overload 1 of 2, '(a: string, b: string): void', gave the following error. etc.

const example2d: F1 | F2 = fn1d // Okay
example2d(true, {a: 1}) // Error! Argument of type 'boolean' is not assignable to parameter of type 'never'.

如果是f1 | f2联合,如果我们分配了 narrawer 类型,例如(a:string,b:string)=&gt; {}:这里的TypeScript会遵循分配,并推断我们有一个较窄的类型(如

// Narrower type assignment
const example2c: F1 | F2 = (a: string, b: string) => {}
example2c("Hello", "World") // Okay
example2c(1, 2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
example2c("Hello", 2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.

​代码>示例7 ?

正如 @silviomayolo的评论所指出的那样,这些示例表明了 sigsment 的预期。

进一步,我们也可以描述他们的用法,但是它们仍然接近直觉:

declare const example8: {a: string} & {a: number}
const a = example8.a // never
parseInt(a) // Okay... but should never be reached
a.toFixed() // Property 'toFixed' does not exist on type 'never'.

declare const example9: {a: string} | {a: number}
const a9 = example9.a // string | number
parseInt(a9) // Error! Argument of type 'string | number' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
a9.toFixed() // Error! Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.

组合功能类型的参数确实具有误导性。

TypeScript Playground

It is true that the situation may be confusing regarding the arguments of types of function, when these functions are combined together.

@Bishwajitjha is not totally wrong in trying to explain it with the Set theory, even though simple diagrams may be enough.

Intersection F1 & F2 of function types

Intersection of F1 and F2 sets

const example1: F1 & F2

example1 function can be called both as F1 and as F2. Therefore it corresponds to a TypeScript function overload:

  • you can execute it as if it were an F1 (example1("Hello", "World")),
  • or as if it were an F2 (example1(1, 2)),
  • but you cannot mix (error for example1("Hello", 2))

It is even easier to see how TypeScript handles the F1 & F2 intersection as an overload, if they had return types:

// Overload demonstration
type F1b = (a: string, b:string) => string;
type F2b = (a: number, b:number) => number;

declare const example1b: F1b & F2b;

const r1b1 = example1b("foo", "bar") // string
parseInt(r1b1) // Okay
r1b1.toFixed() // Error! Property 'toFixed' does not exist on type 'string'

const r1b2 = example1b(1, 2) // number
parseInt(r1b2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
r1b2.toFixed() // Okay

declare const r1b3: ReturnType<F1b & F2b> // When TS must choose, it uses the last overload => number
parseInt(r1b3) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
r1b3.toFixed() // Okay

declare const r1b4: ReturnType<F2b & F1b> // When TS must choose, it uses the last overload => string
parseInt(r1b4) // Okay
r1b4.toFixed() // Error! Property 'toFixed' does not exist on type 'string'

Union F1 | F2 of function types

Union of F1 and F2 sets

const example2: F1 | F2

Here it is more complex, because it does not match a specific TypeScript construct.

example2 may be just an F1, or just an F2, or an overload of both. Because the union does not provide any further information, when we try using example2, we must account for all these possibilities simultaneously: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#working-with-union-types

  • as an F1, it would need both its arguments as string
  • as an F2, it would need both its arguments as number
  • as an F1 & F2, it can have either both as string or both as number (previous section)

Therefore both arguments must be at the same time string and number, i.e. string & number. Which is unfortunately never as described by @SilvioMayolo.

Note that we could still have a "valid" call to example2 function:

// Using never type...
declare const arg: string & number; // Inferred as never
example2(arg, arg); // Okay!.. but should never be reached

At least, the return type (if they had one) follows the intuition:

declare const r2b: ReturnType<F1b | F2b> // string | number
parseInt(r2b) // Error! Argument of type 'string | number' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
r2b.toFixed() // Error! Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.

What about the actual function that was assigned?

Unfortunately, even though we assign an actual function that can accept more diverse argument types (it could even have been unions with boolean, null, an arbitrary type, etc.; provided that it can be called one way or another as an F1, or alternatively as an F2), we lose the more tolerant behaviour by explicitly specifying the type of example2.

// Losing information of the actual function
const fn1d = (a: string | number | boolean, b: string | number | {a: number}) => {}
fn1d(true, {a: 1}) // Okay
const example1d: F1 & F2 = fn1d // Okay
example1d(true, {a: 1}) // Error! No overload matches this call. Overload 1 of 2, '(a: string, b: string): void', gave the following error. etc.

const example2d: F1 | F2 = fn1d // Okay
example2d(true, {a: 1}) // Error! Argument of type 'boolean' is not assignable to parameter of type 'never'.

In the case of F1 | F2 union, the situation would have been different if we had assigned a narrower type instead, e.g. (a: string, b: string) => {}: here TypeScript would have followed the assignment, and inferred that we have a narrower type (as exactly described in https://www.typescriptlang.org/docs/handbook/2/narrowing.html#assignments)

// Narrower type assignment
const example2c: F1 | F2 = (a: string, b: string) => {}
example2c("Hello", "World") // Okay
example2c(1, 2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.
example2c("Hello", 2) // Error! Argument of type 'number' is not assignable to parameter of type 'string'.

What about example3 to example7?

As pointed out by @SilvioMayolo's comment, these examples show how assignment is as expected.

Going further, we could describe their usage as well, but they would still be close to intuition:

declare const example8: {a: string} & {a: number}
const a = example8.a // never
parseInt(a) // Okay... but should never be reached
a.toFixed() // Property 'toFixed' does not exist on type 'never'.

declare const example9: {a: string} | {a: number}
const a9 = example9.a // string | number
parseInt(a9) // Error! Argument of type 'string | number' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'.
a9.toFixed() // Error! Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.

Only arguments of combined function types are really misleading.

TypeScript Playground

失退 2025-02-01 07:57:21

由于类型没有赌注集,因此所有这些都遵循集合理论。

首先,让我们了解类型系统中&amp;的关键行为。

{a:'数字'}&amp; {b:'string'} =&gt; {a:'number',b:'string'}

添加了两种成分类型,因为它们是独立的类型。

请记住这一点,让我们尝试证明这种行为,以及f1&amp; F2f1 | F2

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;



Using SET Theory

Eq 1.  (A | B) = A + B - (A & B) 
Eq 2.  (A & B) = A + B - (A | B) 

Also, our F1 and F2 are independent type sets, hence

Eq 3. F1 & F2 => F1 + F2


Case 1: F1 & F2

> F1 + F2     ...(By using eq 3)
> (string, string) => void + (number, number) => void 
> Basically means we can only call F1 and F2 with their own types (not their union one)

 

Case 2: F1 | F2


> F1 + F2 - (F1 & F2)    ....(By Using Eqn 1)
> F1 + F2 - (F1 + F2)    ....(By using Eqn 3)
> never (as everything cancels out)


As types are nothing bet sets, It all follows the Set theory.

First, let's understand a crucial behavior of & in type system.

{ a: 'number' } & { b: 'string' } => { a: 'number', b: 'string' }

Both of the constituents types got added as they were independent types.

Keeping this in mind, let's try to prove this behavior and what should be the equivalent result of F1 & F2 and F1 | F2

type F1 = (a: string, b:string) => void;
type F2 = (a: number, b:number) => void;



Using SET Theory

Eq 1.  (A | B) = A + B - (A & B) 
Eq 2.  (A & B) = A + B - (A | B) 

Also, our F1 and F2 are independent type sets, hence

Eq 3. F1 & F2 => F1 + F2


Case 1: F1 & F2

> F1 + F2     ...(By using eq 3)
> (string, string) => void + (number, number) => void 
> Basically means we can only call F1 and F2 with their own types (not their union one)

 

Case 2: F1 | F2


> F1 + F2 - (F1 & F2)    ....(By Using Eqn 1)
> F1 + F2 - (F1 + F2)    ....(By using Eqn 3)
> never (as everything cancels out)


~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文