输入类型如何选择输出并支持联合?

时间:2019-03-05 14:26:29

标签: typescript typescript-generics

我有以下代码,它们可以采用可迭代或异步可迭代,并返回相同类型的对象。它也有一个可以选择管理的数字。

function _buffer<T>(size: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T> {
  throw new Error('not important')

}

function* syncBuffer<T>(size: number, iterable: Iterable<T>): IterableIterator<T> {
  throw new Error('not important')
}

export function buffer<T>(
  size: number
): {
  (curriedIterable: AsyncIterable<T>): AsyncIterableIterator<T>
  (curriedIterable: Iterable<T>): IterableIterator<T>
  (curriedIterable: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>
}
export function buffer<T>(size: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T>
export function buffer<T>(size: number, iterable: Iterable<T>): IterableIterator<T>
export function buffer<T>(size: number, iterable: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>
export function buffer<T>(size: number, iterable?: Iterable<T> | AsyncIterable<T>) {
  if (iterable === undefined) {
    return <R>(curriedIterable) => buffer<R>(size, curriedIterable)
  }
  if (iterable[Symbol.asyncIterator]) {
    return _buffer(size, iterable as AsyncIterable<T>)
  }

  return syncBuffer(size, iterable as Iterable<T>)
}


function run(a: AsyncIterable<any>) {
  return buffer(4, a)
}


function run(a: AsyncIterable<any> | Iterable<any>) {
  return buffer(4, a)
  return buffer(4)(a)
}

但是我在编译时遇到以下类型错误。

Overload signature is not compatible with function implementation.ts(2394) 

// in reference to 
export function buffer<T>(size: number, iterable: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>

但是似乎并非如此吗?如果我删除了该重载签名,则在不知道是哪个联合的情况下,就无法调用该函数。

2 个答案:

答案 0 :(得分:1)

TypeScript中的

功能overloads将功能签名分为两面:一是 call 签名列表,由函数调用者看到。这些也称为“过载签名”。其中可以有一个或多个。呼叫签名没有内容。

另一面是实现签名,通过功能的实现而不是调用方看到。只能有一个实现签名。实现签名必须具有主体。

调用签名必须在实现签名之前。实现签名必须与调用签名“兼容”(例如,实现签名不能要求任何调用签名都不提供的参数),但是它们不是同一个人。


您的问题:您正在尝试将实现签名视为呼叫签名。

解决方法:在列表的末尾添加一个附加的呼叫签名。它可以与实现签名相同:

// call signatures:
function foobar<T>(foo: AsyncIterable<T>): AsyncIterable<T>;
function foobar<T>(foo: Iterable<T>): Iterable<T>;
// add the following call signature
function foobar<T>(foo: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>;

// implementation signature:
function foobar<T>(foo: Iterable<T> | AsyncIterable<T>) {
  return foo
}

希望有帮助。祝你好运!


已更新以处理新表格:

type CurriedBufferResult<T> = {
  (curriedIterable: AsyncIterable<T>): AsyncIterableIterator<T>
  (curriedIterable: Iterable<T>): IterableIterator<T>
  (curriedIterable: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>
};
export function buffer<T>(
  size: number
): CurriedBufferResult<T>;
export function buffer<T>(size: number, iterable: AsyncIterable<T>): AsyncIterableIterator<T>
export function buffer<T>(size: number, iterable: Iterable<T>): IterableIterator<T>
export function buffer<T>(size: number, iterable: Iterable<T> | AsyncIterable<T>): Iterable<T> | AsyncIterable<T>
export function buffer<T>(size: number, iterable?: Iterable<T> | AsyncIterable<T>):
Iterable<T> | AsyncIterable<T> | CurriedBufferResult<T> 
{
  // impl here
  return null!;
}

这与以前的解释相同,但是我已经明确注释了实现签名返回类型,以表明其将包含调用签名的可能返回类型的意图。这是确保呼叫签名和实现签名兼容的一部分。

现在由您来确保实现(在// impl here中)符合该注释。您可能看到的问题是您的函数实现实际上没有返回上面注释的类型,并且推断的实现返回类型与调用签名不匹配。

再次祝你好运。

答案 1 :(得分:1)

这是一个单独的答案,因为它提出了不同的方法。人们有时会遇到过载签名的问题之一是他们don't always behave intuitively with unions of parameters。这是一个愚蠢的例子:

// call signatures
function foo(x: string): number;
function foo(x: number): string;
// implementation
function foo(x: string | number): number | string {
  return (typeof x === 'string') ? x.length : "a".repeat(x);
}

函数foo()接受string并返回number,或者接受number并返回string。并按预期工作:

const num: number = foo("string"); // okay
const str: string = foo(12345); // okay

但是人们希望您可以将其传递为string | number类型,并获得number | string类型的值。有时,这种期望来自于将实现签名与调用签名相混淆,而其他时候,似乎编译器应该能够选择多个重载并对其进行统一。但这不会发生。编译器仅选择一个重载签名(无论如何至少从TS3.3开始。过去,调用函数类型的并集是不可能的,但是now you can ...好,caveats也是如此。也许最终将发生超载统一):

const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // error!

在我的另一个答案中,建议通过添加专门对应于该联合的呼叫签名来解决此问题。确实有效:

function foo(x: string): number;
function foo(x: number): string;
function foo(x: string | number): number | string; // added
function foo(x: string | number): number | string {
  return (typeof x === 'string') ? x.length : "a".repeat(x);
}

const num: number = foo("string"); // okay
const str: string = foo(12345); // okay
const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // okay

但是还有另一种方式。您可以使用generic functionsconditional types来代替三个呼叫签名,如下所示:

function foo<X extends string | number>(x: X): X extends string ? number : string;
function foo(x: string | number): number | string {
  return (typeof x === 'string') ? x.length : "a".repeat(x);
}

const num: number = foo("string"); // okay
const str: string = foo(12345); // okay
const oops: string | number = foo(Math.random() < 0.5 ? "string" : 12345); // okay

为什么行得通?

那么,基于传入的参数,泛型类型X将被推断为string | number的任何子类型。对于foo("string"),推断Xstring literal类型"string"。对于foo(12345),推断Xnumeric literal类型12345。在与Math.random()的通话中,X被推断为"string" | 12345。因此,所有呼叫都应该成功。

他们还返回什么?这就是条件类型的来源。类型X extends string ? number : string意味着如果Xstring的子类型,那么条件类型将是number。否则,条件类型将为string。因此,对于foo("string")X extends string为true,返回类型为number。对于foo(12345)X extends string为false,返回类型为string。那么带有Math.random()的联合类型呢?好吧,因为条件类型为distribute over unions,所以最终变成了所需的number | string

您可能会或可能不想在功能上做类似的事情:

type MaybeIterable<T> = AsyncIterable<T> | Iterable<T>;
type UnmaybeIterable<M extends MaybeIterable<any>> = M extends Iterable<infer T> ? Iterable<T> : M extends AsyncIterable<infer T> ? AsyncIterable<T> : never;
type CurriedBufferResult = {
  <M extends MaybeIterable<any>>(curriedIterable: M): UnmaybeIterable<M>
 };
export function buffer(
  size: number
): CurriedBufferResult;
export function buffer<M extends MaybeIterable<any>>(size: number, iterable: M): UnmaybeIterable<M>;
export function buffer(size: number, iterable?: MaybeIterable<any>): CurriedBufferResult | UnmaybeIterable<any>
{
  // impl here
  return null!;
}

这就是您想要的吗?不确定。