Typescript: 제안: 가변 종류 -- 가변 함수에 특정 유형 제공

에 만든 2015년 10월 29일  ·  265코멘트  ·  출처: microsoft/TypeScript

다양한 종류

가변 함수에 특정 유형 지정

이 제안을 통해 Typescript는 다양한 수의 매개변수를 사용하는 고차 함수에 유형을 제공할 수 있습니다.
이와 같은 함수에는 concat , apply , curry , compose 및 함수를 래핑하는 거의 모든 데코레이터가 포함됩니다.
Javascript에서 이러한 고차 함수는 가변 함수를 인수로 받아들일 것으로 예상됩니다.
ES2015 및 ES2017 표준에서는 프로그래머가 배열과 객체 모두에 대해 스프레드 인수와 나머지 매개변수를 사용하기 시작함에 따라 이러한 사용이 더욱 보편화될 것입니다.
이 제안은 고차 종류를 기반으로 하는 매우 일반적인 단일 타이핑 전략으로 이러한 사용 사례를 해결합니다.

이 제안은 다음을 포함한 여러 문제를 완전히 또는 부분적으로 해결할 것입니다.

  1. #5331 -- 나머지 ...인수를 위한 유형으로서의 튜플
  2. #4130 -- 컴파일러가 스프레드 연산자를 사용할 때 매개변수/호출 대상 서명 불일치를 잘못 보고합니다.
  3. #4988 -- 튜플은 Array.prototype.slice()로 복제할 수 있어야 합니다.
  4. #1773 -- Variadic 제네릭?
  5. #3870 -- 교차 유형에 대한 제네릭의 나머지 유형.
  6. #212 -- bind, call 및 apply는 유형이 지정되지 않습니다(#3694의 this-function 유형 필요).
  7. #1024 -- 제네릭이 있는 ...rest 매개변수를 입력했습니다.

Typescript-Handbook: sandersn/ TypeScript-Handbook@76f5a75868de3fb1ad4dbed5db437a8ab61a2698 에서 이 제안을 업데이트
저는 현재 제안의 간단한 부분이 구현된 sandersn/ TypeScript@f3c327aef22f6251532309ba046874133c32f4c7 에서 진행 중인 구현을 가지고 있습니다.
그것은 내 이전 제안인 #5296의 2부를 대체합니다.
편집: 할당 가능성에 대한 섹션을 추가했습니다. 더 이상 엄격하게 #5296을 대체하는지 확신할 수 없습니다.

curry 미리보기 예

curry 인수가 두 개인 함수는 Javascript와 Typescript로 간단하게 작성할 수 있습니다.

function curry(f, a) {
    return b => f(a, b);
}

유형 주석이 있는 Typescript에서:

function curry<T, U, V>(f: (t: T, u: U) => V, a:T): (b:U) => V {
    return b => f(a, b);
}

그러나 가변 버전은 Javascript로 작성하기 쉽지만 TypeScript에서는 유형을 지정할 수 없습니다.

function curry(f, ...a) {
    return ...b => f(...a, ...b);
}

다음은 이 제안의 가변 종류를 사용하여 curry 를 입력하는 예입니다.

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...a, ...b);
}

여기서 사용하는 가변 튜플 유형의 구문은 Javascript의 값에 사용되는 스프레드 및 나머지 구문과 일치합니다.
이것은 배우기 쉽지만 값 표현식에서 유형 주석을 구별하기 어렵게 만들 수 있습니다.
유사하게, 연결을 위한 구문은 실제로 두 튜플 유형의 연결임에도 불구하고 튜플 구성처럼 보입니다.

이제 curry 에 대한 예제 호출을 살펴보겠습니다.

function f(n: number, m: number, s: string, c: string): [number, number, string, string] {
    return [n,m,s,c];
}
let [n,m,s,c] = curry(f, 1, 2)('foo', 'x');
let [n,m,s,c] = curry(f, 1, 2, 'foo', 'x')();

첫 번째 통화에서는

V = [number, number, string, string]
...T = [number, number]
...U = [string, string]

두 번째 통화에서는

V = [number, number, string, string]
...T = [number, number, string, string]
...U = []

통사론

가변 종류 변수의 구문은 ...T 여기서 _T_는 규칙에 따라 단일 대문자 또는 T 다음에 PascalCase 식별자가 오는 식별자입니다.
가변 종류 변수는 여러 구문 컨텍스트에서 사용할 수 있습니다.

가변 종류는 함수 및 클래스를 포함하여 형식 매개 변수 바인딩의 일반적인 위치에 바인딩될 수 있습니다.

function f<...T,...U>() {}
}
class C<...T> {
}

그리고 모든 유형 주석 위치에서 참조할 수 있습니다.

function makeTuple<...T>(ts:...T): ...T {
    return ts;
}
function f<...T,...U>(ts:...T): [...T,...U] {
    // note that U is constrained to [string,string] in this function
    let us: ...U = makeTuple('hello', 'world');
    return [...ts, ...us];
}

유형 변수와 같은 가변 종류 변수는 매우 불투명합니다.
그들은 유형 변수와 달리 하나의 작업을 가지고 있습니다.
다른 종류나 실제 튜플과 연결될 수 있습니다.
이에 사용되는 구문은 튜플 확산 구문과 동일하지만 유형 주석 위치에 있습니다.

let t1: [...T,...U] = [...ts,...uProducer<...U>()];
let t2: [...T,string,string,...U,number] = [...ts,'foo','bar',...uProducer<...U>(),12];

튜플 유형은 가변 유형의 인스턴스이므로 이전에 유형 주석이 허용된 모든 위치에 계속 나타납니다.

function f<...T>(ts:...T): [...T,string,string] { 
    // note the type of `us` could have been inferred here
    let us: [string,string] = makeTuple('hello', 'world');
    return [...ts, ...us];
}

let tuple: [number, string] = [1,'foo'];
f<[number,string]>(tuple);

의미론

가변 종류 변수는 모든 길이의 튜플 유형을 나타냅니다.
유형의 집합을 나타내므로 유형 이론에서 사용된 대로 '종류'라는 용어를 사용하여 참조합니다.
그것이 나타내는 유형 세트는 길이에 관계없이 튜플이므로 '종류'를 '가변'으로 한정합니다.

따라서 가변 튜플 종류의 변수를 선언하면 모든 _single_ 튜플 유형을 사용할 수 있습니다.
유형 변수와 마찬가지로 종류 변수는 함수, 클래스 등에 대한 매개변수로만 선언할 수 있으며, 그러면 본문 내부에서 사용할 수 있습니다.

function f<...T>(): ...T {
    let a: ...T;
}

가변 종류로 입력된 인수로 함수를 호출하면 특정 튜플 유형이 종류에 할당됩니다.

f([1,2,"foo"]);

튜플 유형 할당 ...T=[number,number,string] ...T . So in this application of f , let a:...T is instantiated as let a:[number,number,string] . However, because the type of a is not known when the function is written, the elements of the tuple cannot be referenced in the body of the function. Only creating a new tuple from a`가 허용됩니다.
예를 들어 튜플에 새 요소를 추가할 수 있습니다.

function cons<H,...Tail>(head: H, tail: ...Tail): [H,...Tail] {
    return [head, ...tail];
}
let l: [number, string, string, boolean]; 
l = cons(1, cons("foo", ["baz", false]));

유형 변수와 마찬가지로 가변 종류 변수는 일반적으로 유추될 수 있습니다.
cons 대한 호출은 다음과 같이 주석 처리될 수 있습니다.

l = cons<number,[string,string,boolean]>(1, cons<string,[string,boolean]>("foo", ["baz", false]));

예를 들어 cons 는 _H_ 유형과 _...Tail_ 유형의 두 변수를 유추해야 합니다.
가장 안쪽 호출에서 cons("foo", ["baz", false]) , H=string...Tail=[string,boolean] .
가장 바깥쪽 호출에서 H=number...Tail=[string, string, boolean] .
_...Tail_에 할당된 유형은 목록 리터럴을 튜플로 입력하여 얻습니다. 튜플 유형의 변수도 사용할 수 있습니다.

let tail: [number, boolean] = ["baz", false];
let l = cons(1, cons("foo", tail));

또한 가변 종류 변수는 유형과 연결될 때 유추될 수 있습니다.

function car<H,...Tail>(l: [H, ...Tail]): H {
    let [head, ...tail] = l;
    return head;
}
car([1, "foo", false]);

여기에서, 유형 l 로 추정된다 [number, string, boolean] .
그런 다음 H=number...Tail=[string, boolean] .

형식 유추에 대한 제한

검사기가 두 종류 사이의 경계가 어디에 있어야 하는지 추측할 수 없기 때문에 연결된 종류를 유추할 수 없습니다.

function twoKinds<...T,...U>(total: [...T,string,...U]) {
}
twoKinds("an", "ambiguous", "call", "to", "twoKinds")

검사기는 할당 여부를 결정할 수 없습니다.

  1. ...T = [string,string,string], ...U = [string]
  2. ...T = [string,string], ...U = [string,string]
  3. ...T = [string], ...U = [string,string,string]

일부 명확한 호출은 이 제한의 피해입니다.

twoKinds(1, "unambiguous", 12); // but still needs an annotation!

해결책은 유형 주석을 추가하는 것입니다.

twoKinds<[string,string],[string,string]>("an", "ambiguous", "call", "to", "twoKinds");
twoKinds<[number],[number]>(1, "unambiguous", 12);

rotate 와 같이 형식 인수와 함수 본문 사이에 확인할 수 없는 종속성이 발생할 수 있습니다.

function rotate(l:[...T, ...U], n: number): [...U, ...T] {
    let first: ...T = l.slice(0, n);
    let rest: ...U = l.slice(n);
    return [...rest, ...first];
}
rotate<[boolean, boolean, string], [string, number]>([true, true, 'none', 12', 'some'], 3);

이 함수는 입력할 수 있지만 n 와 종류 변수 사이에는 종속성이 있습니다. 유형이 정확하려면 n === ...T.length 가 true여야 합니다.
이것이 실제로 허용되어야 하는 코드인지 확실하지 않습니다.

클래스 및 인터페이스에 대한 의미론

의미는 클래스와 인터페이스에서 동일합니다.

TODO: 의미 체계에 일부 클래스별 주름이 있을 수 있습니다.

튜플과 매개변수 목록 간의 할당 가능성

튜플 종류는 범위 내에서 함수의 나머지 인수에 유형을 제공하는 데 사용할 수 있습니다.

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {
    return ap(...args);
}
function f(a: number, b: string) => string {
    return b + a;
}
apply(f, [1, 'foo']);

이 예에서의 매개 변수 목록 f: (a: number, b:string) => string 종류에 대한 인스턴스 튜플 타입에 할당해야합니다 ...T .
유추되는 튜플 유형은 [number, string] 이며, 이는 (a: number, b: string) => string(...args: [number, string]) => string 할당 가능해야 함을 의미합니다.

부작용으로 함수 호출은 함수에 튜플 종류가 없더라도 튜플을 나머지 매개변수로 분산하여 이 할당 가능성을 활용할 수 있습니다.

function g(a: number, ...b: [number, string]) {
    return a + b[0];
}
g(a, ...[12, 'foo']);

선택적 및 나머지 매개변수에 대해 생성된 튜플 유형

튜플은 선택적 매개 변수를 직접 나타낼 수 없으므로 튜플 종류로 형식이 지정된 함수 매개 변수에 함수를 할당하면 생성된 튜플 형식은 튜플 형식의 합집합입니다.
커리한 후 h 유형을 확인합니다.

function curry<...T,...U,V>(cur: (...args:[...T,...U]) => V, ...ts:...T): (...us:...U) => V {
    return ...us => cur(...ts, ...us);
}
function h(a: number, b?:string): number {
}
let curried = curry(h, 12);
curried('foo'); // ok
curried(); // ok

여기 ...T=([number] | [number, string]) , 그래서 curried: ...([number] | [number, string]) => number 예상대로 호출할 수 있습니다. 불행히도 이 전략은 나머지 매개변수에 대해서는 작동하지 않습니다. 이것들은 그냥 배열로 바뀝니다:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

여기, curried: ...([string, boolean[]] | [boolean[]]) => number .
튜플의 마지막 요소가 배열인 튜플 나머지 매개변수가 있는 함수에 대한 특별한 경우가 있다면 이것이 지원될 수 있다고 생각합니다.
이 경우 함수 호출은 배열과 일치하는 올바른 유형의 추가 인수를 허용합니다.
그러나 그것은 가치가 있기에는 너무 복잡해 보입니다.

typescript의 다른 부분에 대한 확장

  1. Typescript는 사용자가 빈 튜플 유형을 작성하는 것을 허용하지 않습니다.
    그러나 이 제안에서는 가변 종류가 빈 튜플에 바인딩할 수 있어야 합니다.
    따라서 Typescript는 내부적으로만 빈 튜플을 지원해야 합니다.

이러한 예제의 대부분은 현재 Typescript에서 고정 인수 함수로 가능하지만 이 제안을 사용하면 가변적으로 작성할 수 있습니다.
consconcat cons 와 같은 일부는 현재 Typescript의 동종 배열에 대해 작성할 수 있지만 이제 튜플 종류를 사용하여 이종 튜플에 대해 작성할 수 있습니다.
이것은 일반적인 Javascript 관행을 더 밀접하게 따릅니다.

연결된 유형 반환

function cons<H,...T>(head: H, tail:...T): [H, ...T] {
    return [head, ...tail];
}
function concat<...T,...U>(first: ...T, ...second: ...U): [...T, ...U] {
    return [...first, ...second];
}
cons(1, ["foo", false]); // === [1, "foo", false]
concat(['a', true], 1, 'b'); // === ['a', true, 1, 'b']
concat(['a', true]); // === ['a', true, 1, 'b']

let start: [number,number] = [1,2]; // type annotation required here
cons(3, start); // == [3,1,2]

매개변수로 연결된 유형

function car<H,...T>(l: [H,...T]): H {
    let [head, ...tail] = l;
    return head;
}
function cdr<H,...T>(l: [H,...T]): ...T {
    let [head, ...tail] = l;
    return ...tail;
}

cdr(["foo", 1, 2]); // => [1,2]
car(["foo", 1, 2]); // => "foo"

인수로서의 가변 함수

function apply<...T,U>(f: (...args:...T) => U, args: ...T): U {
    return f(...args);
}

function f(x: number, y: string) {
}
function g(x: number, y: string, z: string) {
}

apply(f, [1, 'foo']); // ok
apply(f, [1, 'foo', 'bar']); // too many arguments
apply(g, [1, 'foo', 'bar']); // ok
function curry<...T,...U,V>(f: (...args:[...T,...U]) => V, ...ts:...T): (...us: ...U) => V {
    return us => f(...ts, ...us);
}
let h: (...us: [string, string]) = curry(f, 1);
let i: (s: string, t: string) = curry(f, 2);
h('hello', 'world');
function compose<...T,U,V>(f: (u:U) => U, g: (ts:...T) => V): (args: ...T) => V {
    return ...args => f(g(...args));
}
function first(x: number, y: number): string {
}
function second(s: string) {
}
let j: (x: number, y: number) => void = compose(second, first);
j(1, 2);

TODO는 : 수 f 반환 ...U 대신 U ?

데코레이터

function logged<...T,U>(target, name, descriptor: { value: (...T) => U }) {
    let method = descriptor.value;
    descriptor.value = function (...args: ...T): U {
        console.log(args);
        method.apply(this, args);
    }
}

열린 질문

  1. tuple-to-parameter-list 할당 가능성 이야기가 유효합니까? 특히 선택적 및 나머지 매개변수 주변에서 흔들리고 있습니다.
  2. 선택적 매개변수의 경우와 같이 유추된 유형이 튜플의 합집합이 될까요? bind , call , apply 는 Function에 정의된 메소드이기 때문에 bind 호출 사이트가 아닌 함수 생성 시점에 타입 인자를 바인딩해야 합니다. (예를 들어). 그러나 이것은 오버로드가 있는 함수가 해당 인수와 관련된 형식을 취하거나 반환할 수 없음을 의미합니다. 즉, 오버로드 형식의 조합이어야 합니다. 또한 Function에는 유형 인수를 직접 지정하는 생성자가 없으므로 bind 등에게 올바른 유형을 제공할 방법이 없습니다. TODO: 여기에 예제를 추가하세요. 이 문제가 반드시 가변 함수에 고유한 것은 아닙니다.
  3. 나머지 매개변수는 튜플 유형에서 생성된 경우에도 멋진 호출 구문을 유지하기 위해 특수 케이스를 사용해야 합니까? (이 제안에서 튜플 종류로 입력된 함수는 나머지 매개변수에 배열을 전달해야 하며 추가 매개변수를 가질 수 없습니다.)
Fix Available In Discussion Suggestion

가장 유용한 댓글

이 문제는 이제 TS 4.0용으로 예정된 #39094로 수정되었습니다.

모든 265 댓글

+1, 이것은 TypeScript의 함수형 프로그래밍에 정말 유용합니다! 이것은 선택적 또는 나머지 인수와 함께 어떻게 작동합니까? 더 구체적으로, compose 함수를 나머지 인수 또는 선택적 인수가 있는 함수에 사용할 수 있습니까?

좋은 지적. 튜플은 추가 멤버를 허용하는 객체일 뿐이므로 허용되는 가장 작은 튜플 유형을 optional-param 함수에 할당할 수 있다고 생각합니다. 그러나 그것은 이상적이지 않습니다. compose 예시를 알아낼 수 있는지 확인한 다음 제안서를 업데이트하겠습니다.

실제로 Union 유형이 더 잘 작동할 것입니다. 같은 것

function f(a: string, b? number, ...c: boolean[]): number;
function id<T>(t: T): T;
let g = compose(f, id): (...ts: ([string] | [string, number] | [string, number, boolean[]]) => number

g("foo"); // ok
g("foo", 12); // ok
g("foo", 12, [true, false, true]); // ok

그러나 이것은 여전히 ​​휴식 매개 변수를 깨뜨립니다.

@ahejlsberg , 튜플 종류가 작동하는 방식에 대한 아이디어가 있었던 것 같습니다.

그래서 :+1: 이것에 대해. 정보를 위해 이것은 #3870과 관련이 있습니다. TypeScript에서 작성 유형 API 를 구현하려고 시도했지만 이 제안에 언급된 몇 가지 제한 사항을 해결해야 합니다. 이것은 확실히 그러한 문제 중 일부를 해결할 것입니다!

때때로 이러한 튜플 유형을 유지하는 대신 특히 compose와 같은 것을 사용하여 "병합"하고 싶을 수도 있습니다. 예를 들어:

function compose<T, ...U>(base: T, ...mixins: ...U): T&U {
    /* mixin magic */
}

또한 많은 예제에서 기본 요소를 사용했습니다. 특히 충돌이 있는 경우 더 복잡한 작업이 어떻게 작동합니까?

불행히도 이 제안은 튜플 종류에 대한 유일한 합성 연산자가 [T,...U] 이므로 #3870 또는 유형 합성을 다루지 않습니다. 이것을 T + ...U 로 작성할 수도 있지만(유형에 어떤 일이 발생하는지 더 잘 나타냄) #3870 및 유형 구성 라이브러리에는 T & ...U 합니다. 나는이 방법을 사용할 수있을 것 같아요,하지만 난 이해할 필요가 @JsonFreeman의@jbondc의 첫 # 3870에서의 아이디어. 어떻게 작동해야 하는지 알 수 있다면 제안을 확대하겠습니다.

참고: 저는 [...T, ...U] 구문을 사용하기로 결정했는데, 이는 동등한 값 확산 구문처럼 보이지만 T + ...U 는 유형에 어떤 일이 일어나고 있는지 더 잘 나타냅니다. 둘 다 사용하는 경우 +& 가 사용할 연산자일 수 있습니다.

큰:+1: 이것에 대해!

+1 굉장해! 그런 것들을 훨씬 더 표현력 있고 가볍게 표현할 수 있습니다.

#3870에서 내 요점은 여기에서 문제인 것 같습니다. 특히, 가변 형식 매개변수에 대한 형식 인수를 유추하는 것에 대해 걱정합니다.

형식 인수 유추는 다소 복잡한 프로세스이며 시간이 지남에 따라 미묘한 방식으로 변경되었습니다. 유형 유형 인수를 유추하기 위해 인수가 매개변수와 일치할 때 후보가 유추되는 순서나 유추되는 후보의 수(주어진 유형 매개변수에 대해)에 대한 보장이 없습니다. 사용자에게 표시되는 결과는 (대부분의 경우) 이러한 세부 정보를 노출하지 않기 때문에 일반적으로 문제가 되지 않았습니다. 그러나 추론 결과에서 튜플 유형을 만들면 추론의 순서와 개수가 모두 노출됩니다. 이러한 세부 사항은 관찰할 수 있도록 의도된 것이 아닙니다.

이 얼마나 심각한가? 추론이 정확히 어떻게 작동하는지에 달려 있다고 생각합니다. 다음 결과는 무엇입니까?

function f<...T>(x: ...T, y: ...T): ...T { }
f(['hello', 0, true], [[], 'hello', { }]); // what is the type returned by f?

@jbondc , - 좋은 생각인 것 같습니다. 새 유형 연산자를 한 번에 하나씩 도입해야 한다고 생각하기 때문에 여기에서 다루지 않겠습니다. &+ 새로운 유형을 생성하지만 & 는 교차 유형을 생성하는 반면 + 는 새로운 튜플 유형을 생성합니다(이것이 내가 [T,...U] 구문을 선호하는 이유입니다) T + ...U [T,...U] 대신 T + ...U , [T,U] 유형에 대해 이미 이 작업을 수행하기 때문입니다.

@JsonFreeman 반복되는 종류 매개변수로 두 가지 중 하나를 수행하는 것이

  1. 조합 유형: f(['hello', 1], [1, false]): [string | number, number | boolean]
  2. 특히 유형 인수 추론이 복잡한 것으로 판명되면 반복되는 튜플 종류 매개변수의 추론을 허용하지 않습니다. 이 같은:
f(['hello', 1], [1, false]) // error, type arguments required
f<[string, number]>(['hello', 1], [1, false]) // error, 'number' is not assignable to 'string'
f<[string | number, number | boolean]>(['hello', 1], [1, false]); // ok

@Igorbek에 연결된 반응 확장과 같은 실제 라이브러리에는 일반적으로 하나의 튜플 종류 매개변수만 있으므로 (1)과 (2) 모두 특별히 사용할 수는 없지만 실제 코드에 많은 영향을 미치지 않아야 합니다.

위의 예에서, curry 가장 단단한 추론하는 것입니다 - 당신이 건너가 f: (...args:[...T,...U]) => V 추론, ...ts:...T , 다시 가서 설정 ...U 무엇을 f 의 매개변수에서 ...T 를 소비한 후 남습니다.

나는 이것을 프로토타이핑하기 시작했지만(sandersn/TypeScript@1d5725d) 아직 그 정도까지는 가지 못했습니다. 그것이 효과가 있는지 어떤 아이디어가 있습니까?

의미 체계가 명확하지 않은 경우(예: 동일한 확산 유형 매개변수에 대한 반복적인 추론)을 허용하지 않는 편입니다. 그것은 위에서 언급한 나의 걱정도 덜어준다.

카레를 타이핑하기 위한 좋은 메커니즘이 생각나지 않습니다. 지적했듯이 ...T 인수를 사용하려면 첫 번째 함수의 매개변수 목록을 건너뛰고 남은 것을 확인해야 합니다. 목록에서 최종 매개변수가 아닌 경우 확산 유형 매개변수에 대한 추론을 연기하는 정책이 있어야 합니다. 지저분해질 수 있습니다.

즉, 시도해 볼 가치가 있다고 생각합니다. 기능에 대한 수요가 높습니다.

동일한 컨텍스트에서 발생하는 여러 튜플 종류를 건너뛰어야 한다고 생각합니다(예: (...T,string,...U) => V 와 같은 최상위 또는 [...T,...U,...T] 와 같이 연결됨). 그런 다음 건너뛴 종류에 대해 여러 패스를 만들어 이미 추론된 종류를 제거하고 여전히 모호한 종류를 다시 건너뛸 수 있습니다. 어느 시점에서 추론에 사용할 수 있는 단일 종류가 없으면 중지하고 오류를 반환합니다.

네. 복잡한.

비슷한 문제에서 영감을 얻을 수 있습니다. 실제로 합집합이나 교집합을 추론하는 문제와 다소 유사합니다. function f<T>(x: T | string[]) 에서와 같이 추론 컨텍스트의 구성원인 유형 매개변수를 포함하는 공용체 유형을 유추할 때 T를 유추할지 여부를 알 수 없습니다. 공용체 유형의 의도된 표현은 다음과 같을 수 있습니다. string[] . 따라서 typescript는 먼저 다른 모든 구성 요소를 추론한 다음 추론이 이루어지지 않은 경우 T를 추론합니다.

교차의 경우 다른 교차 구성 요소에 걸쳐 인수 유형을 분할해야 할 수 있기 때문에 훨씬 더 어렵습니다. Typescript는 교차 유형을 전혀 추론하지 않습니다.

튜플이 시퀀스의 마지막 유형인 경우에만 확산 튜플을 허용하면 어떻게 될까요? 따라서 [string, ...T] 는 허용되지만 [...T, string] 는 허용되지 않습니까?

내가 올바르게 이해한다면 실제로 TypeScript의 mixin 이야기를 해결할 것입니다. 이 이해가 맞습니까?

아마도. 예를 들어주실 수 있나요? 나는 mixin 패턴에 능숙하지 않습니다.

가변 종류 변수의 구문은 ...T입니다. 여기서 T는 관례상 단일 대문자인 식별자이거나 T 다음에 PascalCase 식별자가 오는 식별자입니다.

유형 매개변수 식별자의 경우를 개발자에게 맡길 수 있습니까?

@aleksey-bykov +1. 나는 그것이 사실이되어서는 안되는 이유를 보지 못한다.

Haskell에 대한 배경 지식이 있는 개발자는 감사할 것입니다.

죄송합니다. 해당 문장은 모호하게 구문 분석될 수 있습니다. 나는 엄격하게 구문 분석하기 위해 '또는'을 의미했습니다. "관례에 따라 (단일 대문자 || T 다음에 PascalCase 식별자가 옴)". 나는 식별자의 대소문자를 제한하는 것을 제안하지 않고 단지 규칙을 지적합니다.

하지만 그만한 가치가 있는 _I_는 Haskell 배경을 가지고 있고 내가 쓰는 언어의 관습을 깨는 것을 좋아하지 않습니다.

탈선해서 죄송합니다. 내 마지막 궁금한 질문(만일 내가 물어봐도 상관없다면) 고장날 수 있는 TypeScript의 "관례"는 무엇이며 누가 염려합니까?

@sandersn

T & ...U T & U & V & ... (직관적인 동작)를 의미한다고 가정하면 이것은 확인을 입력해야 합니다.

function assign<T, U, ...V>(obj: T, src: U, ...srcs: ...V): T & U & ...V {
  if (arguments.length < 2) return <T & U & ...V> obj

  for (const key of Object.keys(src)) {
    (<any> obj)[key] = (<any> src)[key]
  }

  if (arguments.length === 2) return <U> obj
  return mixin<T, ...V>(obj, ...srcs)
}

또는 정의 파일에서:

interface Object {
    assign<T, U, ...V>(host: T, arg: U, ...args: ...V): T & U & ...V
}

@aleksey-bykov 내가 말하는 규칙은 유형 매개변수 식별자의 경우입니다. 누가 걱정합니까? 이전에 본 적이 없는 새로운 Typescript 코드를 읽어야 하는 사람들 -- 규칙은 새로운 독자가 새 코드를 더 빨리 이해하는 데 도움이 됩니다.

@sandersn @aleksey-bykov는 다음이 _구문적으로_ 유효하지 않다는 인상을 받았습니다.

function assign<a, b, ...cs>(x: a, y: b, ...zs: ...cs): a & b & ...cs;

@isiahmeadows &| 종류에 대한 작업은 이 제안에서 다루지 않지만, 그렇지 않은 경우 미결 질문/향후 작업에 추가해야 합니다. 현재 제안된 유일한 연산자는 연결: [THead, ...TTail] 입니다.

한 가지 차이점은 연결은 여전히 ​​튜플 유형을 생성하는 반면 &| 는 각각 교차 및 통합 유형을 생성한다는 것입니다.

@sandersn TypeScript의assign 예제는 그것으로 변경하기 쉽습니다.

하지만:

  1. 교차는 연결과 유사하지만 목록을 연결하는 것보다 사전을 연결하는 것과 비슷합니다. Variadic 유형은 기존 기계 위에 구현될 수 있습니다.
  2. Union은 공통 부분만 유지한다는 점을 제외하면 교차로와 같습니다. 다시 한 번, variadic 유형은 기존 기계 위에 구현될 수 있습니다.

@isiahmeadows 교차는 일반적으로 사전의 연결이 아닙니다. 이는 개체 유형의 교차에만 해당되지만 예를 들어 공용체의 교차에는 해당되지 않습니다. Union은 또한 객체가 공통적으로 가지고 있는 속성을 취하는 것과 동일하지 않습니다. 둘은 그 안에 있는 일련의 값으로 더 잘 특징지어집니다.

@sandersn 가변 종류의 유형 인수 유추에 대해 약간 혼란 스럽습니다. 여기서 유추해야 할 것은 무엇입니까?

function foo<...T>(...rest: ...T): ...T { }
foo('str', 0, [0]);

결과는 [string, number, number[]] 입니까? 즉, 왼쪽에서 오른쪽 순서로 후보를 추가하는 형식 인수 유추에 의존해야 하며, 이는 사소한 가정이 아닙니다. 또한 유형 시스템이 사용자에게 유추 후보 목록을 처음으로 표시하는 경우이기도 합니다.

나는 그것이 실험적/초기 제안이라는 것을 알고 있지만 나머지 매개변수에 대한 ...T 구문에 대해 논의할 수 있습니다. 내 관점에서, 그것은 실제로 작동하지 않습니다.
따라서 제안된 구문은 다음과 같습니다.

declare function f<...T>(...a: ...T);

나머지 매개변수의 기존 구문과 비교해 보겠습니다.

declare function f(...a: number[]);

따라서 나머지 인수를 잡는 a 매개변수의 유형은 number[] 이므로 배열임을 분명히 알 수 있습니다. 유추하여 제안서의 ...T 배열을 나타냄을 유추할 수 있습니다. 그러나 그것은 아주 분명하지 않습니다.
다음으로 더 제한적인 휴식 매개변수를 정의할 수 있다고 가정해 보겠습니다.

declare function f(...a: [number, string]);
// same as
declare function f(c: number, d: string); // or very close to

이제 a 유형이 튜플(배열)임을 알 수 있습니다.

내 제안은 ...T 개념을 처리하여 "일부 추상적인 정렬된 유형 목록"으로 표현하는 보다 일관된 방법을 사용하는 것입니다. 스프레드 연산자를 사용하는 것과 같은 방식으로 사용합니다.

var a: [number, string] = [1, "1"];
var b = [true, ...a]; // this must be [boolean, number, string], but it doesn't work :)

따라서 ...a 변수의 경우 1, "1" 입니다.

...T 개념으로 나머지 매개변수를 정의하는 구문:

declare function f<...T>(...a: [...T]);
declare function g<H, ...T>(head: H, ...tail: [...T]): [H, ...T];

저에게는 훨씬 더 의미가 있습니다.

@Igorbek declare function f<...T>(...a: ...T); 이미 그렇게 작동했다는 가정 하에 실행했습니다. 하지만 declare function f(...a: [number, string]); 가) 많이 사용되지 않는 것 같습니다.

더 명확하게.

원래 제안된 나머지 매개변수 구문:

function func<...T>(...a: ...T)

내가 할 수 있다면

function g<...T>(...a: ...T): [number, ...T] { ... }

그러면 나는 이것을 할 수 있을 것이다:

function f<...T>(...a: ...T): [...T] { return a; }

따라서 a 의 유형은 [...T] 이지만(그렇게 반환) 서명에서 ...T 로 정의했습니다.
...T[...T] 는 같다고 할 수 있지만 변수의 경우에는 동작하지 않습니다.
변수의 경우:

var a = [1, 2];
[a] === [[1,2]];
[...a] === [1, 2];
f(...a) === f(1, 2)
...a === 1, 2 // virtually

표준 휴식 매개변수에 동일하게 적용하면

function f(...a: number[]): number[] { return a; }

anumber[] (반환 유형별)이며 서명에 정의된 것과 동일합니다.

@isiahmeadows 예, function f(...a: [number, string]) 가 작동하지 않습니다. 방금 휴식 매개변수를 처리하는 방법에 대한 생각을 개발했습니다.

그래서, 더 나아갑니다. 유형 매개변수를 명시적으로 정의하기 위해 다음 구문이 제안되었습니다.

function f<...T, ...U>()
f<[number, string], [boolean, number]>();

다음으로 변경:

f<...[number, string], ...[boolean, number]>();

따라서 다음과 같이 작동할 수도 있습니다.

function g<T1, T2, T3>()

g<A, B, C>();
// same as
g<...[A, B, C]>();
g<...[A], ...[B, C]>(); 
g<...[A], B, C, ...[]>();

@JsonFreeman 이 내 프로토타입이 작동하는 방식입니다. 그러나 형식 유추 알고리즘이 작동하는 이유를 이해할 만큼 충분히 익숙하지 않습니다. 다시 말해, 문제는 왼쪽에서 오른쪽으로의 추론이 _사소한_ 가정이 아니라 올바른 가정인지 여부입니다. 신원 사례의 경우 대답은 예이지만 대답이 아니오인 경우를 구성할 수 있는지 모르겠습니다.

또한 노출된 유형 유추 후보 집합의 예를 통해 작업할 수 있습니까? 내가 말했듯이, 나는 추론 알고리즘의 작동을 잘 이해하지 못하므로 예가 당신이 의미하는 바를 이해하는 데 도움이 될 것입니다.

그리고 더 나은:

function<...T>(...a: T): T;
// same as
function<...T>(...a: [...T]): T;

나머지 유형 매개변수를 나타내기 위해 유형 식별자에 [] 접두사를 사용하는 것이 좋습니다.

function fn<R, []T>(...a:[]T): R;

...T 보다 1자 짧고 (제 생각에는) 시각적 노이즈가 적습니다.

@aleksey-bykov 나는 실제로 그것에 대해 반대 의견입니다. 기존의 나머지 매개변수 구문과 맞지 않아 언뜻 보기에도 명확하지 않다고 생각합니다.

[...T] / T 나머지 배열 매개변수 유형이 저에게 훨씬 더 나은 것 같습니다. 다시 한 번 배열 및 해당 sprad 연산자와 비교하십시오.

| 배열 | 유형(제안에서) | 유형(내 업데이트) |
| --- | --- | --- |
| var x = [1,2] | 아니 | T = [T1, T2] |
| [0, ...x] === [0,1,2] | [T0, ...T] === [T0, T1, T2] | [T0, ...T] === [T0, T1, T2] |
| f(x) === f([1, 2]) | 아니 | f<T>() === f<[T1, T2]>() |
| f(...x) === f(1, 2) | f<...T>() === f<[T, T2]> ? | f<...T>() === f<T1, T2> |
| f(0, ...x) === f(1, 2) | f<T0, ...T>() === f<T0, [T, T2]> ? | f<T0, ...T>() === f<T0, T1, T2> |

제안서에서

function g<...T>(...x: ...T) {
 // being called as g(1, "a");
  var a: ...T; // [number, string] ?
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string] - same as a ? so [...T] is same as ...T - weird
}

내 업데이트에서

function g<...T>(...x: T) {
 // being called as g(1, "a");
  var a: T; // [number, string]
  var b: [number, ...T]; // [number, number, string]
  var c: [...T]; // [number, string]
}

업데이트가 IMO에서 더 멋지게 보입니다. 유형을 나타내는 목록은 매우 훌륭하게 들리지만 유형이 지정된 Lisps도 그렇게 멀리 가지 않습니다(동음이코닉 유형, 누구? :smile:).

순수함이 주는 매력도 있지만 실용주의적인 면도 보고 있어요. 목록은 자체적으로도 비교적 쉽게 구현할 수 있지만 나머지 언어에는 적합하지 않습니다. Java(언어)로 모나드를 구현하거나 C에서 람다를 구현하려는 수많은 시도와 거의 비슷합니다.

@sandersn 후보자 목록을 공개하여 제가 의미하는 바를 설명하려고 할 수 있습니다. 유형 인수 유추는 각 유형 매개변수에 대한 후보 목록을 생성합니다. 그런 다음 어떤 후보가 다른 모든 후보의 상위 유형인지 확인하고 그렇다면 해당 후보가 승자입니다. 따라서 다음 예에서:

function foo<T>(a: T, b: T): T {}
foo(["hi", 0], ["", ""]);

인수가 입력된 다음 각 매개변수로 유추됩니다. (string | number)[]string[] 두 개의 후보가 생성됩니다. 그러나 첫 번째 것은 두 번째의 수퍼타입이기 때문에 이길 것입니다. 결과적으로 사용자는 string[] 가 사진에 있다는 것을 결코 관찰하지 못합니다. T 대한 하나의 추론이 있으며 다른 모든 후보는 보이지 않습니다. 이것은 사용자에게 보이지 않는 두 가지, 즉 후보자의 순서와 후보자의 다중성이 있음을 의미합니다.

...T 로 표시된 튜플의 요소 목록으로 후보 목록을 사용하는 경우 다음과 같은 다중성 문제가 있습니다.

function foo<...T>(...rest: ...T): ...T
foo(0, 1);

내가 이해하는 한 제안의 의도를 감안할 때 T에 대해 [number, number] 를 추론하고 싶을 것입니다. 그러나 https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L6256 라인의 포함 체크 때문에 number 후보는 한 번만 추가되고 T[number] 로 유추됩니다. 이것이 내가 말했던 다중성 문제입니다.

순서는 왼쪽에서 오른쪽입니다. 그러나 여러 패스가 있으며 컨텍스트에 따라 입력되는 함수 표현식이 포함된 인수는 다시 처리됩니다. 컨텍스트 형식의 함수 표현식을 포함하는 n개의 인수가 있는 경우 인수에 대한 n + 1개의 전달이 있습니다. 예를 들어 Array.prototype.reduce가 있습니다. 여기서 initialValue 매개변수는 오른쪽에 있음에도 불구하고 콜백 전에 효과적으로 유형이 지정되고 유추됩니다. 따라서 다음과 같은 것이 제안에 대한 문제일 수 있습니다.

function foo<...T>(...rest: ...T): ...T
foo(x => x, 0);

직관적으로 T 는 [(x: any) => any, number] 여야 하지만 후보가 추가되는 순서에 의존하면 [number, (x: any) => any] 됩니다. 일반적으로 형식 인수 유추는 왼쪽에서 오른쪽으로 진행되지만 컨텍스트 형식 지정의 대상이 되는 함수는 끝까지 미루기 때문입니다.

내가 설명한 다중성과 순서 문제는 모두 후보 목록을 표면화하는 경우입니다. @ahejlsberg 는 이것에 대해 물어볼 수 있는 좋은 사람이 될 것입니다. 실제로 그는 제가 말한 모든 것을 설명, 확인 또는 반증하는 데 도움을 줄 수 있습니다.

@JsonFreeman 왜 그것이 문제가 될 것이라고 생각합니까?
각 나머지 사실 인수에 대해 추가 제네릭 유형을 가상으로 도입하고 고정 매개변수 길이로 함수에 대해 추론하여 구현할 수 있습니다.
예를 들어,

function foo<...T>(...rest: T) { ... }
foo(x => x, 0);
// to infer, the following function is used
function foo2<T0, T1>(rest0: T0, rest1: T1) { ... }
foo2(x => x, 0);
// inferred as
foo2<(x: any) => any, number>
// T0 = (x: any) => any
// T1 = number
// T = [T0, T1] = [(x: any) => any, number]

BTW, x => x{ <T>(x: T): T; } 유형이라고 추론할 수 있습니까?

@Igorbek 제조 유형 매개변수에 대한 귀하의 제안(적어도 구현 방법에 관계없이 직관으로)이 올바른 방법이라고 생각합니다. T 대한 유형의 시퀀스를 유추할 수 있습니다. 여기서 시퀀스의 각 요소에는 인덱스와 후보 목록이 있습니다(이는 언급한 것을 구현하는 대체 방법입니다).

그러나 내 요점은 추론 후보 목록을 추론된 튜플로 용도를 변경했다면 이것이 자연스럽게 일어날 것이라고 생각하지 않는다는 것입니다. 올바른 일이 일어나도록 하려면 명시적인 역학이 필요합니다.

{ <T>(x: T): T; } 에 대한 요점은 foo 가 일부 기능인 x => foo(x) 와 같은 것을 입력하는 것으로 일반화되지 않습니다. foo 대한 오버로드 해결을 수행하려면 x 유형을 알아야 합니다.

유형 검사기 추론 규칙과의 싸움에서 작은 한 걸음.
구문에 대한 의견/제안이 있습니다. 일관성이 있지만 상호 배타적인 두 가지 옵션이 있다고 생각합니다.

1. 형식적인 나머지 유형 인수

이 양식을 선택하면:

type F<...Args> = (...args:...Args) => ...Args

그런 다음 우리는 그것을 사용해야합니다

var a:  F // a: () => []
var b:  F<number> // b: (arg: number) => [number]
var c:  F<number, string> // c: (arg1: number, arg2: string) => [number, string]
...

따라서 진정한 휴식 형식이 될 것입니다. 형식 매개변수 섹션의 마지막 위치에서만 사용해야 합니다.

2. 튜플 형식의 나머지 인수

(...args:[string, number]) => boolean    IS EQUIVALENT TO   (s: string, n: number) => boolean

이 경우 형식 매개변수 섹션에는 항상 고정된 수의 슬롯이 있습니다.

function f<T>(...args: T): T {
    return args;
}

두 조건 중 하나가 충족되면 T가 튜플 유형이어야 한다고 추론합니다.

  1. T는 (...args: T) => T와 같은 나머지 매개변수에 사용됩니다.
  2. T는 [...T] 또는 [숫자, ...T, 문자열]과 같은 스프레드 구성에 사용됩니다.

따라서 형식 매개변수 섹션에서 줄임표를 사용할 필요가 없습니다(유형 검사기 없이도 _구문적으로_ 유추할 수 있음).

이 경우에도 쓸 수 있습니다.

function f<T>(...args: [...T]): [...T] {
    return args;
}

그러나 그것은 중복입니다.

개인적으로 나중에 TypeScript에서 구현되는 것을 보고 싶습니다. @JsonFreeman , @sandersn?

@Artazor 나는 그것이 표현력으로 귀결

제네릭 형식 참조의 경우 나머지 형식 매개 변수를 구문적으로 사용하는 위치와 방법을 결정하는 문제일 뿐입니다. 이것은 형식 시퀀스(튜플, 서명, 일반 형식 참조)를 사용하는 모든 형식 생성자에 대해 결정해야 합니다.

일반 서명의 경우 형식 인수 유추 때문에 더 복잡합니다. 다음이 있다면 어떨까요?

function callback(s: string, n: number): void { }
declare function foo<...T>(cb: (...cbArgs: T) => void, ...args: T): [...T];

foo(callback, "hello", 0, 1);

foo는 무엇을 반환합니까? 제 요점은 사람들이 제네릭 형식과 제네릭 서명에 대해 제네릭 규칙이 동일할 것으로 기대하지만 제네릭 형식을 보다 표현적으로 만들면 형식 인수 유추에 이를 처리할 방법이 필요하다는 것입니다. 이것은 형식 인수 추론이 어려운 경우를 공식적으로 식별하고 이러한 경우에 사용자가 명시적 형식 인수를 전달하도록 요구하는 문제일 수 있습니다.

제 생각에는 옵션 1이 더 낫다고 생각합니다. 나는 개인적으로 튜플 유형을 나머지 매개변수로 사용하는 것을 보지 않습니다. rest 매개변수는 가변 길이를 가져야 하기 때문에 배열 유형 또는 나머지 유형 매개변수만 허용되어야 한다고 생각합니다. 나는 또한 유형 시스템에 이미 존재하는 것과 연관되지 않은 유형의 추상 시퀀스인 나머지 유형 매개변수의 개념을 좋아합니다.

튜플에 대한 내 철학은 길이가 알려진 배열 값의 하위 집합을 나타내는 것입니다. 이러한 배열 값은 실제 런타임 엔터티입니다. 나는 그것들을 일종의 유형 시스템 장치로 사용하여 유형의 추상 시퀀스(예: 서명의 매개변수 시퀀스)를 나타내는 아이디어를 좋아하지 않습니다. 그러나 튜플에서 나머지 유형 매개변수를 퍼뜨릴 수 있는지 여부는 다른 이야기입니다.

나는 튜플 제안이 더 강력하고 더 많은 사용 사례를 해결하기 때문에 좋아합니다. 튜플은 배열일 뿐이고 나머지 매개변수가 있는 함수를 호출할 때 배열을 분산할 수 있기 때문에 튜플을 나머지 매개변수로 확산할 수 있다는 것도 매우 직관적입니다. 그러면 유형 시스템이 코드에 대한 나의 이해와 더 잘 일치할 것입니다.

@JsonFreeman 의 경우 foo는 ...args 에서 유추된 [string, number, number] 를 반환하고 유추된 cb 유형은 (string, number, number) => void 이고 전달된 콜백은 마지막 인수를 무시합니다. TS와 JS 모두에서 매우 일반적입니다.

나는 그것들을 일종의 유형 시스템 장치를 사용하여 유형의 추상 시퀀스를 나타내는 아이디어를 좋아하지 않습니다.

그것이 정확히 무엇인지, JS는 튜플에 대해 알지 못하고 TS에 대해서만 모릅니다. TS의 경우 튜플은 유형의 시퀀스입니다.

튜플 기반 접근 방식도 좋아합니다. 특히 다음과 같은 호환 가능한 함수 서명을 가질 수 있다면:

// all are equivalent
(a: A, b: B, c: C) => R;
(a: A, b: B, ...rest: [C]) => R;
(a: A, ...rest: [B, C]) => R;
(...args: [A, B, C]) => R;

// this is more complicated 
(a: A, ...rest: T[]) => R;
(...args: [A, ...T]) => R; // no in current syntax

후자는 현재 구문으로 표현할 수 없지만 #6229를 채택했다면 표현할 수 있습니다.
그래서 나에게 적절한 방법은 튜플을 사용하고 튜플을 통합하여 더 많은 것을 표현하는 것 같습니다. 보다 표현적인 튜플이 없으면 튜플로서의 T가 열린 길이를 가지므로 [...T, ...T] 와 같은 것을 갖기가 어려울 것입니다.

귀하의 예를 들어 @JsonFreeman는 @Pajn은의 나의 이해 정확히 보이지 않았다 - 이러한 유형의 추론에 눈에 보이는 문제가 더있다.

@JsonFreeman 해당 구문을 사용하는 것이 좋습니다.

declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): T;
declare function foo<T>(cb: (...cbArgs: T) => void, ...args: T): [...T]; // same

흠, 아마도 약간의 모호성을 유발할 수 있습니다.

declare function foo<T>(...args: T): T;
foo(1); // T is [number] or number[]?

// however, here it'd be more explicit
declare function foo<T>(...args: T[]): T[];
foo(1); // T is number[]

// and here
declare function foo<T>(...args: [...T]): T;
foo(1); // T is [number]

튜플에서 나머지 유형 매개변수를 퍼뜨리는 아이디어를 얻을 수 있습니다. 하지만 나머지 유형 매개변수가 암시적으로 튜플로 해석되기를 원하는지 잘 모르겠습니다. @Pajn 의 예제는 나머지 유형 매개변수가 모든 유형 시퀀스 위치(튜플, 매개변수 목록, 유형 인수)에 퍼질 수 있는 경우에도 여전히 작동합니다.

@Igorbek 첫 번째 예의 모호성에 대해 맞습니다. 세 번째 예도 문제가 있습니다. number, string 와 같은 시퀀스가 ​​주어지면 서명의 두 가지 가능한 인스턴스화가 있습니다. 즉, (arg1: number, arg2: string) => [number, string](arg1: [number, string]) => [number, string] (예제를 위해 암시적 튜플 해석 채택)입니다.

암시적 튜플 해석에 대한 또 다른 이상한 점은 다음과 같습니다. number, string 인스턴스화되는 나머지 유형 매개변수 T가 있다고 가정합니다. 이제 Foo<T> 형식 인수로 전달한다고 가정합니다. Foo<...T>Foo<number, string> 반면 Foo<[number, string]> 로 해석되어야 합니까? 확산 연산자를 유형 시스템으로 확장하기 때문에 이에 대한 주장이 있습니다. 그러나 나는 여전히 튜플 버전을 Foo<[...T]> 로 표현하고 싶습니다.

저를 미쳤다고 부르세요. 하지만 저는
튜플. 너무 많은 튜플 유형을 퍼뜨리려고 하면 어떻게 됩니까?
매개변수? 이와 같이?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

또한 유형 매개변수가 잘못된 유형이거나
비정상적이고 잠재적으로 잘못된 장소?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2)

첫 번째 예는 Function#apply와 직접 관련이 있으며
오류), 두 번째는 컴파일에 실패하는 명확하지 않은 실수입니다.
Intellisense로 감지하는 것은 중요하지 않습니다.

2016년 2월 28일 일요일 03:04 Jason Freeman [email protected] 다음과 같이 썼습니다.

암시적 튜플 해석에 대한 또 다른 이상한 점은 다음과 같습니다.
나머지 유형 매개변수 T가 숫자, 문자열로 인스턴스화되고 있습니다.
이제 그것들을 유형 인수로 전달한다고 가정해 보겠습니다. Foo. 그게 되는거야?
Foo<[숫자, 문자열]>로 해석되는 반면 Foo<...T>는 Foo입니다. 문자열>?


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

세 번째 예도 문제가 있습니다. number, string 와 같은 시퀀스가 ​​주어지면 서명의 두 가지 가능한 인스턴스화가 있습니다. 즉, (arg1: number, arg2: string) => [number, string](arg1: [number, string]) => [number, string] (예제를 위해 암시적 튜플 해석 채택)입니다.

세 번째 예에서는 (...args: [number, string]) => [number, string] 로만 해석할 수 있습니다.

declare function foo<T>(...args: [...T]): T;
foo(1, "a"); // T is [number, string]
const result: [number, string] = foo<[number, string]>(1, "a");

// however, it is assignable to/from the following signatures:
const f1: (arg1: number, arg2: string) => [number, string] = foo<[number, string]>;
const f2: (arg1: number, ...rest: [string]) => [number, string] = foo<[number, string]>;

암시 튜플 해석에 대한 또 다른 이상한 점은 이것이다 : 당신이 매개 변수 휴식 유형이 있다고 T 에 인스턴스화 number, string .

T 는 실제 튜플이므로 number, string 로 인스턴스화할 수 없습니다. [number, string] 이어야 합니다.

이제 Foo<T> 유형 인수로 전달한다고 가정합니다. Foo<...T>Foo<number, string> 반면 Foo<[number, string]> 로 해석됩니까?

진실. 그러나 <...T> 를 갖는 것은 우리가 논의하고 있는 이 특정 사용 사례에 대해 중복되는 것처럼 보입니다(나머지 인수에 대해 위치 지정 유형을 포착). 그럼에도 불구하고 우리가 그것을 가지고 있다고 가정 해 봅시다.

확산 연산자를 유형 시스템으로 확장하기 때문에 이에 대한 주장이 있습니다. 그러나 나는 여전히 튜플 버전을 Foo<[...T]> 로 표현하고 싶습니다.

해당 구문을 사용할 수 있는 두 가지 경우가 있습니다.

// in a signature declaration
declare function foo<[...T]>(...args: [...T]): [...T];
// and when type instantiated, so in the usage
type T = [number, string]
foo<T>();
foo<[...T]>();
// the latter can virtually be replaced as
type _T = [...T]; // which is a type operation that should produce [number, string]
foo<_T>();
// and more
type Extended = [boolean, ...T]; // [boolean, number, string]

따라서 사용법은 | , & 또는 [] 와 같은 유형 연산자에 지나지 않습니다. 그러나 구문이 T extends any[] 또는 모든 튜플에 대한 기본 유형이 무엇이든 간에 해석될 수 있다는 선언에서 튜플 유형이어야 함을 나타냅니다.

@isiahmeadows

너무 많은 튜플 유형을 퍼뜨리려고 하면 어떻게 됩니까?
매개변수? 이와 같이?

declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2); // ok, foo<[number]> is of type (...args: [number]) => void
// [1, 2] is being passed in place of args
// is [1, 2] which is [number, number] assignable to [number]? yes, with current rules
// no error

또한 유형 매개변수가 잘못된 유형이거나
비정상적이고 잠재적으로 잘못된 장소?

// 1. unusual place
declare foo<T>(x: T, ...ys: [...T]): void
// 1. [...T] can be interpret as a type constraint "must be a tuple type"
// 2. if we call with type specified
foo<number>(1); // number doesn't meet constraint
foo<[number]>(1, 2); // argument of type 'number' is not assignable to parameter 'x' of type '[number]'
foo<[number]>([1], 2); // ok
// 3. if we call without type, it must be inferred
foo(1); // according to current rules, T would be inferred as '{}[]' - base type of all tuples
        // so, argument of type 'number' is not assignable to parameter 'x' of type '{}[]'
foo([1, 2], 2); // T is inferred as '[number, number]
                // rest arguments of type '[number]' are not assignable to rest parameters 'ys' of type '[number, string]'
foo([1], 2, 3); // T is '[number]',
                // x is of type '[number]',
                // ys is of type '[number]',
                // rest arguments are of type '[number, number]' which is assignable to '[number]',
                // no error

// 2. bad type
declare foo<T>(...xs: [...T]): void
foo<number>(2); // type 'number' doesn't meet constraint

나는 여전히 이러한 것들을 튜플로 나타내는 이점을 보지 못합니다. 또한 <...T> 가 아닌 <T> 로 선언해야 한다고 생각합니다. 전에 말했듯이 튜플 유형은 유형 시스템에서 임의의 길이 유형 시퀀스에 사용하기에 적절한 장치로 보지 않습니다. 나는 이것이 사람들이 원하는 표현력을 위해 필요하다는 것을 아직도 확신하지 못한다.

더 많은 표현이 가능하다는 점에는 동의하지만 유형 매개변수 위치에 '확산' 연산자를 사용하면 나머지 매개변수를 두 번 가질 수 없는 것과 마찬가지로 나머지 인수를 한 번만 잡을 수 있습니다. 따라서 <...T><A, B, C> 주어지면 <...T> T[A, B, C] 로 catch합니다. 그리고 [A, B, C], [] 또는 [A, B], [C] 또는 ... 등과 같이 모호하므로 <...T, ...U> 를 표현할 수 없습니다.

다음 동작으로 함수를 표현하고 싶다고 가정해 보겠습니다.

declare function foo(a: A, b: B): R;
declare function boo(c: C, d: D, e: E): U;

let combined: (a: A, b: B, c: C, d: D, e: E) => [R, U] = combine(foo, boo);

// so the signature could be:

declare function combine<R, U, ???>(
  f1: (...args: [...T1]) => R,
  f2: (...args: [...T2]) => U):
    (...args: [...T1, ...T2]) => [R, U];

// if ??? is '...T1, ...T2'
combine<R, U, A, B, C, D, E> // what will be T1 and T2 ?
combine<R, U, ...[A, B, C], ...[D, E]> // ok ? so we will preserve spread to specific positions. so then
combine<...[R, U], A, ...[B, C, D], E> // will be restricted.
// however, ES6 allows to do it with function arguments
f(1, 2, 3);
f(...[1, 2], 3);
f(...[1], ...[2, 3]);

// if ??? is 'T1 extends TupleBase, T2 extends TupleBase'
// or just '[...T1], [...T2]' as a shortcut for such constraints
combine<R, U, [A, B, C], [D, E]> // pretty explicit, and doesn't occupy spread operator for type arguments

좋아, 이제 어떻게 생각하는지 알겠어. 당신이 제안하는 것은 실제로 내가 생각한 것과 다른 기능인 것 같습니다. 유형 매개변수의 시퀀스를 캡처하기 위한 새로운 구성을 추가하는 대신, 튜플 유형이 이미 유형의 시퀀스를 나타내기 때문에 퍼질 수 있기를 원합니다. 그런 식으로 다양한 길이의 여러 튜플을 보다 투명한 방식으로 전달할 수 있습니다.

자바 스크립트에서는 더처럼 function foo([...rest]) { } 대신 function foo(...rest) { } .

설명해주셔서 감사합니다. 합리적인 접근이라고 생각합니다.

@JsonFreeman 맞습니다!

@JsonFreeman 질문: [1, 2] [number] 만족시켜야 하는 이유는 무엇입니까? 그것은 나에게 매우 이상하게 보인다. 실제로 작동하는 것은 매우 놀라운 일입니다. 그것은 전혀 안전하지 않습니다.

그러나 가변 유형에 사용되는 튜플에 대해 반대하는 것은 아닙니다(저는 중립적이며 정직합니다).

어떤 방법으로 @isiahmeadows은 [1, 2] 에 대한 substituable하지 [number] ? 그것은 확실히 하위 유형입니다. { x: 1, y: 2 } 가 유효한 { x: number } 것과 같습니다.

괜찮아. 부분적으로 인정하겠지만, 튜플 인수를 허용하는 Function.prototype.apply를 고려하십시오.

interface Function<T, U, V> {
    (this: T...args: [...U]): V;
    apply(object: T, args: U): V;
}

호출자가 너무 많은 인수에 대해 TypeError를 발생시키면 너무 많이 전달하면 컴파일 오류가 아닌 런타임 오류가 발생합니다.

너무 많은 인수를 전달할 때 JS 함수가 TypeError를 발생시키는 것은 매우 드물지 않습니까? 몇 가지 예가 있습니까?

@isiahmeadows 를 추상적인 예로 들자면, 나는 당신이 걱정하는 오류가 다음과

function f(x: number): void {
  // throw if too many arguments
}
f.apply(undefined, [1,2,3]); // runtime error, no compile-time error
f(1,2,3) // compile-time error and runtime error.

그 맞습니까?

@sandersn , 너무 많은 인수에 대한 TypeError는 JS의 정신을 위반하는 것이라고 생각합니다. 일반적으로 이 함수에 전달될 실제 인수보다 덜 형식적인 인수로 함수를 전달하기 때문입니다. 우리는 단순히 그것들을 사용하지 않습니다. 예: Array.prototype.forEach

함수 카레는 어떻습니까? 그것은 아마도 Ramda와 함께 훨씬 더 일반적입니다.
및 lodash/fp.

2016년 2월 29일 월요일 13:45 Anatoly Ressin [email protected]에서 다음과 같이 썼습니다.

@sandersn https://github.com/sandersn , TypeError도 켜져 있다고 생각합니다.
많은 주장은 JS의 정신을 위반하는 것입니다.
일반적으로 실제 인수보다 덜 형식적인 인수로 함수를 전달합니다.
이 함수에 전달됩니다. 우리는 단순히 그것들을 사용하지 않습니다. 예를 들어
Array.prototype.forEach


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190327066
.

@isiahmeadows arguments.length 기반의 커링은 매우 불안정하고 런타임 오류가 발생하기 쉽습니다. 실제 커링은 추가 인수 증거입니다.

var plus = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // still 7

고정 서명이 있는 함수를 어딘가에 콜백으로 전달할 때 다음과 같은 방식으로 생각합니다. '내 함수는 _적어도_ 해당 인수를 기대합니다'

foldl 같은 것은 어떻습니까?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

이는 함수형 프로그래밍에서 매우 일반적입니다. 그리고 마지막 생략
주장은 꽤 일반적입니다.

2016년 2월 29일 월요일 13:52 Anatoly Ressin [email protected]에서 다음과 같이 썼습니다.

@isiahmeadows https://github.com/isiahmeadows 나는 카레라고 말하고 싶다
aruments.length 기반은 매우 불안정하고 런타임 오류가 발생하기 쉽습니다.
실제 커링은 추가 인수 증거입니다.

var 더하기 = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // 여전히 7


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

이를 콜백으로 전달하려면 map (목록 작업
목록), 아마도 카레를 원할 것입니다.

2016년 2월 29일 월요일, 13:59 Isiah Meadows [email protected] 작성했습니다.

foldl 같은 것은 어떻습니까?

const list = [1, 2, 3]
console.log(foldl((a, b) => a + b, 0, list))
console.log(foldl((a, b) => a + b, 0)(list))
console.log(foldl((a, b) => a + b)(0, list))
console.log(foldl((a, b) => a + b)(0)(list))

이는 함수형 프로그래밍에서 매우 일반적입니다. 그리고 마지막 생략
주장은 꽤 일반적입니다.

2016년 2월 29일 월요일, 13:52 Anatoly Ressin [email protected]
썼다:

@isiahmeadows https://github.com/isiahmeadows 나는 카레라고 말하고 싶다
aruments.length 기반은 매우 불안정하고 런타임 오류가 발생하기 쉽습니다.
실제 커링은 추가 인수 증거입니다.

var 더하기 = x => y => x + y
console.log(plus(3)(4)) // 7
console.log(plus(3,10)(4,20)) // 여전히 7


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190330620
.

나는 그것이 대부분 그것에 관한 것이라고 생각합니다.

type T = [number, string];
var a: T = [1, "a", 2]; // valid

// in this cases tuple types or parameter types cannot be inferred:
f(...a, true); // you could think number,string,boolean were passed, but weren't
const c = [...a, true]; // you could think that is of type [number, string, boolean] but it's not
// according to current rules, the best inferred types might be [number, string, number|string|boolean]

// same manner with variadic kinds, types are constructed properly:
type R = [...T, boolean]; // [number, string, boolean]

그래서 #6229를 제안했습니다.

[1, 2][number] 만족하는지에 대한 질문은 묻고 토론할 수 있는 유효한 질문입니다. 그러나 확산 가능한 튜플 기능과 무슨 관련이 있습니까?

튜플의 가변 응용 프로그램이 추가 인수를 무시해야 하는지 여부입니다.
아니면. 이 오버로드된 함수는 내 관심사를 더 자세히 설명해야 합니다.

declare function foo(x: number, ...args: string[]): void
declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

2016년 2월 29일 월요일 18:47 Jason Freeman [email protected] 다음과 같이 썼습니다.

[1, 2]가 [숫자]를 만족하는지 여부에 대한 질문은 유효한 질문입니다.
그리고 토론. 그러나 확산 가능한 튜플 기능과 무슨 관련이 있습니까?


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

그리고 이것이 실용적인 이유로 내가 rest 매개변수와 같은 것을 선호하는 이유입니다.
가변형.

2016년 2월 29일 월요일 19:00 Isiah Meadows [email protected] 다음과 같이 썼습니다.

튜플의 가변 응용 프로그램이 추가 항목을 무시해야 하는지 여부입니다.
주장이든 아니든. 이 오버로드된 함수는 내
우려.

declare function foo(x: number, ...args: string[]): void


declare function foo<T>(...args: [...T]): void
foo<[number]>(1, 2)

// This will always fail
declare function foo(x: number, ...args: string[]): void
declare function foo<T>(x: T): void
foo<number>(1, 2)

2016년 2월 29일 월요일, 18:47 Jason Freeman [email protected]
썼다:

[1, 2]가 [숫자]를 만족하는지 여부에 대한 질문은 유효한 질문입니다.
그리고 토론. 그러나 확산 가능한 튜플 기능과 무슨 관련이 있습니까?


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman 유형 및 배열/튜플에 대한 스프레드 연산자 때문입니다. 스프레드 유형 연산자가 "주어진 유형 A , BT = [A] 되면 [...T, B][A, B] "를 구성합니다. (암시적으로 제안됨) 배열/튜플 확산 연산자와 정렬되지 않습니다. 을 감안할 때 var a: [A]var b: B 표현의 종류, [...a, b] 종류 여야으로 입증 할 수없는 [A, B] . 튜플의 현재 규칙에 따르면 [A, A|B] 유형으로 입증될 수 있습니다.
당신에게 의미가 있습니까? 또는 그 불일치를 강조하기 위해 비교 테이블을 생성할 수 있습니다.

@Igorbek 무슨 말인지 이해합니다. 그것은 궁극적으로 컴파일러가 처리하는 유형에 대한 완벽한 지식을 가지고 있지만 값에 대한 완벽한 지식이 없다는 사실에서 비롯됩니다. 특히 귀하의 예에서 a 은 길이를 알 수 없는 반면 [A] 유형은 길이를 알 수 있습니다. 이것이 내가 처음에 이 목적으로 튜플 유형을 사용하는 것이 불편했던 이유 중 하나입니다. 하지만 심각한 문제인지는 잘 모르겠습니다.

@isiahmeadows 질문하신 내용은

유형 안전 솔루션은 나머지 솔루션과 더 일관성이 있습니다.
인수 구문을 모방한 경우 언어입니다.

제 요점은 휴식 매개변수를 효과적으로 퍼뜨리는 것입니다.
정확히 인수 유형이며 더 이상은 아닙니다. 커리 함수에는 반환값이 있습니다.
유형은 인수 유형에 따라 다릅니다. 따라서 너무 많은 인수를 적용하면
커리 함수를 부분적으로 적용하면 완전히 다른
유형. 튜플과 같은 나머지 유형을 처리하면 대신 런타임 오류가 발생합니다.
결코 좋지 않은 것.

2016년 3월 1일 화요일 06:07 Jason Freeman [email protected] 다음과 같이 썼습니다.

@Igorbek https://github.com/Igorbek 무슨 말인지 이해합니다.
그것은 궁극적으로 컴파일러가 완벽한 지식을 가지고 있다는 사실에서 비롯됩니다.
그것이 다루고 있는 유형의, 그러나 가치에 대한 완전한 지식은 아닙니다. 에
특히 귀하의 예에서 값의 길이는 알 수 없지만
유형 [A]의 길이가 알려져 있습니다. 이것은 내가 처음에 있었던 이유 중 하나였습니다.
이러한 목적으로 튜플 유형을 사용하는 것이 불편합니다. 하지만 확실하지 않다
그것은 심각한 문제입니다.

@isiahmeadows https://github.com/isiahmeadows 질문하신 내용을 알겠습니다
하지만 나머지 유형 매개변수에서 문제가 더 명확해진 이유는 무엇입니까?


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190667281
.

@isiahmeadows 커링 문제에 대한 예제 코드를 제공할 수 있습니까?

나는 여전히 여러분이 나머지 유형 매개변수를 사용하더라도(제가 모두 찬성합니다) 과도한 인수를 허용하지 않도록 명시적으로 결정해야 한다고 생각하지만, @isiahmeadows에 동의해야 할 것입니다.

@sandersn @JsonFreeman

type FullCurry<T> = ((initial: T, xs: T[]) => T) | ((initial: T) => (xs: T[]) => T)
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T, xs: T[]): T
declare function foldl<T>(func: (acc: T, item: T) => T): FullCurry<T>
declare function foldl<T>(func: (acc: T, item: T) => T, initial: T): (xs: T[]) => T

interface Function<T, R, ...A> {
    apply<U extends T>(inst: U, args: [...A]): R
    apply(inst: T, args: [...A]): R
}

function apply(reducer: (initial: number) => number): (number[]) => number {
    reducer.apply(undefined, [0, []])
}

const func = apply(foldl<number>((x, y) => x + y))

func([1, 2, 3]) // Runtime error

내 변형도 추가하겠습니다. 제안에서 variadic 카레의 예를 살펴보겠습니다.

function curry<...T,...U,V>(f: (...ts: [...T, ...U]) => V, ...as:...T): (...bs:...U) => V {
    return ...b => f(...as, ...b);
}

그래서 다음과 같이 사용하기 시작했습니다.

function f(a: number, b: string, c: string) { return c.toUpperCase(); }
var a: [number, string] = [1, "boo", 2]; // valid
const cf = curry(f, ...a); // cf is of type string => string
cf("a"); // runtime error

@isiahmeadows 나머지 유형 매개변수로 나타내든 튜플 유형으로 나타내든 간에, 튜플 위치에 이를 확산하는 기능에 반대하는 것처럼 들립니다.

@Igorbek 문제는 가변 유형 시퀀스가 ​​표현되는 방식이 아니라는 점에서 귀하의 예가 유사하다고 생각합니다. 문제를 일으키는 것은 튜플에 그것들을 퍼뜨리는 능력입니다.

@JsonFreeman 이 행동에 반대하는 것이 더

class A {}
class B {}
class C {}

declare function foo(a: A, b: B): C;

// This should not work
let value: [A, B, C]
foo(...value)

그게 명확해 졌나요?

@isiahmeadows 실제로 작동해야 합니다.

@JsonFreeman
나는 그것이 안된다고 느낀다. 그것이 저의 가장 큰 반대입니다. 있으면 위험할 것 같아요.

질문: ret 의 유추된 반환 유형은 무엇이어야 합니까?

declare function foo(a: A, b: B, c: C, d: D): D
let ret = foo.bind(...[new A(), new B(), new D()])

이것은 실제로 꽤 중요합니다.

마지막 예는 확실히 작동하지 않는 것처럼 보입니다. function.bind가 실제로 제대로 작동하려면 기본적으로 유형 시퀀스를 정렬하는 메커니즘이 필요합니다. 바인딩할 인수의 유형이 원래 함수의 인수와 일치하고 나머지가 반환 유형에 있는 통합과 유사한 것이 필요합니다.

즉, 제안되거나 논의된 내용에서 처리할 수 있는 것 같지 않습니다(튜플의 추가 인수가 허용되는지 여부에 관계없이). 내가 놓친 것이 있을 수 있습니다.

가장 큰 문제는 각 매개변수의 유형이 스프레드 유형과 일치하는 일종의 튜플 패턴 일치가 해당 문제를 해결하기 위해 유형 매개변수(LiveScript/CoffeeScript의 인수)로 수행되어야 한다는 것입니다. 그렇지 않으면 아마도 불가능합니다. 그리고 그것이 얼마나 복잡한지에 관해서는, 그것을 구현하는 행운을 빕니다. :웃다:

@JsonFreeman

또는 더 정확하게 말하면 작동하려면 엄격하지 않은(열심한 대 게으른 의미에서) 유형 검사가 필요합니다. 나는 또한 그것이 자기 재귀 유형과 같은 다른 많은 유용한 것들에 대한 문을 거의 열어주기 때문에 어쨌든 단순한 가변 유형보다 아마도 더 유용한 확장이라고 생각합니다.

// I hate this idiom.
interface NestedArray<T> extends Array<Nested<T>> {}
type Nested<T> = T | NestedArray<T>

// I would much prefer this, but it requires non-strict type checking.
type Nested<T> = T | Nested<T>[]

고맙게도, 엄격하지 않은 유형 검사는 이전에 검사에 실패한 코드만 이제 작동한다는 점에서 순전히 비 주요 변경이어야 합니다.

이것은 아마도 Function.prototype.bind 의 적절한 타이핑을 막는 가장 큰 문제일 것입니다. 매우 복잡한 유형 서명이 필요하다는 사실을 제외하고는 말입니다.

흥미로운 연결입니다. 나는 그들이 관련이 있다고 확신하지 않습니다. 재귀 형식 문제는 제네릭에 대한 캐싱 정책과 컴파일러의 형식 별칭 표현의 결과입니다. 모든 정보가 있습니다. 방해가 되는 것은 컴파일러의 설계일 뿐입니다.

튜플 패턴 일치의 경우 튜플에 대해 얼마나 많은 인수가 일치하는지 항상 알 수는 없습니다. bind 의 인수에 배열을 퍼뜨리면 결과 콜백에 몇 개나 남아 있는지 알 수 없습니다.

@JsonFreeman 즉, 채택 단계로 연산자 제안 #6229를 먼저 고려해야 한다고 생각하십니까?

@JsonFreeman

그리고 유형에 대한 엄격하지 않은 검사는 충분한 게으름을 허용하여 Function.prototype.bind 하여 해당 문제를 더 쉽게 수정할 수 있습니다. 이러한 게으름으로 다음과 같이 해당 유형을 수행할 수 있습니다(유형 선언에서 여러 나머지 매개변수가 허용되지 않는 한 시퀀스를 순서대로 지정하려면 튜플 구문이 필요함).

interface Function {
    bind<R, T, ...X, ...Y>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

유추하기 위해 엄격하지 않은 유형 검사가 필요한 이유는 무엇입니까? 함수에 대해 확인하려면 나머지 유형을 단계별로 추론해야 합니다. 다음을 감안할 때 확인 방법은 다음과 같습니다.

// Values
declare function func(a: number, b: string, c: boolean, d?: symbol): number

let f = func.bind(null, 1, "foo")

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, string, ...*X]
): (this: any, ...rest: [...Y]) => number

// First rest parameter ends: all ones that only uses it are fully spread
bind<number, any, number, string, ...Y>(
    this: (this: any, ...args: [number, string, ...Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [...Y]) => number

// Infer first part of next rest parameter
bind<number, any, number, string, boolean, ...*Y>(
    this: (this: any, ...args: [number, string, boolean, ...*Y]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, ...*Y]) => number

// Infer second part of next rest parameter
// Note that information about optional parameters are retained.
bind<number, any, number, string, boolean, symbol?, ...*Y>(
    this: (
        this: any,
        ...args: [number, string, boolean, symbol?, ...*Y]
    ) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?, ...*Y]) => number

// Second rest parameter ends: all ones that only uses it are exhausted
bind<number, any, number, string, boolean, symbol?>(
    this: (this: any, ...args: [number, string, boolean, symbol?]) => number,
    thisObject: any,
    ...args: [number, string]
): (this: any, ...rest: [boolean, symbol?]) => number

// All rest parameters that are tuples get converted to multiple regular
parameters
bind<number, any, number, string, boolean, symbol?>(
    this: (
        this: any,
        x0: number,
        x1: string,
        x2: boolean,
        x3?: symbol
    ) => number,
    thisObject: any,
    x0: number,
    x1: string
): (this: any, x0: boolean, x1?: symbol) => number

// And this checks

이것이 비엄격 유형 검사가 작동하는 방식입니다. 유형을 보는 순간이 아니라 필요에 따라 유형을 유추합니다. 잘못된 유형이 실패하도록 두 패스를 결합할 수 있습니다. 예시:

let f = func.bind(null, 1, Symbol("oops"))

// How to infer
bind<R, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => R,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => R

// Infer first type parameter
bind<number, T, ...X, ...Y>(
    this: (this: T, ...args: [...X, ...Y]) => number,
    thisObject: T,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer second type parameter
bind<number, any, ...X, ...Y>(
    this: (this: any, ...args: [...X, ...Y]) => number,
    thisObject: any,
    ...args: [...X]
): (this: any, ...rest: [...Y]) => number

// Infer first part of rest parameter
bind<number, any, number, ...*X, ...Y>(
    this: (this: any, ...args: [number, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, ...*X]
): (this: any, ...rest: [...Y]) => number

// Infer second part of rest parameter
bind<number, any, number, string, ...*X, ...Y>(
    this: (this: any, ...args: [number, string, ...*X, ...Y]) => number,
    thisObject: any,
    ...args: [number, symbol /* expected string */, ...*X] // fail!
): (this: any, ...rest: [...Y]) => number

이 경우 예상 매개변수는 깊이 우선 반복을 수행하여 해당 라운드에서 유추된 첫 번째 매개변수여야 합니다. 이 경우 해당 검색에서 유추된 첫 번째 것은 문자열이었고 기호는 문자열에 할당할 수 없으므로 실패합니다.


그리고 이것 때문에 Function.prototype.apply 를 입력하려고 하면 나머지 유형을 적용하기 위해 튜플을 사용하는 것에 대한 제 의견이 변경되었습니다.

interface Function {
    apply<T, R, ...X>(
        this: (this: T, ...args: [...X]) => R,
        thisArg: T,
        args: [...X]
    ): R
}

기타 몇 가지 참고 사항:

  1. 배열과 튜플을 나머지 유형 매개변수로 퍼뜨리는 방법이 필요합니다.

ts interface Foo extends Function<void, ...string[]> {}

  1. 생성자와 콜러블에 대해 두 가지 별도의 유형이 필요하며, Functions는 이 둘을 합친 것입니다. 호출 가능한 객체는 호출 가능한 인터페이스를 구현해야 하고, 클래스 생성자는 구성 가능한 인터페이스를 구현해야 하며, ES5 함수는 둘의 통합을 구현해야 합니다.
  2. Function.prototype.bind 및 친구들은 함수에 대한 모든 오버로드를 확인해야 합니다. 해당 작업이 여러 개 있는 경우 모든 작업의 ​​합집합을 반환해야 합니다.

귀하의 예에서 이러한 유형 매개변수는 실제로 바인드 서명의 유형 매개변수가 아닙니다. 그것들은 Function 유형에 속합니다. 그러나 예, 아이디어는 두 개의 나머지 매개변수를 사용하거나 튜플에 두 개의 나머지 유형 매개변수를 퍼뜨릴 수 있다면 이것을 작성할 수 있다는 것입니다.

bind의 서명이 충분히 유연하기 위해서는 ...X...Y 사이의 경계가 호출별로 결정되어야 합니다. 유추해야 할 것입니다. 서명이 ...X 를 분리해서 사용한다면 문제가 될 것입니다. 이 경우 경계가 결정되지 않습니다. 예를 들어:

interface SomeType<T, R, ...X, ...Y> {
     someMethod(someArgs): [...X]; // No way of knowing how long X is 
}

그리고 오버로드는 Function 유형의 경우 상당히 문제가 됩니다. 오버로드 간에 매개변수를 혼합하고 일치시킬 수 있기 때문에 각 인수에 대해 요소별로 공용체 유형을 사용하고 싶지 않다고 생각합니다. 그게 당신이 의미하는 무엇입니까?

@JsonFreeman

_TL;DR: 가로 줄 바꿈으로 건너뜁니다. 새롭고 더 실용적인 아이디어가 있습니다._

  1. 예, 나는 그들이 실제로 Function 자체에 속한다는 것을 알고 있습니다.
  2. 그런 종류의 문제가 내가 (Haskell의 의미에서) 엄격하지 않은 유형 일치가 필요하다고 말한 이유입니다. 반복적이고 게으른 검색이 필요하기 때문에 형식을 정상적으로 해결할 수 없습니다. 알고리즘적으로 결정할 수는 있지만 C++와 같이 일반적으로 추적할 필요가 없는 항목을 추적해야 합니다.
  3. 두 인수가 분리되어 있으면(귀하의 예에서와 같이) 컴파일러가 불평해야 합니다. 그리고 그 상황은 인터페이스/무엇이든 간에 각 가변 인수의 유형 수준 종속성 분석으로 감지할 수 있습니다. 또한 사소하지 않지만 형식 선언 자체를 읽을 때(실제로는 직후) 확인할 수 있습니다.

나는 또한 문제의 방법에 대해서만 이러한 종류의 상황을 정의하는 것이 조금 더 실현 가능하다고 생각하지만. 또한 귀하가 언급한 이러한 종류의 잠재적인 문제를 훨씬 더 쉽고 빠르게 감지할 수 있습니다.

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

해결하기 훨씬 더 어려운 잠재적인 다른 문제가 있습니다(그리고 _n _ 나눗셈이 아니라 2로 제한되어야 한다고 생각하는 이유).

declare function foo<...T>[...T = ...A, ...B, ...C](
    a: [...A, ...C],
    b: [...A, ...B],
    c: [...B, ...C]
): any

// This should obviously check, but it's non-trivial to figure that out.
let x = foo<
    boolean, number, // ...A
    string, symbol,  // ...B
    Object, any[]  // ...C
>(
    [true, 1, {}, []],
    [true, 1, "hi", Symbol()],
    ["hi", Symbol(), {}, []]
)

_여기서 CS 이론에 너무 깊이 들어가는 것이라면 죄송합니다..._

네, 맞는 생각이라고 생각합니다. 예쁘지는 않지만 Function 의 유형 인수를 알고 bind 를 올바르게 입력하는 다른 방법은 생각나지 않습니다. 궁극적인 것은 경계가 유추되어야 한다는 것입니다. 그리고 나는 그것이 조합적으로 폭발할 수 있는 임의의 수의 경계 대신에 1개의 경계를 유추해야 하므로 2개의 버킷으로 제한되어야 한다는 데 동의합니다.

우리가 생각하지 못한 더 많은 문제가 있을 수 있습니다.

@JsonFreeman 또 다른 문제는 curry 입니다. 나는 아직 그것을 제대로 입력할 수 있는 것을 생각해내지 못했습니다. 그리고 내가 할 수 있기까지 시간이 걸릴 것입니다. 그런 프로세스를 생각해내려면 진지한 Haskell과 같은 유형의 해킹을 해야 할 것입니다.

일부 Bluebird 기능에서 제안을 생각하는 방법에 대해 생각합니다.

interface PromiseConstructor {
    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[]>;
    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T]>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T]>;
    // this is sketchy...    ^
}

interface Promise<T> {
    // all same type
    then<U>(onFulfill: (values: T) => U): Promise<U>;
    spread<U>(onFulfill: (...values: T) => U): Promise<U>;
}
interface Promise<...T> {
    // varying types
    then<U>(onFulfill: (values: [...T]) => U): Promise<U>;
    spread<U>(onFulfill: (...values: [...T]) => U): Promise<U>;
}

위의 all<...T>(promises: [...PromiseLike<T>]): Promise<...T>; 대한 솔루션이 있습니까?

@DerFlatulator

PromiseConstructor에서 내 큰 주석을 참조하십시오. 또한 Promise 인터페이스를 내 제안에 조금 더 가깝게 수정했습니다.

interface PromiseConstructor {
    new <T>(callback: (
        resolve:
        (thenableOrResult?: T | PromiseLike<T>) => void,
        reject: (error: any) => void
    ) => void): Promise<T, [T]>;
    new <...T>(callback: (
        resolve:
        (thenableOrResult?: [...T] | PromiseLike<[...T]>) => void,
        reject: (error: any) => void
    ) => void): Promise<[...T], ...T>;

    // all same type
    all<T>(promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;
    join<T>(...promises: PromiseLike<T>[]):  Promise<T[], ...T[]>;

    // varying types
    all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T>;
    join<...T>(...promises: [...PromiseLike<T>]): Promise<[...T], ...T>;

    // all<...T>(promises: [...PromiseLike<T>]): Promise<[...T], ...T> should
    // expand to this:
    //
    // all<T1, T2, /* ... */>(promises: [
    //     PromiseLike<T1>,
    //     PromiseLike<T2>,
    //     /* ... */
    // ]): Promise<[T1, T2, /* ... */], T1, T2, /* ... */>;
    //
    // This should hold for all rest parameters, potentially expanding
    // exponentially like ...Promise<[Set<T>], ...Thenable<T>> which should
    // expand to something like this:
    //
    // Promise<[Set<T1>], Thenable<T1>, Thenable<T2> /* ... */>,
    // Promise<[Set<T2>], Thenable<T1>, Thenable<T2> /* ... */>,
    // // etc...
}

interface Promise<T, ...U> {
    // all same type
    then<V>(onFulfill: (values: T) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: T) => V): Promise<[V], V>;

    // all same type, returns tuple
    then<...V>(onFulfill: (values: T) => [...V]): Promise<[...V], ...V>;
    spread<...V>(onFulfill: (...values: T) => [...V]): Promise<[...V], ...V>;

    // varying types
    then<V>(onFulfill: (values: [...U]) => V): Promise<[V], V>;
    spread<V>(onFulfill: (...values: [...U]) => V): Promise<[V], V>;

    // varying types, returns tuple
    then<...V>(onFulfill: (values: [...U]) => [...V]): Promise<[V], ...V>;
    spread<...V>(onFulfill: (...values: [...U]) => [...V]): Promise<[V], ...V>;
}

[...Foo<T>][Foo<T1>, Foo<T2>, /*... Foo<TN>*/] [...Foo<T>] 확장되면 [...Foo<T,U>] 는 구문 오류입니까 아니면 조합 확장입니까?

@DerFlatulator

  1. T 또는 U 중 정확히 하나가 나머지 매개변수이면 정상적으로 확장됩니다. T 가 나머지 매개변수라고 가정하면 [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] 됩니다.
  2. 둘 다 나머지 매개변수이고 길이를 올바르게 추론할 수 있는 경우 조합 확장이어야 합니다(음... T의 길이 곱하기 U의 길이).
  3. 둘 다 나머지 매개변수가 아니면 구문 오류입니다.

나는 실용적인 이유로 2개 이상의 나머지 매개변수를 강력히 반대하며 나머지 매개변수는 분할이 필요한 경우 메서드별로만 분할해야 합니다. 이 같은:

interface Function<R, T, ...A> {
    // Split it up for just this method, since it's being resolved relative to the
    // method itself.
    bind[...A = ...X, ...Y](
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

_(누군가 더 나은 구문을 생각해 낼 수 있다면 나는 귀를 기울입니다. 좋아하지는 않지만 시각적으로 충돌하지 않는 것은 생각할 수 없습니다.)_

@isiahmeadows

2.의 경우 확장은 어떤 순서로 이루어집니까?

[
Foo<T1, U1>, Foo<T2, U1>, /*... */ Foo<TN,U1>,
Foo<T1, U2>, Foo<T2, U2>, /*... */ Foo<TN,U2>,
/* ... */
Foo<T1, UN>, Foo<T2, UN>, /*... */ Foo<TN,UN>
]

또는 반대로:

[
Foo<T1, U1>, Foo<T1, U2>, /*... */ Foo<T1,UN>,
Foo<T2, U1>, Foo<T2, U2>, /*... */ Foo<T2,UN>,
/* ... */
Foo<TN, U1>, Foo<TN, U2>, /*... */ Foo<TN,UN>
]

이 모호함이 혼란을 야기하지 않을까요? 아마도 한 차원으로 제한하는 것이 현명할 것입니다.


분할 구문에 대한 대안 제안:

interface Function<R, T, ...A> {
    bind<[...X, ...Y] = [...A]>(
        this: (this: T, ...args: [...X, ...Y]) => R,
        thisObject: T,
        ...args: [...X]
    ): (this: any, ...rest: [...Y]) => R
}

@DerFlatulator

나는 두 번째를 기대합니다. 그리고 그것이 계속되는 한 사람들은 빨리 익숙해지기 때문에 그것이 너무 많은 혼란을 일으킬 것이라고 의심합니다. 또한 자신이 하는 일을 알고 있거나 처음부터 필요성에 의문을 제기해야 하는 사람들만이 실제로 실제로 마주하게 되는 특이한 경우입니다.

나는 또한 당신이 첫 번째, 두 번째를 첫 번째의 각 부분에 대해 확장할 때 그것을 보고 있습니다. 이 의사 코드처럼:

for (let TT of T) {
  for (let UU of U) {
    expand(TT, UU);
  }
}

위의 아이디어 중 일부를 반복하는 중...

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>
}

여기서 ...TBound 의 길이는 args 의 길이로 알려져 있기 때문에 [...TBound, ...TUnbound] = [...TArgs] 이 유효합니다. 또한 TThis 의 유형을 변경할 수 있습니다.

이 접근 방식의 한 가지 문제는 this 한 번만 바인딩할 수 있다는 것입니다. 예를 들면 다음과 같습니다.

interface IFoo { a: number }
interface IBar extends IFoo { b: boolean }
function f(a: number) { }

let x = f.bind(<IBar>{ a: 1, b: false }, 2); // inferred type: Function<number, IBar>
let y = x.bind(<IFoo>{ a: 1 }) // inferred type: Function<number, IFoo>

y 의 유추된 유형이 올바르지 않습니다. Function<number, IBar> 이어야 합니다. 이것이 문제인지 아닌지 확실하지 않지만 이를 해결하려면 <T> 구문에 논리를 도입해야 합니다.

옵션 1

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TNewThis = TThis is undefined ? TNewThis : TThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;
}

옵션 2

interface Function<TReturn, TThis, ...TArgs> {
    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is undefined,
        TNewThis
    >(
        thisObject: TNewThis,
        ...args: [...TBound]
    ): Function<TReturn, TNewThis, ...TUnbound>;

    bind<
        [...TBound, ...TUnbound] = [...TArgs],
        TThis is defined
    >(
        thisObject: any,
        ...args: [...TBound]
    ): Function<TReturn, TThis, ...TUnbound>;
}

그러나 이는 이 제안의 범위를 벗어날 가능성이 있습니다.

유형 확산 연산자를 사용하여 이러한 종류의 확장을 허용해서는 안 된다고 생각합니다. 나는 확산 연산자를 배열 확산 연산자 및 객체/속성 확산 연산자(2단계 제안)와 절대적으로 일치하는 "대괄호 제거기"로 생각합니다. 그냥 비교:

let a =        [1, 2];
let b = [0, ...a     , 3];
//      [0, ...[1, 2], 3]
//      [0,     1, 2 , 3]  // removed brackets

let c =               { a: 1, b: "b" };
let d = { e: true, ...c               , f: 3 };
//      { e: true, ...{ a: 1, b: "b" }, f: 3 };
//      { e: true,      a: 1, b: "b"  , f: 3 };

새로운 유형 세트를 구성하기 위해 확장할 것을 제안합니다.

<...T> = <A, B, C>
...U<T> = <U<A>, U<B>, U<C>>

완전히 다른 작업입니다. 원한다면 다음과 같은 고차 구조로 모델링할 수 있습니다.

<...(from R in T select U<R>)> // linq-like
<...(T[R] -> U<R>)> // ugly

@Igorbek 연산자를 사용하여 확장할 대상을 결정하는 것은 어떻습니까?

interface PromiseConstructor {
    all<
      ...T, 
      [...TThen] = ...(PromiseLike<@T> | @T)
    >(
      promises: [...TThen]
    ): Promise<[...T], ...T>;
}

여기서 ...Foo<<strong i="9">@T</strong>, U>[Foo<T1,U>, /*...*/, Foo<TN,U>] 확장됩니다.

...(PromiseLike<@T> | @T) 확장
[PromiseLike<T1>|T1, /*...*/, PromiseLike<TN>|TN]

일부 구문 대안:

  • ...Foo<&T,U>
  • (T) Foo<T,U>
  • (...T => Foo<T,U>)
  • for (T of ...T) Foo<T,U>

여기에 @Igorbeck에 동의합니다. 적어도 이 단계에서는 가변 유형 매개변수의 보다 기본적인 문제를 해결하려고 노력하고 있다는 점을 감안할 때 유형 시퀀스 매핑이 우선순위처럼 보이지 않습니다.

나는 그것을 금지하는 데 큰 문제가 없습니다 (적어도 처음에는). 그 행동은 매우 비영양적이며 두 명의 다른 사람들이 두 가지 매우 다른 것을 기대할 수도 있기 때문입니다. 타이프 라이터 첫번째 요구 (의 그 높은 순서 형 모델을 가지고 있기 때문에 내가 할, 적어도 지금 @Igorbek에 동의 map 의미에서 유형을 핑 (ping)). 그리고 고차 유형은 단순히 고정할 수 있는 것이 아닙니다.

그래서 확실히 :+1: 그것을 금지하기 위해 아마도 꽤 오랫동안. 가지고 있는 것이 좋지만 TypeScript는 기능적이고 유형이 안전한 유형 시스템을 사용하지 않기 때문에 구현하기가 지옥처럼 복잡하고 수행하기에 완전한 해킹이 될 것입니다.

조금 늦게 왔지만 @Igorbek 도 동의합니다. #1336에서 작성한 내 의견을 반복하고 명확한 'pack' 및 'unpack' 연산자가 있는 C++ 매개변수 패킹에서 아이디어를 빌립니다.

유형을 튜플에 패킹하는 것은 Typescript의 스프레드 연산자 사용법과 일치하는 것 같습니다.

let [x, y, ...rest] = [1, 2, 3, 4, 5] // pack
foo(...params) // unpack
let all = [1, 2, ...other, 5] // unpack

// keep in mind this is already implemented, which kind of similar to mapping types
function map(arr) { ... }
let spreadingmap = [1, 2, ...map(other), 5];

<...T_values> = [T1, T2, T3, etc...] 를 추론하기가 훨씬 쉽습니다.

C++는 압축을 위해 스프레드 연산자를 사용하고 압축을 풀기 위해 줄임표를 사용하지만 둘 다에 대해 스프레드를 사용하는 것이 Typescript와 더 일관성이 있습니다.

module Promise {
  function all<...T_values>(   // pack into a tuple of types, conceptually identical to rest parameters
      values: [ (<PromiseLike<T*>> ...T_values) ]  // unpack, cast, then repack to tuple
  ): Promise<T_values> // keep it packed since T_values is a tuple of whatever types
}

@isiahmeadows @JsonFreeman 매핑 없이 이 모든 것이 무슨 소용이 겠습니까 ?

또한 #1336에서 제기된 것처럼 가변 Array.flatten 어떻습니까?

@jameskeane 전반부는 초기 아이디어였지만 중간 휴식 매개변수(일부 API에 있음)의 경우는 다루지 않습니다.

function foo<...T>(a: Foo, b: Bar, ...rest: [...T, Baz]): Foo;

Function.prototype.applyFunction.prototype.call 도 잘 다루지 않습니다.

#1336의 경우 다음을 통해 유사하게 구현될 수 있습니다.

angular.module('app').controller(['$scope', function($scope: ng.IScope) { /*etc...*/ }]);

interface IModule {
  controller(injectable: [...string[], () => any]);
}

나는 따라 잡았고 튜플 유형이 엄격한 길이라고 순진하게 가정하고 있다는 것을 깨달았습니다. 어떤 imo가 가장 직관적입니다. 엄격한 길이의 튜플 유형(#6229)을 얻는다고 가정할 때 문제는 무엇입니까?

@isiahmeadows 위의 중간 나머지 매개변수 경우의 예에서 엄격한 길이의 튜플을 사용하면 해결되지 않습니까? ...rest: [...T, Baz] arr = [...other, 123] 를 펼치는 스프레드와 동일하게 읽고 있습니다. 이것은 curry 제기한 것과 동일한 문제입니다. 맞습니까?

applycall 경우 교차 유형에 포함되지 않습니까? (어쨌든 Function 인터페이스에서 유형을 갖는 것의 가치를 실제로 보는 것은 아닙니다.)

// as in
const t: [any, string] & [number, any] = [1, "foo"]

interface Function<R, T, ...A> {
    bind<...Y, ...Z>(
        this: (this: T, ...args: A & [...Y, ...Z]) => R, // tricky bit, luckily intersecting tuples is pretty easy
        thisObject: T,
        ...args: Y
    ): (this: any, ...rest: Z) => R
}

@jameskeane

현재 가변성 제안은 #6229가 실제로 수락되는 것으로 가정합니다(즉, 튜플은 기본적으로 엄격함).

func.apply , func.bind , func.call_.curry 의 경우 유일한 문제는 func.bind , _.curry , 및 친구, 또는 더 일반적으로 부분 적용을 사용하는 모든 것. 또한 분리할 나머지 매개변수를 선택할 수 있어야 하며 실제로는 메서드별로만 수행할 수 있습니다.

callapply 는 매우 간단합니다.

type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;

interface Function<R, T, ...A> {
    call(this: Callable<R, T, ...A>, thisArg: T, ...args: [...A]): R;
    apply(this: Callable<R, T, ...A>, thisArg: T, args: [...A]): R;
}

bind 는 더 어렵습니다. 분할 매개변수는 첫 번째 분할 절반이 완전히 풀릴 때까지 지금의 경우와 달리 필요에 따라 일치해야 합니다. 이것은 구문으로 구현되어야 하므로 컴파일러는 아무 것도 평가하지 않고 유형을 구분하고 올바르게 식별할 수 있습니다.

// Function.prototype.bind
type Callable<R, T, ...A> = (this: T, ...args: [...A]) => R;
type Constructible<R, ...A> = new (...args: [...A]) => R;

interface Function<R, T, ...A> {
    // my proposed syntax for splitting a rest parameter
    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, any, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Constructible<R, ...Y>;

    bind[[...A] = [...X, ...Y]](
        this: Callable<R, T, ...A> & Constructible<R, ...A>
        thisArg: T,
        ...args: [...X]
    ): Callable<R, T, ...Y> & Constructible<R, ...Y>;
}

curry f(1, 2, 3) === f(1, 2)(3) === f(1)(2, 3) === f(1)(2)(3) . bind 와 같이 나머지 매개변수를 두 개로 분할하는 기능이 있어야 할 뿐만 아니라 메서드별로 매우 원시적인 패턴 일치를 수행하는 기능도 있어야 합니다.

interface Curried<R, T, ...XS> {
    // none passed
    (): this;

    // all passed
    (this: T, ...args: [...XS]): R;
}

interface CurriedMany<R, T, X, ...YS> extends Curried<R, T, X, ...YS>  {
    // penultimate case, constraint that ...YS contains no parameters
    [[...YS] = []](arg: X): Curried<R, T, X>;

    // otherwise, split rest into ...AS and ...BS, with `A` used as the pivot
    // (basically, default case)
    [[...YS] = [...AS, A, ...BS]](
        ...args: [X, ...AS]
    ): CurriedMany<R, T, A, ...BS>;
}

function curry<R, T>(f: (this: T) => R): (this: T) => R;
function curry<R, T, X>(f: (this: T, arg: X) => R): Curried<R, T, A>;
function curry<R, T, X, ...YS>(
    f: (this: T, arg: X, ...args: [...YS]) => R
): CurriedMany<R, T, X, ...YS>;

curry 대한 추가가 Turing-complete가 될 것이라고는 믿지 않지만 거의 비슷할 것입니다. 이를 방지하는 가장 중요한 것은 특정 유형(Turing-complete 유형 시스템을 사용하는 세 가지 언어인 C++, Scala 및 Haskell 모두가 있음)의 전문화를 일치시키는 기능일 것이라고 생각합니다.

@sandersn 위의 예는 볼 수 없지만 가변 매개변수에 대한 제약 조건에 대해 여쭤봐도 될까요?

다음 예를 고려하십시오.

interface HasKey<T> {
    Key(): T;
}

class Row<...T extends HasKey<X>, X> {
    // ...
}

_추가로, X 를 나열해야 하는 요구 사항을 잠재적으로 삭제하는 방법에 대한 논의는 https://github.com/Microsoft/TypeScript/issues/7848 을 참조

이제 제약 조건이 다음과 같은지 여부에 대해 잠재적으로 약간의 모호성이 있습니다.

  1. (...T) extends HasKey<X> 또는
  2. ...(T extends HasKey<X>)

이 예에서는 2를 가정합니다.

이러한 종류의 제약 조건(1 및/또는 2)이 가능합니까?

@myitcv 2가 가장 좋은 경로일 수 있지만 제약 조건을 확인하기 위해 기존 논리를 재사용하는 것이 좋습니다.

음... 방금 뭔가를 깨달았습니다. 가변 유형을 포함하는 배열은 어떻게 될까요? 또는 더 구체적으로 아래의 arg 유형은 무엇입니까?

function processItems<...T>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

args 의 요소 유형이 무엇인지 묻는 것 같습니다. 튜플의 경우 이것이 일반적으로 요소의 공용체 유형이라고 생각합니다. 나는 그것을 입력하는 더 좋은 방법을 생각할 수 없습니다.

@sandersn 이 기능의 상태에 대해 의견을

@JsonFreeman 구체적으로 arg 가 무엇인지 묻고 있었습니다. 제 생각에는, 그것이 있어야 any 내 원래 예를 들어, 및 Item<T> 아래로 (와 T F-경계) :

function processItems<...T extends Item<T>>(...args: [...T]): void {
    for (const arg of args) { // Here
        process(arg);
    }
}

이렇게 하면 유형을 로컬에서 확인할 수 있습니다. 유형을 미리 알지 못하며 각 호출에 대해 함수 내에서 유형을 계산할 필요가 없기 때문에 컴파일 속도가 엄청나게 빨라집니다. 단일 인수 유형만 필요한 경우 typeof arg 이면 충분하며 더 짧을 수 있습니다.

아 죄송합니다. 원래 예제의 경우 유형이 T 여야 한다는 의미

나는 두 번째에서 Item<any> 를 의미했습니다...죄송합니다.

T가 되어야 한다고 했을 때 T는 타입이라고 생각했는데, 이 기능의 요점은 T가 타입이 아니라는 것입니다(제 생각에는). 예, 예에서 anyItem<any> 여야 합니다.

하지만 더 넓게는 팀이 2.0 이후에 이 기능을 얼마나 적극적으로 고려하고 있는지 궁금합니다. 강한 의견은 없고 그냥 궁금합니다.

내가 반드시 T 이어야 한다고 생각하지 않는 이유는 T 가 무엇인지 모르기 때문입니다. 물론 가변 T 가 가변 유형 목록의 단일 유형을 나타내거나 확산될 때 목록 자체를 의미하지 않는 한, 즉 T 는 전달된 모든 인수의 하위 유형입니다. 인수 ...T[...T]T[] 할당할 수 있습니다.

또는 모든 불분명한 전문 용어에서 내가 의미하는 바를 명확히 하기 위해 코드 측면에서 의미하는 바는 다음과 같습니다.

// To put it into code
function foo<...T>(list: [...T]): void {
    // This is allowed
    let xs: T[] = list

    // This is allowed
    let list2: [...T] = list

    // This is not allowed
    let list1: [...T] = xs

    // This is allowed
    let item: ?T = null

    // This is not allowed, since it's not immediately initialized
    let other: T

    for (let arg of args) {
        // This is allowed
        let alias: T = arg

        // This is allowed
        let other: ?T = arg

        // This is allowed, since `item` is defined upwards as `?T`
        item = arg

        // This is allowed, since you're doing an unsafe cast from `?T` to `T`.
        alias = item as T
    }
}

하지만 그렇게 하는 것이 더 합리적이고 훨씬 더 유연할 것입니다.

여전히 있으면 좋은 목록에 있지만 주로 라이브러리 작성자에게 관심이 있고 적절한 해결 방법(_n_ 오버로드)이 있으므로 적극적으로 작업하지 않습니다. 추측해야 한다면 2.1은 가능하지만 가능성은 없다고 말하고 싶습니다.

객체 휴식/확산(#2103)을 적절하게 지원하기로 약속한 경우 가변 종류는 한 번에 모든 작업을 수행하는 것을 정당화하기 위해 유형을 확산하기에 충분히 가까울 수 있습니다. (Spread 유형은 { ...T, x: number, ...U, y: string, ...V } 처럼 보이는 객체 유형의 변형입니다.)

n overloads 해결 방법이 클래스나 인터페이스에서 작동하지 않는다는 점을 언급하고 싶습니다. 이는 이 기능에 대한 저의 특별한 관심입니다.

@sandersn this 타이핑을 사용하여 함수에서 bind , applycall "_n_ 오버로드"에 대한 풀 요청을 초대하시겠습니까? 나는 그것이 많은 사람들에게 수용 가능한 일시적인 타협이 될 것이라고 생각하며 일부 프로젝트의 프로세스에서 꽤 많은 버그를 잡을 수 있습니다.

@isiahmeadows

T가 반드시 T이어야 한다고 생각하지 않는 이유는 T가 무엇인지 모르기 때문입니다.

T가 variadic 유형의 튜플 유형이라는 데 동의하는 것 같았습니다. 원래 예제에서 arg 유형은 튜플 요소 유형과 동일합니다( @JsonFreeman이 언급한 "요소의 통합 유형"): 지금은 튜플을 나머지로 사용하여 지원하는 typescript를 상상해

function processItems<...T>(...args: T): void {
  for (const arg of args) { // Here - arg:number|string|boolean
    const other: ??? = arg; // I think the issue is, how to _represent_ this type?
  }
}
processItems(1, 'foo', false); // T is tuple [number, string, boolean]

이 제안과 별도로 튜플의 '요소 유형'을 표현할 수 있는 방법이 있어야 한다고 생각합니다. 그것은 스프레드의 또 다른 용도가 될 수 있습니다. ...T :: number|string|boolean ; 튜플 유형을 확산하면 요소 유형이 됩니다.

for (const arg of args) {
  const cst: ...T = arg;
}

// also, even without variadic types...
type Record = [number, string];
function foo(args: Record) {
  for (const arg in args) {
    const cst: ...Record = arg;
  }
}

이를 염두에 두고 귀하의 다른 예는 다음과 같습니다.

function foo<...T>(...list: T): void {
  let xs: T[] = [list, list] // array of the variadic tuple type

  // This is allowed
  let list5: (...T)[] = [...list]

  // This is *not* allowed
  let list2: [...T] = list

  // This is not allowed
  let list1: [...T] = xs

  // This **is** allowed
  // single element tuple, of variadic union
  // i.e. with number|string|boolean
  //      list4 = [1] or list4 = ['foo'] or list4 = [false]
  let list4: [...T] = [list[n]]

  // This **is**  allowed
  let other: T;

  // This is allowed
  let another: ...T;

  for (let arg of args) {
    another = arg; // allowed, if spreading the tuple is the union type

  }
}

원래 목표를 잃지 않고 강력한 형식의 Promise.all ...

declare module Promise {
  function all<...T>(promises: Promise<...T>[]): T; // means promises is an array of promises to the union type, not what I wanted.

  // Then we need something like, which is now very confusing
  function all<...T>(promises: [...Promise<T*>]): T; 
}}

@sandersn 이제 요청된 다른 기능이 이에 의존하기 시작했는데 우선 순위를 높일 수 있습니까? bind , call 등. 타이핑은 이것에 의존하고 ES 바인드 구문 if/when 그것이 나올 때 의존하므로 이제 항상 당신을 잔소리하는 기발한 라이브러리 작성자보다 더 많이 타고 있습니다. . :)

이것이 추가하기에 특별히 건설적인 것은 아니지만 이 두 가지 기능이 2.1에 포함된다면 정말 기쁠 것입니다. 나는 적어도 하나의 라이브러리(RxJS)를 알고 있습니다. 여기서 코드베이스 자체가 이러한 기능에 의해 개선될 뿐만 아니라 코드를 소비하는 것이 훨씬 덜 어색하고 버그가 발생하기 쉽습니다(Angular 2를 시작하는 모든 제3자가 누락된 가져오기로 물립니다. 관찰 가능한 프로토타입에 패치된 연산자의 경우). 유지 관리 가능한 기능 코드를 작성하려는 사람들에게 정말 획기적인 기능이 될 것입니다.

이것은 반환 유형이 모든 매개변수의 교차인 _.extend 대한 완전한 유형 정의를 제공하는 데 사용할 수 있습니까?

declare module underscore {
  function extend<A, B, C, D, ...>(a: A, b: B, c: C, d: D, ...): A&B&C&D&...;
}

그대로가 아닙니다. 가변 종류에 대한 새 연산자에 대한 세부 정보를 제공하는 제안에 대한 확장이 필요합니다. 아마도 &라고 부를 것입니다. @kitsonk 는 이 주석에서 이 연산자를 이전에 제안했습니다.
현재 이 기능은 몇 가지 더 즉각적으로 중요한 사항 아래에 있으므로 이 제안을 한동안 보지 않았습니다.

완전한 가변 종류를 제공하지는 않지만 #10727은 솔루션의 일부입니다(그리고 우리(@dojo)가 가진 문제를 해결할 가능성이 있음).

좋은 소식입니다! 아직 실제로 가변적인 종류는 아니지만. :( 예를 들어 이번 주에 Object.assign 를 입력하려고 하면 다음과 같이 됩니다.

interface Object {
  // binary version
  assign<T,U>(target: T, source: U): { ...T, ...U };
  // variadic version: bind a variadic kind variable ...T
  // and then spread it using SIX dots
  assign<...T>(...targets: ...T): { ......T };
}

"6개의 점" 구문은 위에서 실제로 논의하지 않은 튜플 종류 변수의 개체 확산입니다.

@sandersn

특히 Object.assign 를 사용하면 다음과 같이 입력할 있으며 대상을 변경하기 때문에 기술적으로 하위 집합을 캡처할

assign<T>(target: T, ...sources: Partial<T>[]): T;

그 결함은 대상을 변경하여 구조 유형을 제자리에서 변경한다는 것입니다.

@isiahmeadowssources 의 회계 유형 없이 Ttarget 유형으로 수정합니다. 비 가변 버전으로 지금 시도해 볼 수 있습니다.

declare function _assign<T>(target: T, source: Partial<T>): T;
_assign({}, { a: 10 }); // T is {}

이미 언급했듯이 assign 는 _a spread type_ #10727을 사용하며 다음과 같이 정의할 수 있습니다.

// non variadic
declare const assign: {
  <T>(target: T): T;
  <T, S>(target: T, source: S): {...T, ...S};
  <T, S1, S2>(target: T, source1: S1, source2: S2): {...T, ...S1, ...S2};
};
// variadic
declare function assign<T, [...S]>(target: T, ...sources: [...S]): {...T, ...[...S]};

_참고: 나는 여전히 나에게 훨씬 더 의미가 있는 튜플 기반 구문 [...T] 을 주장하고 있습니다._

@sandersn BTW, variadic 종류가 언제 상륙할
그리고 구문과 관련하여 여전히 구문에 대한 피드백을 수락합니까 아니면 모두 동의합니까?

구문과 저수준 의미는 아직 명확한 합의가 없습니다.

2016년 12월 13일 화요일 13:26 Igor Oleinikov [email protected] 다음과 같이 썼습니다.

@sandersn https://github.com/sandersn BTW, 언제 업데이트
다양한 종류가 상륙할 예정입니까? 2.2에서 볼 기회가 있을까요?
그리고 구문과 관련하여 여전히 구문 또는
당신은 그것에 모두 동의합니까?


당신이 언급되었기 때문에 이것을 받는 것입니다.
이 이메일에 직접 답장하고 GitHub에서 확인
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-266819647 ,
또는 스레드 음소거
https://github.com/notifications/unsubscribe-auth/AERrBIa5fE8PSk-33w3ToFqHD9MCFoRWks5rHuM5gaJpZM4GYYfH
.

이 문제의 상태가 무엇인지 알 수 있습니까?

그래서 당신이 생각하는 옵션은 무엇입니까? 이것은 팀과 함께 의제에 있습니까? 그것은 내가 반복적으로 부딪히는 유형 시스템의 유일한 약한 부분입니다. 두 가지 사용 사례가 있습니다. 단순하고 복잡하지만 더 일반적인 것입니다.

간단한 것은 튜플 유형으로만 하위 유형을 지정할 수 있는 Tuple extends any[] 수퍼 유형을 추가하는 것입니다. 스프레드는 any[] 하위 유형이어야 하므로 다음과 같이 작동합니다.

declare interface Plugin<A: Tuple, P> {
  (...args: A): P | Promise<P>
}

const p: Plugin<[string, { verbose: boolean }], int> =
  (dest, { verbose = false }) => 4

현재 ...args: T[] 는 서명 끝에만 허용됩니다.

복잡한 사용 사례는 ...args: Tuple 가 서명 내부의 모든 위치에서 합법적이어야 합니다(튜플은 고정 길이이므로 문제 없음).

/**
 * Takes a function with callback and transforms it into one returning a promise
 * f(...args, cb: (err, ...data) => void) => void
 * becomes
 * g(...args) => Promise<[...data]>
 */
function promisify<A extends Tuple, D extends Tuple, E>
    (wrapped: (...args: A, cb: (error: E, ...data: D) => void) => void)
    : ((...args: A) => Promise<Data>) {
  return (...args) => new Promise((resolve, reject) =>
    wrapped(...args, (e, ...data) =>
      e ? reject(e) : resolve(data)))
}

const write: ((fd: number, string: string, position?: number, encoding?: string)
              => Promise<[number, string]>) =
  promisify(fs.write)

예, 어제 TypeScript로 막 시작했는데 기능을 래핑하는 데 사용하는 단일 데코레이터로 인해 이미 기능을 자동으로 입력하는 것이 불가능했습니다(물론 수동으로 수행할 수 있음). 나는 시작을 시도했다!):

function portable(func) {
    return function(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
}

사실상 데코레이터가 하는 일은 함수가 메서드로도 호출될 수 있도록 하여 메서드로 연결되고 동일하게 작동하도록 하는 것입니다. 기본 예제로 Array 프로토타입을 패치하는 나쁜 예제가 있습니다. flatMap 의 기본 버전 사용:

function _flatMap<T, R>(
    array: T[],
    iteratee: (item: T) => R[]
): R[] {
    let result: R[] = []
    for (const item of array) {
        for (const value of iteratee(item)) {
            result.push(value)
        }
    }
    return result
}

const flatMap = portable(_flatMap)
Array.prototype.flatMap = flatMap

flatMap([1,2,3,4], x => [x, x])
// Is the same as
[1,2,3,4].flatMap(x => [x, x])
// Is the same as
flatMap.apply([1,2,3,4], [x => [x, x]])
// Is the same as
flatMap.call([1,2,3,4], x => [x, x])

이제 flatMap ( _flatMap 아님) 유형이 다음과 같은 것이 분명하기를 바랍니다.

function flatMap<T, R>(this: T[], iteratee: (item: T) => R[]): R[]
function flatMap<T, R>(this: undefined, array: T[], iteratee: (item: T) => R[]): R[]

그러나 _flatMap 에서 매개변수 유형을 추출하여 데코레이팅된 함수의 유형 정의 내에서 사용할 수 없기 때문에 이식 가능한 types 을 추가할 방법이 없습니다 _flatMap 다음과 같이 작성하십시오.

// First argument to func is required for portable to even make sense
function portable<T, R, ...Params>(func: (first: T, ...rest: Params) => R) {
    // The arguments of calling with this is undefined should be simply
    // exactly the same as the input function
    function result(this: undefined, first: T, ...rest: Params): R
    // However when this is of the type of the first argument then the type
    // should be that the parameters are simply the type of the remaining
    // arguments
    function result(this: T, ...rest: Params): R
    function result(...args) {
        if (this === undefined) {
            return func(...args)
        } else {
            return func(this, ...args)
        }
    }
    return result
}

TypeScript에 대한 나의 초기 경험을 보여주고 가변 제네릭이 왜 중요한지에 대한 또 다른 사례를 보여주기 때문에 이것을 공유하고 싶었습니다.

@sandersn :

적절한 해결 방법이 있습니다 - n 오버로드

기술적으로 정확하지는 않지만 이것이 현실을 완전히 반영하지 못한다고 생각합니다. 네, 기술적으로 이것이 부족하다고 해서 오버로드가 이 스레드에서 언급된 기능을 입력하는 것을 막을 수는 없습니다. 그러나 이러한 사소한 불편으로 인해 이러한 과부하 기반 솔루션은 지금까지 lib.d.ts 도달하지 못했습니다.

사실, 이 스레드의 많은 사람들은 ...... 뿐만 아니라 ...*X , [...T = ...A, ...B, ...C] 포함하여 원래 이 제안의 일부가 아닌 더 많은 구문을 제안하기 위해 각각의 기능을 다루는 데 필사적이었습니다 [...PromiseLike<T>] , <[...X, ...Y] = [...A]><PromiseLike<T*>> .

나는 이것이 우리 모두가 여기에서 문제를 해결하려고 노력하고 있음을 보여주기 위한 것이라고 생각합니다. 우리는 이와 같은 더 강력한 구문이 필요하다는 일반적인 인식을 공유하고 있으며 여기에서 선택하는 방법이 문제를 해결하는 데 도움이 되기를 바랍니다.

사이드 노트 : 대한 Ramda의 R.path 우리는 여전히 (여전히 열심히 순열가 폭발 한 것이다 방법) 튜플 지원을 놓친 과부하의 천 틱 라인의 입력을 생성, 그냥 종료하지 않도록 실제 프로젝트에 컴파일을 야기했다 더 이상. 최근에 이터레이션이 실행 가능한 대안으로 발견되었습니다(#12290).

그건 그렇고, @Artazor 와 @Igorbek이 제안한 제안에 대해 아직 언급하지 않으셨습니다. 그것에 대한 당신의 생각은 어땠나요?

저는 여기(+ #6606)와 같은 기본 구현에 대해 논쟁하고 싶습니다. 우리는 거의 모든 것을 할 수 있습니다. 여기에서 이를 설명하기 위해 몇 가지 솔루션을 제공할 것이지만 추가 질문이 있을 수 있습니다.

먼저 ... 연산자를 구현할 있는 몇 가지 장소를 살펴보겠습니다.

v ... for | 정의(캡처) | 사용(확산)
-|-|-
기능 | type Fn = (...args: any[]) => {} | type Returns = typeof fn(...MyTuple); (#6606)
배열 | 유형 수준 튜플 구조 분해. 인덱스 액세스 + 스프레드(오른쪽 참조) + 재귀를 사용하여 기술적으로 에뮬레이트할 수 있습니다. | type Arr = [Head, ...Tail];
개체 | 유형 수준 객체 구조화. 필요하지 않습니다. Omit 하면 됩니다. #12215를 참조하세요. | type Obj = { a: a, ...restObj }; (필요하지 않음, Overwrite , #12215 참조)
제네릭 | type Foo<...T> 를 정의하여 Foo<1, 2, 3> ( [1, 2, 3T 로 캡처). 재미있지만 어떤 유스 케이스에 이것이 필요한지 모르겠습니다. | Bar<...[1,2,3]> ( A = 1 등)을 수행하기 위해 type Bar<A,B,C> 를 정의합니다. 마찬가지로, 이것이 필요한 유스 케이스를 모릅니다.
노동 조합 (보너스) | ? | type Union = "a" | "b"; type MyTuple = ...Union; // ["a", "b"] (순서는 신뢰할 수 없지만 튜플을 통해 공용체/객체의 반복을 가능하게 합니다. 어쨌든, 여기서는 범위를 벗어납니다.)

따라서 즉시 관련성이 있는 유형 수준 ... 인스턴스가 두 개뿐입니다. 특히 여기에 사용된 두 가지:

declare function f<U, T>(head: U, ...tail: T): [U, ...T];

#6606의 맥락에서 또 다른 것이 관련이 있습니다. 함수 응용 프로그램을 위한 튜플 유형의 압축을 푸는 기능(예: typeof f(...MyTuple) 입니다. 여기에서 언급한 더 어려운 문제를 해결하기에 충분하다고 생각합니다. 여기에 몇 가지 솔루션을 제공하려면:

@jameskeane :

튜플의 '요소 유형'을 나타내는 방법이 있어야한다고 생각합니다

요소의 합집합을 얻으려면 내 TupleToUnion 참조하십시오.

Promise.all

// helpers: `mapTuple` needs #5453 to define, #6606 to use
type TupleHasIndex<Arr extends any[], I extends number> = ({[K in keyof Arr]: '1' } & Array<'0'>)[I];
type Inc = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // longer version in gist
declare function mapTuple<F extends (v: T) => any, Tpl extends T[], T>(f: F, tpl: Tpl): MapFn<F, Tpl, T>;
type MapFn<
    F extends (v: T) => any,
    Tpl extends T[],
    T,
    // if empty tuple allowed:
    // I extends number = 0,
    // Acc = []
    // otherwise:
    I extends number = 1,
    Acc = [F(Tpl[0])]
> = { 1: MapFn<F, Tpl, T, Inc[I], [...Acc, F(Tpl[I])]>; 0: Acc; }[TupleHasIndex<Tpl, Int>];

declare module Promise {
  function all<Promises extends Promise<any>[]>(promises: Promises): typeof mapTuple(<T>(prom: Promise<T>) => T, Promises);
}

@danvk :

_.extend

@sandersn :

Object.assign

이것들은 둘 다 Ramda의 mergeAll 의 가변 버전입니다. 6개의 점이 필요하지 않습니다!

@isiahmeadows :

이 문제의 상태가 무엇인지 알 수 있습니까?
구문과 저수준 의미는 아직 명확한 합의가 없습니다.

내가 올바르게 이해한다면, 당신은 다른 몇몇이 제안한 접근 방식이 당신이 언급한 currybind 와 같은 더 힘든 타이핑을 다루는 것을 고려할 것인지 여부를 주로 걱정했습니다. 다음은 그들의 제안에 따라 그 특정한 것에 대한 나의 견해입니다.
이 전략은 함수 응용 프로그램에 대한 인수 유형 검사를 연기하여 함수 유형에서 튜플 유형으로 params i~j에 대한 유형 요구 사항을 추출하는 것이 어렵다는 사실을 속이고 약간 유사합니다.

// helpers in https://gist.github.com/tycho01/be27a32573339ead953a07010ed3b824, too many to include

// poor man's version, using a given return value rather than using `typeof` based on the given argument types:
function curry<Args extends any[], Ret>(fn: (...args: Args) => Ret): Curried<Args, Ret>;
type Curried<
  ArgsAsked,
  Ret,
  ArgsPrevious = [] // if we can't have empty tuple I guess any[] might also destructures to nothing; that might do.
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      Ret,
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// robust alternative that takes into account return values dependent on input params, also needs #6606
function curry<F>(fn: F): Curried<F>;
type Curried<
  F extends (...args: ArgsAsked) => any,
  ArgsAsked extends any[] = ArgsAsked,
  ArgsPrevious = []
> = <
  ArgsGiven extends any[] = ArgsGiven,
  ArgsAll extends [...ArgsPrevious, ...ArgsGiven]
      = [...ArgsPrevious, ...ArgsGiven]
  >(...args: ArgsGiven) =>
    If<
      TupleHasIndex<ArgsAll, TupleLastIndex<ArgsAsked>>,
      F(...[...ArgsPrevious, ...ArgsGiven]), // #6606
      Curried<ArgsAsked, Ret, ArgsAll>
    >;

// bind:
interface Function {
    bind<
        F extends (this: T, ...args: ArgsAsked) => R,
        ArgsAsked extends any[],
        R extends any,
        T,
        Args extends any[], // tie to ArgsAsked
        Left extends any[] = DifferenceTuples<ArgsAsked, Args>,
        EnsureArgsMatchAsked extends 0 = ((v: Args) => 0)(TupleFrom<ArgsAsked, TupleLength<Args>>)
        // ^ workaround to ensure we can tie `Args` to both the actual input params as well as to the desired params. it'd throw if the condition is not met.
    >(
        this: F,
        thisObject: T,
        ...args: Args
    ): (this: any, ...rest: Left) => R;
    // ^ `R` alt. to calc return type based on input (needs #6606): `F(this: T, ...[...Args, ...Left])`
}

네, 저는 많은 도우미 유형을 사용했습니다. 우리가 가진 것을 활용하려고 했습니다(+ 조금만 더 있으면 무엇을 할 수 있을지 상상해 보세요). 나는 ...... , ...*X , [...T = ...A, ...B, ...C] , [...PromiseLike<T>] , <[...X, ...Y] = [...A]> 또는 <PromiseLike<T*>> 에 대해 그다지 반대하지 않습니다. 그러나 IMO는 ... 만으로도 현재 실제 문제를 해결하는 데 도움이 되며 해결되기를 바랍니다.

편집: bind 대한 인수 제약을 해결했습니다.

그냥 아마 바보 같은 질문입니다. 이는 카레 함수를 올바르게 입력할 수 있다는 점에서 매우 유망해 보입니다.
그러나 일부 실제 프로젝트의 경우 고도로 기능적인 프로그래밍 지향적인 코드를 입력하는 데 많은 시간을 할애하고 싶지 않을 수 있습니다.
그래서 _tsconfig.json_에서 --strict 가 기본적으로 활성화되어 있기 때문에 코드의 일부에 대한 유형 검사를 비활성화하는 방법이 있는지 궁금합니다(게으름이나 시간 부족 때문에).
하지만 말했듯이, 그것은 아마도 어리석은 질문 일 것입니다. ^_^

@ 주제 오프의 exclude 섹션을 tsconfig 또는 다른 tsconfig 다른 프로젝트 수준에 대한의.

또한 다른 제안을 하고 싶습니다. &| 가 다음 구문을 사용하는 단일 튜플 인수와 함께 작동하도록 할 수 있습니까?

<...T>(...args:T): ...T&
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 & t2 & t3;
// and
<....T>(...args:T): ...T|
// is the same as 
<t1, t2, t3>(...args:[t1, t2, t3]): t1 | t2 | t3;

@HyphnKnight 의 위 제안은 내가하고있는 일에도 매우 유용합니다.

이 제안이 활발히 진행되고 있지 않다는 면책 조항을 추가하고 싶습니다. 그러나 이 문제를 처음 보기 시작했을 때 읽고 싶었던 "선행 기술" 종류의 논문을 찾았습니다. http://www.ccs.neu.edu/racket/pubs/esop09-sthf.pdf

나중에 참조할 수 있도록 여기에 남겨 둡니다.

나는 이것을 위해 실험하는 몇 가지 PR을 열었습니다.

  • [ ] #17884 튜플 유형의 스프레드(WIP)
  • [x] #17898 나머지 매개변수 추출(준비)
  • [ ] #18007 유형 호출(WIP)에서 확산
const c = 'a' + 'b';

문제를 해결할 수 있습니까? 의 추론 유형 c 이다 'ab' 하지 string

StackOverflow에 대한 관련 질문: TypeScript의 명시적 마지막 함수 매개변수

@sandersn 당신의 제안은 내가 볼 수있는 한이 경우를 다룰 것입니다. 이것이 맞습니까?

초기 제안 이후 2년이 넘었지만 여전히 희망을 가져야 합니까?

여보세요!
가변 수의 배열을 사용하고 요소를 혼합하고 일치시켜 새 배열을 만드는 생성기를 입력하려고 합니다.
for...of 루프에서 이 생성기를 사용하고 싶지만 값을 적절하게 입력할 수 없습니다.
코드(아직 실행하지 않았기 때문에 실수가 있을 수 있지만 이것이 내가 하려는 것입니다):

function* CombineEveryArgumentWithEveryArgument(...args: any[][]) {
    if (args.length < 1) {
        return [];
    }
    var haselements = false;
    for (var arg of args) {
        if (arg && arg.length > 0) {
            haselements;
        }
    }
    if (!haselements) {
        return [];
    }
    var indexes = [];
    for (var i = 0; i < args.length; i++) {
        indexes.push(0);
    }
    while (true) {
        var values = [];
        //One item from every argument.
        for (var i = 0; i < args.length; i++) {
            values.push(args[i][indexes[i]]);
        }
        if (indexes[0] + 1 < args[0].length) {
            yield values;
        }
        else {
            return values;
        }
        //Increment starting from the last, until we get to the first.
        for (var i = args.length; i > 0; --i) {
            if (indexes[i]++ >= args[i].length) {
                indexes[i] = 0;
            }
            else {
                break;
            }
        }
    }
}

사용 예:

for (let [target, child] of
    CombineEveryArgumentWithEveryArgument(targetsarray, childrenarray)) {

중간 변수를 만들지 않고 대상 및 자식에 대한 입력을 얻을 수 있는 방법을 알 수 없습니다.

이런게 좋겠죠?

function * generator<...T[]>(...args: T[]): [...T]

@Griffork 이 제안이 구현될 때까지 올바른 방법은 함수에 대해 많은 오버로드를 생성하는 것입니다.
예를 들어, promise.all 유형을 참조하세요.
https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L41 -L113

이 구문이 매우 혼란스럽습니다.

function apply<...T,U>(ap: (...args:...T) => U, args: ...T): U {

이것은 나에게 훨씬 더 자연스럽게 느껴집니다.

function apply<T, U>(ap: (...args: T) => U, args: T): U {

런타임에 나머지 매개변수는 배열이며 현재 TS에서 이 작업을 수행할 수 있습니다.

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

따라서 argsT 의 배열이라는 제한을 제거하고 대신 TS 컴파일러가 T 대한 튜플 유형을 추론할 수 있도록 하는 것이 논리적으로 보입니다 T

function apply(ap: (...args: [number, number]) => number, args: [number, number]): number {

튜플에 대해 제기된 몇 가지 우려가 있다는 것을 보았고 모두 이해하지 못했지만 현재 제안에서 개발자가 ... 를 사용해야 하는 경우를 이해하기 어렵다는 점을 강조하고 싶었습니다. 유형 위치와 튜플은 훨씬 더 직관적입니다.

... 하지만 [...T, ...U] 와 같이 두 개의 튜플 유형을 연결하는 데 여전히 의미가 있습니다.

@felixfbecker
에 대한 제안

function apply<...T,U>(ap: (...args:T) => U, ...args: T): U {

T 는 동적으로 생성된 튜플 유형이므로 stringint 를 함수에 전달하면 T[string, int] .
이것은 다음과 같은 패턴을 동적으로 표현하려는 경우에 특히 흥미롭습니다.

function PickArguments<T>(a: T[]): [T];
function PickArguments<T, U>(a: T[], b: U[]): [T, U];
function PickArguments<T, U, V>(a: T[], b: U[], c: V[]): [T, U, V];
//More overloads for increasing numbers of parameters.

//usage:
var [a, b, c] = PickArguments(["first", "second", "third"], [1, 2, 3], [new Date()]);
var d = b + 1; //b and d are numbers.
var e = c.toDateString(); //c is a date (autocompletes and everything), e is a string.

현재 가변 수의 인수를 사용하여 일반적으로 입력하는 함수를 작성하려면 해당 함수에 제공될 수 있는 모든 수의 인수에 대해 일반화된 오버로드를 작성해야 합니다. ...T 제안은 본질적으로 컴파일러가 자동으로 함수 정의를 생성하도록 합니다.

당신의 제안:

function apply<T, U>(ap: (...args: T) => U, args: T): U {

모든 매개변수가 동일한 유형으로 처리되도록 강제하며 더 구체적인 유형 검사를 가질 수 없습니다. 예를 들어 위의 예에서 반환된 모든 값은 any 유형입니다.

나는 또한 추가 ... 를 읽기가 매우 어렵다는 것을 알게 되었습니다.
@felixfbecker 아이디어와 마찬가지로

function apply<...T, U>(ap: (...args: ...T) => U, args: ...T): U {...}

apply<...T, 읽을 때 가장 먼저 떠오르는 것은 이것이 스프레드 연산자이지만 실제로는 스프레드를 전혀 수행하지 않는다는 것입니다.

@Griffork , 귀하의 예에서 T 는 여전히 [string, int] 입니다.
이것이 @felixfbecker 가 "대신 TS 컴파일러가 T에 대한 튜플 유형을 추론할 수 있도록 하는 것"을 의미하는 것입니다. 적어도 제가 이해하는 방식은

모든 매개변수가 동일한 유형으로 처리되도록 강제하며 더 구체적인 유형 검사를 가질 수 없습니다. 예를 들어 위의 예에서 반환된 모든 값은 any 유형입니다.

@Griffork 아니요, 제 생각에는 args 배열에 대한 튜플 유형을 유추하여 각 매개변수에 튜플의 위치에 따라 고유한 유형을 부여합니다. ...args: T[] 는 모두 같은 유형 T 가 되도록 강제하지만 ...args: T (현재 컴파일 오류임)는 T 대한 튜플 유형을 유추합니다.

apply<...T를 읽을 때 가장 먼저 떠오르는 것은 스프레드 연산자이지만 실제로는 스프레드를 전혀 수행하지 않는다는 것입니다.

@unional 동의합니다. 바로 이것이 혼란이 시작되는 곳입니다.

@unional
나는 그것을 확산 연산자로도 읽었고 "이 유형을 사용할 때마다 확산"으로 읽었습니다.
이 글을 읽는 나에게

function apply<T, U>(ap: (...args: T) => U, args: T): U {

T 는 무언가 배열(예: string[] )이 될 것으로 예상합니다.

그리고 이것을 읽고 :

function apply<T, U>(ap: (...args: T[]) => U, args: T[]): U {

모든 인수를 T 유형에 할당할 수 있을 것으로 예상합니다( string 와 같은 한 유형임).

위의 제안에 대한 요점은 암시적 으로 제네릭이 임의의 양의 유형을 나타낼 수 있도록 하는 것을 피하는 것이었습니다.

@felixfbecker
편집하다:
오 그래. 여전히 직관적이라고 생각하지 마십시오.

나는 T가 무언가 배열(예: string[])일 것으로 예상합니다.

튜플은 예를 들어, 각 요소에 대해 고정 된 길이와 특정 유형의 단지 배열이다 "뭔가 배열"입니다 [string, number] (대 (string | number)[] 언 바운드이며 요소가 무엇을 가지고 무엇을 선언하지 않는, 유형).

그렇다면 실제로 그 동작을 원하면 무엇을 입력합니까?

정확히 어떤 동작을 말하는지 모르겠지만 ...args: T[] 의해 수행될 "모든 매개변수를 동일한 유형으로 강제 설정"한다고 가정합니다.

나는 그것을 확산 연산자로도 읽었고 "이 유형을 사용할 때마다 확산"으로 읽었습니다.

그렇기 때문에 혼란스럽다고 생각합니다.
확산할 때 그냥 하면 "확산 가능한" 것을 선언하지 않습니다.

const a = { x: 1, y: 2 }
const b = { ...a }

// likewise
function appendString<T>(...args: T): [...T, string] {
  args.push('abc')
  return args
}

응. 제네릭 유형 인수가 "확산 가능"해야 한다고 선언하려면(ES 사양에 따라 반복 가능해야 함을 의미함) TypeScript에서 extends 이를 표현할 수 있는 방법이 이미 있습니다.

function foo<T extends Iterable<any>>(spreadable: T): [...T, string] {
  return [...spreadable, 'abc']
}

const bar = foo([1, true])
// bar is [number, boolean, string]

물론 나머지 매개변수의 경우 Iterable이 아니라 Array라는 것을 알 수 있습니다.

우리가 말하는 것은 이미 제안되었습니다: https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189703556

하지만 나머지는 한 자리에서 먹기에는 너무 길다. 🌷

튜플 연결이 착륙하면 교회 번호를 구현할 수 있습니다! 만세!

type TupleSuc<T extends [...number]> = [...T, T['length']];
type TupleZero = [];  // as proposed, we need empty tuple
type TupleOne = TupleSuc<TupleZero>;
type Zero = TupleZero['length'];
type One = TupleOne['length'];

조건부 유형의 재귀가 작동하면 원하는 길이의 튜플을 만들 수 있습니다.

type Tuple<N extends number, T = TupleZero> = T['length'] extends N ? T : Tuple<N, TupleSuc<T>>;
type TupleTen = Tuple<10>;
type Ten = TupleTen['length'];

이 스레드를 모두 읽었다고 생각하지는 않지만 일반 매개변수에 ...T 있는 것이 혼란스럽다면,
값 수준 구조화 구문을 추가로 미러링하여 [...T]
유형 수준 인수 위치는 유형 수준 튜플인 경우 유형을 구조화합니까? 이것은 또한 필요합니다
튜플을 사용하여 나머지 매개변수를 입력할 수 있으므로 호출 사이트에서 다음 항목과 동일합니다.
타입스크립트에서:

const first = (a: number, b: string) => …;
const second = (...ab: [number, string]) => …;

first(12, "hello"); // ok
second(12, "hello"); // also ok

INB4 "그러나 그것은 유형 지향 방출입니다" – 아니요. 이것은 방출하는 것을 변경하지 않으며 first 에는 여전히 두 가지가 있습니다.
고유한 인수가 방출되더라도 second 에는 여전히 하나의 나머지 인수가 있습니다. 변하는 유일한 것은
호출 사이트에서 TypeScript는 second 의 매개변수가 순서대로 튜플과 일치하는지 확인합니다.
[number, string] .

어쨌든 [...Type] 구문을 인정한다고 가정하면 다음과 같이 apply 를 작성할 수 있습니다.

function apply<
  [...ArgumentsT], // a type-level tuple of arguments
  ResultT
>(
  // the call site of `toApply` function will be used to infer values of `ArgumentsT`
  toApply:   (...arguments: ArgumentsT) => ResultT,
  arguments: ArgumentsT
) :
  ResultT
{
  // …
}

// NB: using my preferred formatting for complex type-level stuff; hope it's readable for you
// this is entirely equivalent to OP's notation version:
function apply<[...T], U>(ap: (...args: T) => U,  args: T): U {
  // …
}

// so at the call site of
const fn = (a: number, b: string, c: RegExp) => …;

// we have `ArgumentsT` equal to [number, string, RegExp]
apply(fn, [12, "hello" /s+/]); // ok, matches `ArgumentsT`
apply(fn, [12, /s+/]); // not ok, doesn't match `ArgumentsT`

[...Type] 구문은 완전히 값 수준 구조 분해로 작동하여 분할을 허용합니다.
필요에 따라 유형 수준 튜플을 결합합니다.

type SomeType  = [string, number, "constant"];
type OtherType = ["another-constant", number];

type First<[First, ..._]> = FirstT;
type Rest<[_, ...RestT]> = RestT;
type Concat<[...LeftT], [...RightT]> = [...LeftT, ...RightT];
type FirstTwo<[FirstT, SecondT, ..._]> = [FirstT, SecondT];

// has type `string`
const aString: First<SomeType> =
  "strrriiing";
// has type `[number, "constant"]
const numberAndConstant: Rest<SomeType> =
  [42, "constant"];
// has type `[string, number, "constant", "another-constant", number]`
const everything: Concat<SomeType, OtherType> =
  ["herpderp", 42, "constant", "another-constant", 1337];
// has type `[string, number]`
const firstTwo: FirstTwo<SomeType> =
  ["striiiing", 42];

다음을 사용하여 curry 함수를 입력하는 방법에 대한 예:

type Curried<
  [...ArgumentsT]
  ResultT,
  ArgumentT      = First<ArgumentsT>,
  RestArgumentsT = Rest<ArgumentsT>
> =
  // just ye olde recursione, to build nested functions until we run out of arguments
  RestArgumentsT extends []
    ? (argument: ArgumentT) => ResultT
    : (argument: ArgumentT) => Curried<RestArgumentsT, ResultT>;

// NB. with more complex generic types I usually use generic defaults as a sort-of
// of type-level variable assignment; not at all required for this, just nicer to read IMO

function curry<
  [...ArgumentsT],
  ResultT
>(
  function: (...arguments: ArgumentsT) => ResultT
) :
  Curried<ArgumentsT, ResultT>
{
  // do the magic curry thing here
}

// or in the short indecipherable variable name style

function curry<[...T], U>(fn: (...args: T) => U): Curried<T, U>
{
  // …
}

// this should let you do this (using `fn` from before)
const justAddRegex = curry(fn)(123, "hello");

justAddRegex(/s+/); // ok, matches the arguments of `fn`
justAddRegex(123); // not ok, doesn't match the arguments of `fn`

어떤 유형 인수가 유형 수준 튜플이라고 말할 수 있는 것도 도움이 될 것이라고 가정합니다.
어떤 종류의. 그렇다면 문제는 2.7(제 생각에는?) 튜플 할당 가능성을 고려하는 방법입니다.
튜플 길이를 고려하여 _모든 유형 수준 튜플_의 개념을 표현합니다. 하지만 아마도 다음과 같은
[...] 가 작동할까요? 확고한 의견은 없지만 컨셉에 이름을 붙일 수 있다면 좋을 것 같아요.

// bikeshed me
type OnlyTuplesWelcome<ArgumentT extends [...]> = ArgumentT;

이 경우 위의 [...ArgsT] 구문은 기본적으로 ArgsT extends [...] 의 약어일 수 있습니다.
유형 수준 구조 제거를 사용하는 것은 유형에 대한 제약 조건이 유형 수준 튜플임을 의미합니다.

생각?

@jaen :

(...ab: [number, string]) => …

예, #4130처럼 보입니다. #18004에서 무언가를 시도했지만 내 접근 방식은 약간 해킹되었습니다(합성 노드).

어떤 튜플을 표현할 때 any[] & { 0: any } 사용하는 사람을 보았고, 이는 fwiw 빈 튜플 유형이 될 때까지 작동하는 것 같습니다. 나는 개인적으로 별로 신경쓰지 않았고 대부분 any[] 정착했습니다.

RxJS는 모든 곳에서 이것을 필요로 합니다. 현재 많은 오버로드가 있는 Observable.prototype.pipe 에 대해 가장 중요하지만 항상 "하나만 더 레벨"을 추가하라는 요청을 받고 있습니다.

두 번째 @benlesh에 대해 우리는 RXJS를 광범위하게 사용하며 파이프 기능에 필요합니다.

저는 RXJS 의 파이프 함수처럼 이것을 필요로 하는

저는 runtypes 의 저자이며 이 기능은 합집합과 교집합을 표현하는 데 꼭 필요합니다. 유일한 (불완전한) 해결 방법은 엄청난 과부하입니다.

https://github.com/pelotom/runtypes/blob/master/src/types/union.ts

🤢

pipe , 렌즈 및 커링에도 필요한 ramda 유형을 다시 작성했습니다.
직접적으로 과부하를 관리할 수 없게 만드는 커링으로 인해 codegen이 필요했습니다. 우리의 path 유형은 천 줄에 걸쳐 있으며, 이 시점에서 과부하 유형 성능도 문제가 된다는 것을 발견했습니다.

나머지 인수를 추론하고 적용하는 문제가 해결되었습니까?

function example(head: string, ...tail: number[]): number[] {
  return [Number(head), ...tail]
}

function apply<T, U>(fn: (...args: T) => U, args: T): U {
  return fn.apply(null, args)
}

에서 T의 입력하면 apply(example, ['0', 1, 2, 3]) 로 추정된다 [string, number[]] , 적용 할 수있는 호출에서 오류가 발생합니다.

즉, T 유형은 실제로

type T = [string, ...number[]]

또는

type T =
  {0: string} &
  {[key: Exclude<number, 0>]: number} &
  Methods

참으로 이상한 짐승이지만 ({0: string} & Array<number>)[0]
현재 string [1] 많은 변경 없이 인코딩이 가능한 것 같습니다.
유형 시스템에.

[1] 버그인가요, 정말 string | number 인가요?

이 문제의 36명의 참가자를 귀찮게 해서 죄송합니다(팁: this 사용). 그러나 이것이 아직 고려 중인지, 로드맵에 있는지 등을 어떻게 모니터링할 수 있습니까?

2년 반이 지난 지금까지 아무도 배정받지 못한 게 아쉽네요. 꽤 중요한 기능인 것 같습니다. :(

추신: 나는 수십 개의 댓글을 읽었고 Cmd+F 등을 시도했지만 이 정보를 찾지 못했습니다.

@brunolemos 최신 디자인 회의에서 가변 유형에 대한 참조가 있습니다.
https://github.com/Microsoft/TypeScript/issues/23045
이 기능을 만들려면 먼저 더 원시적이고 개념을 반복적으로 만들어야 하며, 기초가 충분하면 모든 이정표에 추가할 것이라고 확신합니다.

나는 이것을 할 수 없다

type Last<T extends any[]> =
    T extends [infer P] ? P :
    ((...x: T) => any) extends ((x: any, ...xs: infer XS) => any) ? Last<XS> :

#14174의 문제지만 관계로

@kgtkr 참조는 컴파일러가 재귀를 간과하도록 하는 @fightingcat트릭 을 참조하세요.

감사 해요

type Last<T extends any[]> = {
    0: never,
    1: Head<T>,
    2: Last<Tail<T>>,
}[T extends [] ? 0 : T extends [any] ? 1 : 2];

흠, 질문이 있습니다. 믹스인을 처리하기 위해 다음과 같은 코드가 있습니다.

export const Mixed = <

    OP = {}, OS = {}, // base props and state
    AP = {}, AS = {}, // mixin A props and state
    BP = {}, BS = {}, // mixin B props and state
    // ...and other autogenerated stuff
>(

    // TODO: Find a way to write that as ...args with generics:
    a?: ComponentClass<AP, AS>,
    b?: ComponentClass<BP, BS>,
    // ...and other autogenerated stuff

) => {

    type P = OP & AP & BP;
    type S = OS & AS & BS;
    const mixins = [a, b];

    return class extends Component<P, S> {
        constructor(props: P) {
            super(props);
            mixins.map(mix => {
                if (mix) {
                    mix.prototype.constructor.call(this);
                    // some state magic...
                }
            });
        }
    };
};

다음과 같이 사용합니다.

class SomeComponent extends Mixed(MixinRedux, MixinRouter, MixinForm) {
     // do some stuff with mixed state
}

적절한 타이핑, 상태 처리 등으로 예상대로 작동하지만 다양한 종류를 기다리지 않고 더 짧은 방식으로 다시 작성할 수 있는 방법이 있습니까? 왜냐하면 나는 지금 이것에 대해 약간 멍청하다고 느끼기 때문입니다.

3.0에서는 이제 나머지 인수를 튜플로 선언할 수 있습니다.

declare function foo(...args: [number, string, boolean]): void;

그러나 주어진 함수의 튜플 유형의 인수를 얻기 위해 반대로 얻을 수 있습니까?

Arguments<foo> 것이 좋을 것입니다.

@whitecolor 이거 어때요?

type Arguments<F extends (...x: any[]) => any> =
  F extends (...x: infer A) => any ? A : never;

TS 3.0을 사용하면 지금 할 수 있습니다.

function compose<X extends any[], Y extends any[], Z extends any[]>(
  f: (...args: X) => Y,
  g: (...args: Y) => Z
): (...args: X) => Z {
  return function (...args) {
    const y = f(...args);
    return g(...y);
  };
}

그러나 우리는 단일 매개변수에 대해 튜플 이벤트를 반환하고 어떻게든 void를 처리하는 함수를 선언해야 하는 작은 문제가 있으며 반환 유형을 선언해야 합니다. 그렇지 않으면 배열로 유추됩니다. :)

https://www.typescriptlang.org/play/index.html#src =%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Afunction%20foo0() %3A%20void%20%7B%0D%0A%20%0D%0A%7D%0D%0A%0D%0Afunction%20bar0()%3A%20void%20%7B%0D%0A%0D%0A%7D %0D%0A%0D%0Afunction%20foo1(a%3A%20string)%3A%20%5Bstring%5D%20%7B%0D%0A%20%20return%20%5Ba%5D%3B%0D%0A% 7D%0D%0A%0D%0Afunction%20bar1(a%3A%20string)%3A%20%5Bstring%5D%20%7B%0D%0A%20%20return%20%5Ba%5D%3B%0D%0A %7D%0D%0A%0D%0Afunction%20foo2(a1%3A%20string%2C%20a2%3A%20boolean)%3A%20%5Bstring%2C%20boolean%5D%20%7B%0D%0A%20% 20return%20%5Ba1%2C%20a2%5D%3B%0D%0A%7D%0D%0A%0D%0Afunction%20foo21(a1%3A%20string%2C%20a2%3A%20boolean)%3A%20%5Bstring %5D%20%7B%0D%0A%20%20return%20%5Ba1%5D%3B%0D%0A%7D%0D%0A%0D%0A%0D%0Afunction%20bar2(a1%3A%20string%2C %20a2%3A%20boolean)%3A%20%5Bstring%2C%20boolean%5D%20%7B%0D%0A%20%20return%20%5Ba1%2C%20a2%5D%3B%0D%0A%7D% 0D%0A%0D%0A%0D%0Afunction%20compose%3CX%20extens%20any%5B%5D%2C%20Y%20extens%20any%5B%5D%2C%20Z%20extens%20any%5B%5D%3E( %0D%0A%20%20f%3A%20(... 인수%3A%20X)%20%3D%3E%20Y%2C%0D%0A%20%20g%3A%20(...인수%3A%20Y)%20%3D%3E%20Z%0D%0A )%3A%20(...인수%3A%20X)%20%3D%3E%20Z%20%7B%0D%0A%20%20반환%20함수%20(...인수)%20%7B% 0D%0A%20%20%20%20const%20y%20%3D%20f(...args)%3B%0D%0A%20%20%20%20return%20g(...y)%3B% 0D%0A%20%20%7D%3B%0D%0A%7D%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Aconst%20baz0%20%3D%20작성(%0D%0A %20%20foo0%2C%0D%0A%20%20bar0%0D%0A)%3B%0D%0A%0D%0Aconst%20baz21%20%3D%20compose(%0D%0A%20%20foo21%2C%0D %0A%20%20bar1%0D%0A)%3B%0D%0Aconst%20baz2%20%3D%20compose(%0D%0A%20%20foo2%2C%0D%0A%20%20bar2%0D%0A)% 3B%0D%0A%0D%0A%0D%0Aalert(baz2('a'%2C%20false))%0D%0Aalert(baz21('a'%2C%20true))%0D%0Aalert(baz0())

@maciejw
조건부 유형 사용:

function compose<X extends any[], Y extends any, Z extends any>(
  f: (...args: X) => Y,
  g: Y extends any[] ? (...args: Y) => Z : () => Z
): (...args: X) => Z {
    return function (...args) {
        const y = (f as any)(...args);
        return (g as any)(...y);
    } as any;
}

확실히, 하지만 이것은 as any 일종의 해킹입니다. :) 해킹 없이 유형 시스템이 이것을 지원한다면 멋질 것이라고 생각합니다.

글쎄, 함수 본문의 유형은 여러 가지일 수 있으며 이에 대해 할 수 있는 일은 많지 않습니다. 대신 (f as (...args: any[]) => Y) 와 같은 것을 할 수 있지만 실제 이유 없이 명확성이 감소한다고 생각합니다.

variadic 유형이 결실을 맺는다면 내 프로젝트를 위해 작성한 일부 TypeScript 코드를 일반화할 수 있을 것입니다. 이를 통해 REST API의 모양을 완전히 정의하고 해당 Node 서버 및 JavaScript의 유형에 해당 모양을 적용할 수 있습니다. 그것을 위한 클라이언트 라이브러리.

이를 일반화하면 내 코드를 단순화할 수 있을 뿐만 아니라 임의의 API에 대해서도 동일한 작업을 수행할 수 있으며 더 나아가 다른 언어 클라이언트에 대해서도 Swagger 정의를 생성할 수 있습니다... 다른 사용자에게 유용할 수 있습니다! 큰 소리로 꿈을 꾸는 것만으로도 haha

파이프 타이핑에 성공했습니다
https://github.com/kgtkr/typepark/blob/master/src/pipe.ts

@kgtkr :

파이프 유형이 나를 위해 TS 플레이그라운드를 충돌시킵니다(다른 사람들은 잘 작동하지만), 최신 TS가 필요한가요?

TS는 또한 일부 재귀 깊이 오류를 표시 - @isiahmeadows 같은 외모가에 대한 # 26980을 열었다.

@tycho01

파이프 유형이 나를 위해 TS 플레이그라운드를 충돌시킵니다(다른 사람들은 잘 작동하지만), 최신 TS가 필요한가요?

그것은 나에게도 달려 있고, 나는 그것을 벗어나기 위해 오류를 던지도록 강제하기 위해 devtools를 해킹해야했습니다.

TS는 또한 일부 재귀 깊이 오류를 표시 - @isiahmeadows 같은 외모가에 대한 # 26980을 열었다.

그것은 관련이 있지만 다른 것입니다. 두 가지 이유로 조건부 유형으로 제약 조건을 해제합니다.

  • 목록 반복과 같은 더 복잡한 작업을 더 쉽게 수행할 수 있도록 합니다. 또한 컴파일러를 충돌시키거나 임의의 혼란에 빠지지 않고 유형 수준 정수 수학과 같은 것을 열기 위한 프레임워크를 마련할 것입니다.
  • TS의 유형 시스템을 Turing-완전하게 만드는 문제를 보다 적절하게 해결하기 위해 이를 활용하는 도구로 잠재적으로 구체화하거나 향후 입증 가능한 종료를 시행하여 제거할 수 있습니다.

인덱싱된 유형을 제거하는 것이 유형 시스템을 있는 그대로 튜링-완전하지 않게 만들기에 충분하지 않은 경우 그에 따라 업데이트할 수 있도록 자유롭게 의견을 남겨주세요. (물론 제거를 제안하지는 않습니다. 잠재적인 무한 루프에 대해 사람들에게 경고하기 위해 내부적으로 더 잘 처리할 것을 제안합니다.)

다시 생각해보면 이 Pipe 유형은 (vs...: Params<T[0]>) => ReturnType<Last<T>> 을 수행하는 정말 복잡한 방법처럼 느껴집니다. (중간 매개변수 검사를 제외하고) 그 이상의 반복은 아마도 입력 종속 반환 유형에 더 유용할 것입니다.

@ tycho01 그것은 같은 종류의 것들을 시도하는 것 유형은 기본적으로 이것이다 :

   f1,     f2,   ...,   fm,     fn    -> composed
(a -> b, b -> c, ..., x -> y, y -> z) -> (a -> z)

당신은 다음 매개 변수 반환 값 '매개 변수는 이전 매개 변수에 따라 달라집니다'이후 매개 변수가 개별적으로 올바르게 입력하고, 당신은 또한 첫 번째 매개 변수 및 최종 반환 값 계정이 반복이 (하지 않는이야하는 @kgtkr ') . #26980의 "개선된" 버전은 대신 누산기를 사용하도록 최적화했기 때문에 인수를 훨씬 적게 반복했지만 더 정확해졌습니다.


#26980에서 내 것을 보면, 그것이 무엇을 해야 하는지 조금 더 명확하고(덜 숫자 추적), 그것이 내가 그 기능 요청을 제출한 요점의 일부입니다.

@tycho01 @kgtkr BTW, 수정된 PipeFunc 스니펫으로 버그를 업데이트했으며 편의를 위해 여기에 복사했습니다.

type Last<L extends any[], D = never> = {
    0: D,
    1: L extends [infer H] ? H : never,
    2: ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D,
}[L extends [] ? 0 : L extends [any] ? 1 : 2];

type Append<T extends any[], H> =
    ((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never;

type Reverse<L extends any[], R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ?
        Reverse<T, Append<R, H>> :
        never,
}[L extends [any, ...any[]] ? 1 : 0];

type Compose<L extends any[], V, R extends any[] = []> = {
    0: R,
    1: ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ?
        Compose<T, H, Append<R, (x: V) => H>>
        : never,
}[L extends [any, ...any[]] ? 1 : 0];

export type PipeFunc<T extends any[], V> =
    (...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>);

이것은 놀이터에서 충돌하지 않습니다, BTW. 실제로 유형 검사를 수행하며 매우 빠르게 수행됩니다.

아직 잠재적인 _.flow 또는 _.flowRight 유형에서 테스트하지 않았지만 시작점으로 작동해야 합니다.

@tycho01
필수의
typescript@next
3.0.1/3.0.2가 작동하지 않습니다

이 스레드 덕분에, 내가 만든

여러분, 이 문제에 대한 논의와 거의 관련이 없는 정보를 게시하는 것을 중단하십시오. 우리는 다양한 종류를 원하기 때문에 이 토론을 따르는 많은 사람들이 있습니다. 지난 며칠 동안 이 문제를 팔로우하는 이유와 무관한 10개 이상의 이메일을 받았습니다.
나는 나와 동의하는 다른 사람들이 있기를 기대합니다. 지금까지는 스팸에 기여하고 싶지 않았기 때문에 중단되기만을 바랐습니다. 그러나 진지하게, 충분합니다.
추신: 이 알림에 대해 유감입니다. 저처럼 아픈 사람에게

@Yuudaari 저는 Lodash의 _.flow , Ramda의 _.compose 등을 입력하는 것이 이 버그의 원동력 중 하나이며 성공적인 입력은 이 문제를 해결하는 일부라는 점을 지적하겠습니다. 사실 이것이 원래 문제 설명에 나열된 이유 중 하나입니다.

실제로 이 시점에서 저는 오늘날 variadics에 존재하는 문제의 99%가 기능이 아니라 인체 공학에 있다고 생각합니다. 우리는 입력 할 수 있습니다 Function.prototype.bindPromise.all 인덱스 유형, 조건 유형 및 재귀의 혼합물로 완벽를 (당신은 반복 할 수 Append 에 대한 반복 목록 Function.prototype.bind , 그리고 Promise.all 는 간단한 반복 + Append ), 그렇게 하는 것이 매우 어색하고 상용구입니다.

단지 물건을 시작하는 것을 설명하고, 여기에 소음에 추가하려고하지 여기 것은 그들이 당신이 개인적으로 염려되는 사람이 아니더라도, 버그가 존재하는 이유 중 일부에 관한로서 기술적으로 온 주제입니다.

여기에서 발표를 기다리는 사람들이 큰 소식을 놓친 것 같습니다. 이제 가능한 Concat<T, U> 기능이 정확히 [...T, ...U] 밝혀졌습니다.

Pipe 하위 스레드는 여기서 요청한 기능을 시연하는 것입니다. 오늘 이 스레드의 요점에 도달하는 것입니다.

이것은 우리가 이 스레드를 닫는 것이 더 나을 것이 없다는 것을 의미한다고 생각합니다. 그래서 아마도 지금이 질문하기에 좋은 순간일 것입니다. 사람들이 이 제안에서 여전히 원하는 것은 무엇입니까?

[그냥] 너무 어색하고 상투적이야

이것을 사용하는 대부분의 유형은 재귀 자체를 사용하므로 이를 작성하는 사람들은 확실히 익숙할 것입니다. 반면 최종 사용자는 TS 반복이 존재하는지 알 필요 없이 사전 정의된 유형이 있는 라이브러리를 사용하고 프런트 엔드를 작성할 것입니다.

그 시점에서 아마도 이 제안이 대부분 성능을 향상시킬 수 있습니까?

첫째, 맵 개체를 사용하여 형식 시스템이 의도한 대로 재귀를 수행하도록 속이는 것입니까? 그것은 나에게 꽤 해키 보인다. 그런 기능을 사용하는 경우(그렇지만 관련이 없음) 나중에 중단되지 않습니까?

둘째, 이러한 해결 방법을 사용하는 것은 ... 친절하지 않습니다. (특히 작성하지 않은 사람들의 경우) 가독성이 좋지 않으며 결과적으로 유지 관리하기가 비참해 보입니다.

해결 방법이 있다는 이유만으로 의도하고 읽기 쉽고 유지 관리 가능한 방식으로 동일한 기능을 추가하는 제안을 거부하고 싶은 이유는 무엇입니까?

이 해결 방법의 존재로 인해 이 제안이 구문 설탕으로 간주되지 않는다고 생각합니다. 하지만 그렇다고 하더라도 이 혼란에 대해 구문 설탕을 원하지 않는 이유는 무엇입니까?

@유다아리

편집: 컨텍스트에 대한 링크를 추가합니다.

첫째, 맵 개체를 사용하여 형식 시스템이 의도한 대로 재귀를 수행하도록 속이는 것입니까? 그것은 나에게 꽤 해키 보인다. 그런 기능을 사용하는 경우(그렇지만 관련이 없음) 나중에 중단되지 않습니까?

내가 최근에 신고한 버그를 살펴보세요: #26980. 패턴에 의문을 제기하는 것은 당신 혼자가 아닙니다. 여기에서는 약간 주제에서 벗어나지만 자유롭게 이야기하십시오.

재귀가 종료되는지 여부를 파악하는 방법과 관련된 약간의 수학이 있음을 유의하십시오(처음에 그토록 미묘한 차이가 나는 주된 이유 중 하나).

둘째, 이러한 해결 방법을 사용하는 것은 ... 친절하지 않습니다. (특히 작성하지 않은 사람들의 경우) 가독성이 좋지 않으며 결과적으로 유지 관리하기가 비참해 보입니다.

해결 방법이 있다는 이유만으로 의도하고 읽기 쉽고 유지 관리 가능한 방식으로 동일한 기능을 추가하는 제안을 거부하고 싶은 이유는 무엇입니까?

이 해결 방법의 존재로 인해 이 제안이 구문 설탕으로 간주되지 않는다고 생각합니다. 하지만 그렇다고 하더라도 이 혼란에 대해 구문 설탕을 원하지 않는 이유는 무엇입니까?

효과적인 Array.prototype.map 의 일반적인 경우에 튜플을 반복하는 단순화된 방법 이 존재하지만 기본적으로 내 필요에는 쓸모가 없었습니다(누적기가 필요했습니다).

나는 개인적으로 다음과 같은 구문 설탕을 원합니다.

  1. [...First, ...Second] 를 통해 두 목록을 연결합니다.
  2. [...Values, Item] 를 통해 값을 추가합니다.
  3. T extends [...any[], infer Last] 를 통해 마지막 요소 추출.
  4. T extends [A, B, ...infer Tail] 를 통해 꼬리 추출.

이것을 #26980과 결합 하면 위의 유형 을 다음

type Compose<L extends any[], V, R extends any[] = []> =
    L extends [infer H, ...infer T] ?
        Compose<T, H, [...R, (x: V) => H]> :
        R;

export type PipeFunc<T extends any[], V> =
    T extends [...any[], infer R] ?
        (...f: Compose<T, V>) => ((x: V) => R) :
        () => (x: V) => V;

하지만 그게 전부입니다. 여기 있는 대부분의 모든 것이 튜플만 처리하고 객체에는 이미 유사한 작업에 필요한 모든 것이 있기 때문에 다른 구문 설탕은 별로 사용하지 않습니다.

첫째, 맵 개체를 사용하여 형식 시스템이 의도한 대로 재귀를 수행하도록 속이는 것입니까? 그것은 나에게 꽤 해키 보인다. 그런 기능을 사용하는 경우(그렇지만 관련이 없음) 나중에 중단되지 않습니까?

공식적인 단어는 "하지마"와 같은 것 같아요. @ahejlsberg 다음과 같이 말했습니다 .

그것은 영리하지만 의도한 용도를 훨씬 뛰어넘는 것입니다. 작은 예에서는 작동할 수 있지만 끔찍하게 확장됩니다. 이러한 심도 있는 재귀 유형을 해결하는 것은 많은 시간과 자원을 소모하며 향후 체커에 있는 재귀 거버너와 충돌할 수 있습니다.

하지마!

☹️

@jcalz 그래서 #26980이 존재하는 이유가 더 많습니까?

올해 방학에 TS를 사용하기 시작했을 때 내 성향은 _그냥_! ( ...T ) 가변 typevar 튜플의 구문이 되기를 바랍니다. 글쎄, 이것이 들어가기를 바랍니다 :)

[...T, ...U] 의 새로운 용도를 찾았습니다. HTML 빌더를 올바르게 입력합니다. 구체적인 예를 들어 <video> 의 자식은 다음과 같아야 합니다.

  • 요소에 src 속성이 있는 경우:

    • 0개 이상의 <track> 요소

  • 요소에 src 속성이 없는 경우:

    • 0개 이상의 <source> 요소

  • audio 또는 video 하위 요소를 제외하고 상위 콘텐츠 모델에 따라 0개 이상의 요소가 허용되지 않습니다.

이것은 기본적으로 이 유형과 동일하지만 오늘날 TypeScript에서 이를 표현할 방법이 없습니다.

type VideoChildren<ParentModel extends string[]> = [
    ...Array<"track">, // Not possible
    ...{[I in keyof ParentModel]: P[I] extends "audio" | "video" ? never : P[I]},
]

3.5년 :/

사용 사례:

type DrawOp<...T> = (G: CanvasRenderingContext2D, frame: Bounds, ...args: any[]) => void;
const drawOps: DrawOp<...any>[] = [];

function addDrawOp<...T>(fn: DrawOp<...T>, ...args: T) {
    drawOps.push(fn);
}

제안서의 공개 질문 섹션에서 간략하게 언급된 오버로드만 볼 수 있지만 분명히 내가 겪었고 솔루션이나 제안을 보는 데 탁월할 것입니다. 예:

  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: Projection<T>,
    cb: Cb<Pick<TSchema, T>>,
  ): void;
  function $findOne(
    ctx: ICtx,
    filter: FilterQuery<TSchema>,
    projection: undefined,
    cb: Cb<TSchema>,
  ): void;
  function $findOne<T extends keyof TSchema>(
    ctx: ICtx,
    filter: mongodb.FilterQuery<TSchema>,
    projection: Projection<T> | Cb<TSchema> | undefined,
    cb?: Cb<Pick<TSchema, T>>,
  ): void {

  promisify($findOne) // this can't infer types correctly

현재 이것은 전혀 작동하지 않고 promisify(ctx: ICtx, filter: FilterQuery<TSchema>) => Promise<TSchema[]> 로 입력하면 이러한 서명에서 정보가 손실됩니다.

AFAICT에 대한 유일한 실제 솔루션은 기본적으로 약속 함수를 만들고 해당 변형에 대한 모든 가능한 유형을 수동으로 지정하는 것입니다. 래핑된 변형에서 실제 서명을 제공하는 유일한 방법은 오버로드를 지정하지 않고 구현 서명만 지정하는 것입니다. 그러나 그런 방식으로 서명을 지정하는 경우 전달한 인수를 기반으로 예상해야 하는 반환 유형을 호출자에게 알릴 방법은 없습니다.

이것은 나머지 매개변수가 마지막 매개변수만 될 수 있다는 사실 때문에 악화됩니다(예: (cb, ...args) 는 유효하지만 (...args, cb) 아닙니다. 따라서 서명이 내부적으로 공용체 유형이더라도 실제로 예를 들어 cb 가 항상 function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T 로 유형 promisify에 대한 첫 번째 인수이고 동일한 반환 응답으로 서명에 대한 공용체 유형을 얻을 수 있는 경우 매우 간단합니다

@Qix- 귀하의 시나리오는 #24897에 의해 활성화되었습니다. TS 3.0에서 구현되었습니다.

@ahejlsberg 으악 ! 굉장합니다, 감사합니다 ♥️

오래 기다리셨군요... 하지만 오늘은 가변형을 쓸 수 있게 되었습니다. TS는 작동하는 복잡한 유형을 작성할 만큼 충분히 성숙했습니다. 그래서 나는 시간을 내어 람다의 카레, concat , 작성 및 파이프 유형을

이제 ts-toolbelt 와 함께 제공됩니다.

그러나 이 제안은 일반적인 튜플 조작을 훨씬 쉽게 만드는 훌륭한 구문 설탕입니다.

이미 medium.com에 있나요? URL?

Medium에 원본 기사가 있지만 보너스는 포함되어 있지 않고 repo에 있습니다. 또한 작곡, 파이프 및 카레 만들기에 필요한 모든 작은 도구를 만든 방법도 설명합니다.

@pirix-gh 그러나 이것은 이 제안에서와 같이 가변 제네릭이 아닙니다.

declare function m<...T>(): T

m<number, string>() // [number, string]

@goodmind 예, 그렇지 않습니다. 더 에뮬레이트됩니다. 따라서 다음과 같이 ... 에뮬레이트할 수 있습니다.

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

와 같다:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

그 동안 이 제안을 기다리는 동안 :hourglass_flowing_sand:

@pirix-gh와 같은 랩핑 기능을 도와 주시겠습니까?

type fn = <T>(arg: () => T) => T
let test1: fn
let res1 = test1(() => true) // boolean

type fnWrap = (...arg: Parameters<fn>) => ReturnType<fn>
let test2: fnWrap
let res2 = test2(() => true) // {}

작성 방법을 사용하려고 했지만 실패했습니다. 적절한 방법을 제안해 주시겠습니까?

이것은 제네릭에 의존하는 fn 의 매개변수/반환을 추출할 때 TS가 가장 가까운 유형으로 추론하기 때문에 발생합니다(이 경우 Tany ). 따라서 현재로서는 방법이 없습니다. 우리의 최선의 희망은 이 제안이 https://github.com/Microsoft/TypeScript/pull/30215 와 결합될 때까지 기다리는 것입니다

또는 다음과 같은 방식으로 제네릭을 보존/이동하는 방법을 찾을 수 있습니다.

declare function ideal<...T>(a: T[0], b: T[1], c: T[2]): T

ideal('a', 1, {}) // T = ['a', 1, {}]

이런 식으로 조각에서 fn 를 재구성합니다. 오늘 빠진 부분은 @goodmind가 지적한 것과 같은 일반적인 부분입니다.

@pirix-gh 내가 틀리지 않았다면, 당신은 당신이 거기에있는 것을 달성하기 위해 이것을 할 수 있습니다 :

declare function MyFunction<A, B, C, Args extends [A, B, C]>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@ClickerMonkey 정확히는 아닙니다. 제가 제안한 것이 무제한의 인수에 대해 작동하기 때문입니다. 그러나 아마도 우리는 당신이 제안한 것으로 이것을 할 수도 있습니다 (나는 제안에서 그것을 보지 못했습니다).

declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args

const a = MyFunction(1, 'hello', true);
// typeof a = [number, string, boolean]

@pirix-gh 예제의 A , BC 유형 인수는 사용되지 않습니다.

-declare function MyFunction<A, B, C, ...Args>(...[a, b, c]: Args): Args
+declare function MyFunction<...Args>(...[a, b, c]: Args): Args

가변 유형이 구현된 경우에도 앞의 두 예제는 컴파일 오류를 생성할 수 있습니다. 세 개의 인수만 원할 경우 가변 유형의 요점은 무엇입니까?

귀하의 예는 가변성이 필요한 이유를 보여주어야 합니다. 기존 TS 코드로 수행할 수 있다면 원인에 전혀 도움이 되지 않습니다.

@goodmind 예, 그렇지 않습니다. 더 에뮬레이트됩니다. 따라서 다음과 같이 ... 에뮬레이트할 수 있습니다.

declare function m<T extends any[], U extends any[]>(): Concat<T, U>

m<[number, string], [object, any]>() // [number, string, object, any]

와 같다:

declare function m<...T, ...U>(): [...T, ...U]

m<number, string, object, any>() // [number, string, object, any]

그동안 이 제안을 기다리면서 ⏳

Concat<> 어디서 구하셨나요?

편집: 소스 코드를 찾지 않아도 됩니다.

@pirix-gh 그래서 나는 당신의 제안으로 이것을 시도했지만 그것을 알아낼 수 없었습니다.

~문제는 클래스의 ctor 매개변수를 확장하려고 하는데 유형의 배열이 있지만 ctor 매개변수에 대해 이를 확산할 수 없다는 점에서 작동한다는 것입니다.~

Class Test {
  constructor(x: number, y: string) {}
}
let ExtendedClass = extendCtor<[number, string], [number]>(Test);

let instance = new ExtendedClass(1, '22', 2);

업데이트: ctor 함수에서 스프레드를 사용하여 작동하는 것도 신경쓰지 마십시오.

솔루션 의 링크 는 다음과 같습니다.

유일한 문제는 TS 거의 매번
이것이 TypeScript가 말하는 Type instantiation is excessively deep and possibly infinite.ts(2589)

업데이트 2:
나는 처음에 새로운 유형을 넣어 그것을 달성했지만 여전히 이러한 유형을 병합할 수 있으면 좋을 것입니다.

// ...
type CtorArgs<T, X> = T extends (new (...args: infer U) => any) ? [...U, X] : never;
// To be used as CtorArgs<typeof Test, string>
// ...
let instance = new MyClass1('22', 2, 'check');

반대로:

let MyClass1 = extendClass<typeof Test, string>(Test);

let instance = new MyClass1('check', '22', 2);

최종 솔루션에 대한 링크 .

내가 올바르게 이해한다면 Object.assign 는 가변 인수를 완전히 지원하기 위해 다음과 같이 선언될 수 있습니다.

type Assign<T, U extends any[]> = {
  0: T;
  1: ((...t: U) => any) extends ((head: infer Head, ...tail: infer Tail) => any)
    ? Assign<Omit<T, keyof Head> & Head, Tail>
    : never;
}[U['length'] extends 0 ? 0 : 1]

interface ObjectConstructor {
  assign<T, U extends any[]>(target: T, ...source: U): Assign<T, U>
}

TypeScript의 lib.d.ts 에서 다른 방식으로 선언된 이유가 있습니까?

재귀 조건부 유형이 지원되지 않기 때문에 사용하지 않습니다(이에 대한 논의는 #26980을 참조하거나 이 주석은 그렇게 하지 말라고 알려줍니다). 현재 교차 반환 유형을 사용하려는 경우 #28323이 있습니다.

@jcalz Minus 유형이 실제로 작동하는지 보여주는 무거운 테스트 를 만들었습니다. 4초 이내에 Minus 216000회 수행합니다. 이것은 TS가 재귀 유형을 매우 잘 처리할 수 있음을 보여줍니다. 그러나 이것은 아주 최근의 일입니다.

왜요? Anders :tada: (https://github.com/microsoft/TypeScript/pull/30769) 덕분입니다. 그는 조건부 유형에서 인덱싱된 조건(예: 스위치)으로 전환할 수 있도록 허용했습니다. 그리고 사실 ts-toolbelt의 경우 성능이 6배 향상되었습니다. 그에게 많은 감사를 드립니다.

따라서 기술적으로 ts-toolbelt를 사용하여 @kimamula 의 유형을 안전하게 다시 작성할 수 있습니다. 복잡성은 O(n)을 따릅니다.

import {O, I, T} from 'ts-toolbelt'

// It works with the same principles `Minus` uses
type Assign<O extends object, Os extends object[], I extends I.Iteration = I.IterationOf<'0'>> = {
    0: Assign<O.Merge<Os[I.Pos<I>], O>, Os, I.Next<I>>
    1: O
}[
    I.Pos<I> extends T.Length<Os>  
    ? 1
    : 0
]

type test0 = Assign<{i: number}, [
    {a: '1', b: '0'},
    {a: '2'},
    {a: '3', c: '4'},
]>

lib는 또한 TypeScript에서 오버플로를 방지하는 Iteration 재귀를 안전하게 만듭니다. 즉, I40 를 초과하면 오버플로되고 Pos<I> number 와 같습니다. 따라서 재귀를 안전하게 중지합니다.

내가 작성한 유사한 재귀 유형( Curry )이 Ramda와 함께 제공되며 잘 작동하는 것 같습니다.

그건 그렇고, 나는 당신의 모든 좋은 조언에 대해 프로젝트 페이지에서 (@jcalz) 감사합니다.

#5453이 이 토론을 하기에 가장 좋은 장소인지 잘 모르겠습니다... #26980에서 이에 대해 이야기해야 할까요? 아니면 더 정식 위치가 있을까요? 어떤 경우에는 내가 아마도 타이프의 후속 릴리스에 내파하지 않을있는이 작업을 수행하는 공식 및 지원 방법을 가지고 싶어요. 기준 테스트에 포함된 것이므로 고장나면 고칠 것입니다. 성능이 좋은 것으로 테스트되었더라도 @ahejlsberg와 같은 사람의 공식적인 언급 없이는 모든 프로덕션 환경에서 이 작업을 수행하는 것이 조심스럽습니다.

기준 테스트에 포함된 것이므로 고장나면 고칠 것입니다.

나는 우리가 내부적으로 꽤 가까운 것을 사용하고 있다고 생각합니다.

@weswigham 조밀한 적인지 보여줄 수 있습니까? 내가 걱정하는 것은 형식이다.

type Foo<T> = { a: Foo<Bar<T>>, b: Baz }[Qux<T> extends Quux ? "a" : "b" ]

또는 내가 본 변형 중 하나. 내가 놓치고 있는 것이 있고 이것이 일종의 청신호를 받았다면 누군가 나에게 알려주십시오(그리고 그것을 사용하는 방법을 가르쳐 주세요!).

오, 공평합니다. 그런 점에서 다릅니다. 네. 나는 "유형을 선택하기 위해 즉시 인덱싱된 객체" 패턴을 말하고 우리가 _that_을 가지고 있다는 것을 깨달았습니다.

질문이 있습니다.

여기에 제안된 내용 중 얼마나 많은 것이 여전히 관련성이 있습니까? 이 문제는 4년 전에 열렸고 그 이후로 많은 것이 변한 것 같은 느낌이 듭니다.

여기 내 댓글에서,
https://github.com/microsoft/TypeScript/issues/33778#issuecomment -537877613

나는 말했다,

TL;DR, 튜플 유형, 나머지 인수, 매핑된 배열 유형, 나머지가 아닌 인수에 대한 튜플 추론, 재귀 유형 별칭 = 변수 유형 인수 지원이 실제로 필요하지 않음

하지만 기존 도구로 단순히 활성화할 수 없는 사용 사례가 있는 사람이 있는지 궁금합니다.

공식적으로 축복받은 Concat<T extends any[], U extends any[]> 버전을 얻을 때까지 이것은 여전히 ​​관련이 있습니다. 나는 곧 나올 재귀 유형 참조 기능 이 이것을 우리에게 제공한다고 생각하지 않지만 (권위 있게) 달리 말하면 기쁠 것입니다.

Concat<> 구현이 이미 있지 않습니까?

아니면 여기에서 핵심 문구가 "공식적으로 축복받은"입니까?

내 주장은 기본적으로 "공식적으로 축복받은" 것은 아니지만 현재 원하는 모든 것(또는 거의 모든 것?)을 할 수 있다는 것입니다.

하지만 "공식적인 축복"이 항상 선호되어야 한다고 생각합니다... 좋은 지적입니다. 나는 그 재귀 유형 별칭을 사용하는 데 너무 익숙합니다.

나는 일반적으로 이런 일을 할 때마다 현상 유지 남용이 필요로 하는 혼란스럽게 지정된 유형에 대해 무슨 일이 일어나고 있는지 (종종 후배) 팀원들에게 계속 설명할 필요가 없도록 실제적이고 우아한 구문을 선호합니다. 그 혼란은 내 조직에서 TypeScript를 복음화하는 능력 또는 최소한 이러한 사용에 해를 끼칩니다.

이것에 큰 👍!

이 기능은 매우 중요합니다.

@아무튼스텝

내 주장은 기본적으로 현재 원하는 모든 것(또는 거의 모든 것?)을 할 수 있다는 것입니다.

여러 단일 매개변수와 함께 스프레드 매개변수를 입력하는 것이 오늘날 TS에서 쉽게 달성할 수 있다는 증거가 없습니다.

@matthew-dean은 완전히 사실이 아닙니다. 다음은 어느 정도 달성할 수 있는 예 입니다.

내가 이해하는 한 TS는 가능한 한 많은 바닐라 JS 프로그램을 입력하려고 합니다. 다음은 퍼즐입니다.

const f = <T extends any[]>(...args: T): T => args;
const g = <T extends any[]>(...a: T): WhatExactly<T> => {
    return f(3, ...a, 4, ...a, 5);
}
g(1, 2);

[number, ...T, number, ...T, number] 보다 더 복잡하지 않은 유형이 있을 것으로 예상합니다. 버그를 악용하는 이상한 코드를 20줄 작성해야 하는 경우 마지막 줄에 적절한 반환 유형이 있는지 확인하면 이 문제가 해결되지 않습니다.

@polkovnikov-ph 이 유형은 현재 [number, ...any[]] 로 추정되며 이는 도움이 되지 않습니다.

또한 C++가 이미 Head<>Cons<> 를 모두 거쳤기 때문에 C++처럼 15년 동안 준수 할 필요가 없습니다 Head<> 매우 편리하고 깔끔한 가변형 템플릿 구문을 고안했습니다. 우리는 (수백 년의) 개발자 시간을 절약할 수 있고 거기에서 가장 좋은 부분만 가져갈 수 있습니다.

예를 들어 variadic 유형은 C++에서 다른 종류 를 가지므로 TS에서 extends any[] 유형과 달리 유형이 예상되는 곳에 가변 유형 변수를 사용할 수 없습니다. 이를 통해 C++는 줄임표 연산자로 묶인 일부 표현식 내부에 가변 유형 변수를 언급함으로써 튜플을 매핑/집핑할 수 있습니다. 이것은 매핑된 객체 유형의 거의 튜플 대안입니다.

type Somethify<...T> = [...Smth<T>]
type Test1 = Somethify<[1, 2]> // [Smth<1>, Smth<2>]

type Zip<...T, ...U> = [...[T, U]]
type Test2 = Zip<[1, 2], [3, 4]> // [[1, 3], [2, 4]]

type Flatten<...T extends any[]> = [......T]
type Test3 = Flatten<[[1, 2], [3, 4]]> // [1, 2, 3, 4]

extends any[] 대신 제안된 줄임표 구문이 예제에서 사용된 것은 미학적인 이유일 뿐만 아니라

type A<T> = any[]
type B<T extends any[]> = [...A<T>]
type C = B<[1, 2]>

이미 유효한 TS 프로그램입니다. C 는 매핑된 가변 유형이 생성하는 [any[], any[]] 대신 any[] 가 됩니다.

@DanielRosenwasser 이런 식으로 핑을

제쳐두고, 가변 종류의 부족이 유형 매개변수와 함께 사용할 수 없음을 의미하더라도 튜플 유형에 대한 유형 수준 확산 작업을 갖는 것만으로도 우리 팀에 큰 도움이 될 것입니다. 문제 영역에서 "일부 구조의 배열"은 매우 일반적입니다. 이 작업이 작동하면 작업이 크게 단순화됩니다.

type SharedValues = [S1, S2, S3];
type TupleOfSpecificKind = [V1, ...SharedValues, V2];

@sethfowler 표현하고 싶은 예시가 있다면 항상 도움이 됩니다. 그렇지 않으면 https://github.com/microsoft/TypeScript/issues/26113에 관심이 있을 수 있습니다.

@DanielRosenwasser 물론, 좀 더 구체적으로 만들 수 있습니다. 핵심은 생략하지만 상위 수준에서 우리 프로젝트는 원격 서버로 전송되는 그래픽 작업 및 기타 유사한 이벤트의 스트림을 생성하는 것으로 생각할 수 있습니다. 효율성을 위해 직렬화된 형식으로 직접 변환할 수 있는 형식으로 메모리에 이러한 작업을 표시해야 합니다. 이러한 이벤트의 유형은 다음과 같이 표시됩니다.

type OpLineSegment = [
  StrokeColor,
  FillColor,
  number,  // thickness
  number, number, number,  // X0, Y0, Z0
  number, number, number  // X1, Y1, Z1
];
type OpCircle = [
  StrokeColor,
  FillColor,
  number, number, number,  // X, Y, Z of center
  number // radius
];
type OpPolygon = (StrokeColor | FillColor | number)[];  // [StrokeColor, FillColor, repeated X, Y, Z]]
type OpFan = (StrokeColor | FillColor | number)[];  // StrokeColor, FillColor, repeated X, Y, Z up to 10x

우리는 이러한 유형을 다음과 같이 더 표현할 수 있기를 원합니다.

type Colors = [StrokeColor, FillColor];
type Vertex3D = [number, number, number];

type OpLineSegment = [...Colors, number /* thickness */, ...Vertex3D, ...Vertex3D];
type OpCircle = [...Colors, ...Vertex3D, number /* radius */];
type OpPolygon = [...Colors, ...Repeated<...Vertex3D>];
type OpFan = [...Colors, ...RepeatedUpToTimes<10, ...Vertex3D>];

우리는 이러한 명령을 엄청나게 많이 가지고 있기 때문에 유형 수준 확산이 있는 것만으로도 훨씬 더 유지 관리 가능한 코드가 생성될 것입니다. Repeated<>RepeatedUpToTimes<> (이 예에서는 튜플 유형의 재귀적으로 정의된 공용체로 평가됨)와 같은 유형 수준 함수를 작성할 수 있도록 가변 종류를 사용하면 훨씬 더 단순화할 수 있습니다.

튜플 유형의 유형 안전 연결(OP에서 논의됨)과 같은 것을 지원하는 것도 매우 유용할 것입니다. 위의 유형을 사용하려면 현재 단일 튜플 리터럴 표현식으로 전체 튜플을 구성해야 합니다. 지금 당장은 그것을 부분적으로 구성하고 함께 연결할 수 없습니다. 즉, 아래 작업은 오늘날 작동하지 않지만 실제로 작동하기를 바랍니다.

const colors: Colors = getColors();
const center: Vertex3D = getCenter();

// Doesn't work! Produces a homogenous array.
const circle1: OpCircle = [...colors, ...center, radius];

// Doesn't work; can't write this function today.
const circle2: OpCircle = concat(colors, center, radius);

// We need to do this today; it's quite painful with more complex tuple types.
const circle3: OpCircle = [colors[0], colors[1], center[0], center[1], center[2], radius];

이 예제가 도움이 되기를 바랍니다!

당신은 쉽게 쓸 수 Concat<> 종류 및 수 있도록 Concat3<> 사용 유형을 Concat<> .

그 다음에,

type OpCircle = Concat3<Colors, Vertex3D, [number] /* radius */>;

위에서 2,3,4,5,6 등에 대한 오버로드가 있는 concat 함수를 작성할 수 있습니다. 인수의 수.

튜플 튜플을 취하고 튜플을 연결하는 Concat<> impl을 작성하는 것도 가능합니다. var-arg Concat<> 유형입니다.


오늘 할 수 없는 일이 아닙니다. 재귀 유형과 도우미 함수를 작성해야 하는 경우에도 수행할 수 있습니다.

내가 vscode에서 이러한 재귀 유형을 사용할 때마다 TS는 CPU를 죽이고 싶어하거나 그냥 멈춥니다! 그것이 주요 문제입니다. TS가 이유없이 너무 무거워지고 있다고 느낍니다.

아마도 유형을 작성하는 사람들이 최적화하기에 충분하지 않습니까?

이 스레드에 많은 가치를 추가하지 않을 오래된 대화를 스팸하거나 드래그하고 싶지는 않지만 자바스크립트에서 재귀 조건부 유형의 계산이 비싸다는 말을 들은 기억이 있습니다( 이 스레드 에서 찾았습니다)

즉, TS가 이미 너무 많이 성장하여 더 나은 언어를 요구하는 것이 합리적이기 때문에 유형 시스템에 부스트를 제공할 수 있도록 다른 언어로 TS를 다시 작성해야 할 때입니다.

당신은 쉽게 쓸 수 Concat<> 종류 및 수 있도록 Concat3<> 사용 유형을 Concat<> .

설명하는 Concat<> 유형에 대한 구현을 제공해 주시겠습니까? Cons<> 를 작성하는 것은 쉽지만 Concat<> 는 (저에게는) 그리 쉬운 일이 아니며 여러분이 무엇을 구상하고 있는지 보고 싶습니다.

Concat3<> , Concat4<> 등과 관련하여 장기적으로 우리는 다양한 종류를 가질 것이기 때문에 이와 같은 수십 가지 변형을 작성할 필요가 없기를 바랍니다. 🙂 그러나 오늘날 좋은 구현이 가능하다면 합리적인 임시 조치가 될 것입니다.

두 개의 튜플을 정기적으로 연결하려면
https://github.com/AnyhowStep/ts-trampoline-test (대부분의 사람들이 필요하지 않은 매우 큰 튜플을 연결하기 위해 트램폴린을 사용함)

Concat3 은 그냥 Concat일 것입니다., 다>

VarArgConcat은,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

튜플이 비어 있지 않은 동안 VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

TuplesT가 비어 있으면 ResultT를 반환합니다.

물론 순진한 재귀는 적절한 길이의 튜플에서 최대 깊이 오류로 이어질 것입니다. 따라서 ts-toolbelt에서 재귀 기술을 사용하거나 원하는 깊이로 복사하여 붙여넣는 트램폴린을 사용하십시오.


즉 내가 사용에 연결 REPO Reverse<> 구현하기 Concat<> . 작업 중인 다른 프로젝트의 코드를 복사하여 붙여넣었습니다.

매우 유용한 기능이 될 것이라는 데 동의합니다.

T 유형이 있다고 가정해 보겠습니다.

type T = {
  tags: ["a", "b", "c"];
};

그리고 T["tags"] 튜플에 추가된 "d" 태그를 사용하여 새 유형을 만들고 싶습니다. 사용자는 처음에 다음과 같이 이 유틸리티( WithTag<NewTag, ApplyTo> )를 시도하고 만들 수 있습니다.

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
  tags: [Tag, ...Target["tags"]];
};

현재 이것을 시도하면 A rest element type must be an array type 오류가 발생합니다. 사용자는 string[]Array<string> 바꾸면 차이가 있다고 생각할 수 있지만 그렇지 않습니다. 조건 + never 도 사용하지 않습니다.

type WithTag<
  Tag extends string,
  Target extends {tags: string[]}
> = Target & {
- tags: [Tag, ...Target["tags"]];
+ tags: Target["tags"] extends string[] ? [Tag, ...Target["tags"]] : never;
};

놀이터 링크 : https://www.typescriptlang.org/play?#code/C4TwDgpgBA6glsAFgFQIYHMA8AoKU3pQQAewEAdgCYDOU1wATnOegDS76oPoTBGkUaUAN7AM1AFx1GzdAG0AugF9sAPigBeTt15QAZCI5j0kqHIKsoAOhtodwOQCJj1RwoUBubEq -ZQkKABJTUM8FyknVEdLRwAjaKhHAGM3Lx9sP3BoZBD4JAJMR0oEwNVfAHoAKkrcSqgAUWJIJLJKKAADZHaoYAB7KFjoXoAzHsRoYd6AGynegHdZHqyrWqhV4VWe8QjHKJj4mJSY4s9VlShK8qA

관련 문제: :

불행히도 이 전략은 나머지 매개변수에 대해서는 작동하지 않습니다. 이것들은 그냥 배열로 바뀝니다:

function i(a: number, b?: string, ...c: boolean[]): number {
}
let curried = curry(i, 12);
curried('foo', [true, false]);
curried([true, false]);

여기 카레: ...([string, boolean[]] | [boolean[]]) => number .
튜플의 마지막 요소가 배열인 튜플 나머지 매개변수가 있는 함수에 대한 특별한 경우가 있다면 이것이 지원될 수 있다고 생각합니다.
이 경우 함수 호출은 배열과 일치하는 올바른 유형의 추가 인수를 허용합니다.
그러나 그것은 가치가 있기에는 너무 복잡해 보입니다.

여기에는 두 가지 문제가 있습니다.

  1. 코드가 잘못되었습니다. curried() 는 배열을 허용하지 않습니다. 충전 c 와 나머지 매개 변수를 [true, false] 수행 할 수 curried('foo', ...[true, false]) 하지만이 제안에 타이프에 실패합니다. 어떤 경우에는 타이핑 솔루션을 제공하지 못할 수도 있지만 잘못된 사람을 제공하는 것은 권장하지 않습니다!
  2. 의도치 않게 선택적 매개변수와 나머지 매개변수를 결합하여 제안서에 버그가 있음을 밝혔습니다. curried() b 없이 호출할 수 없지만 c 로 호출할 수 있습니다 . 그렇게 하면 잘못된 행동을 하게 됩니다. TypeScript는 curried()(...items: [string, boolean[]] | [boolean[]]) 임을 알고 있지만 사실이 아닙니다 . JavaScript가 입력리스, 통과하므로 [true, false]c 으로 (우리는 상기 과제를 해결 가정) curried([true, false]) 설정되지 bundefined (또는 기본값) 및 c 에서 [true, false] , 그러나 btruec 에서 [false] !

다음 수정 사항을 제안합니다.

  1. 두 번째(그리고 더 쉬운) 문제의 경우 솔루션은 간단합니다. 나머지 매개변수가 있을 때 마지막 선택적 인수(예: [number, string, boolean[]] | [number, boolean[]] 에 대해 유니온 튜플을 추론하지 마십시오. 대신, 추론 [number, string, boolean[]] | [number] - 즉, 모든 선택적 항목과 휴식, 마지막 제외한 각 옵션 하나, 그리고 마지막으로 나머지가없는 것을 포함하여 전체 서명에 대해 하나의 케이스.
  2. 첫 번째 문제는 더 까다롭습니다. 이미 너무 복잡해서 가치가 있다고 생각한다고 말했습니다. 휴식 매개변수의 인기를 고려할 때 _그럴만한_ 가치가 있다고 생각하지만, 첫 번째 문제(승리! 😄) 때문에 _필요한_입니다. 나는 우리가 tuple-with-last-rest-array에 대한 인터페이스를 노출하는 것이 좋을 것이라고 생각합니다(나는 [t1, t2, t3, ...arr] 구문에 대해 생각합니다). 그러나 우리는 그럴 필요가 없습니다. 우리는 그것을 내부적으로 유지할 수 있습니다(하하, 여전히 IDE에서 유형을 표시하는 방법을 처리해야 합니다 😈).

그러나 모든 불만과 도발 끝에 훌륭한 제안! 감사합니다 👍 (당신을 진정시키기 위해 이것은 내가 GitHub에서 세 가지 이모티콘으로 응답한 첫 번째 문제입니다 - 👍, 🎉 및 ❤️).

이것은 현재 any https://github.com/angular/angular/issues/37264 를 사용해야 하는 Angular Injector에서 정말 유용할 것입니다.

이 예에서 A, B, C는 단일 ...A 가변 제네릭 유형으로 표시될 수 있습니다. 그러나 가변 제네릭의 각 요소가 다른 유형( Type )으로 묶이는 위치에 이것이 어떻게 매핑되는지 모르겠습니다. 아마도 도우미 유형으로? 아니면 구문이 ...Type<A> 와 같은 것을 허용해야 합니까?

export declare interface TypedFactoryProvider<T, A, B, C> {
    provide: Type<T | T[]> | InjectionToken<T | T[]>;
    multi?: boolean;
    useFactory: (a: A, b: B, c: C) => T;
    deps: [Type<A>, Type<B>, Type<C>];
}

(컨텍스트 :의 구현 Provider 인스턴스 주입 것이다 deps 순서대로 그 공장 기능에 고정 유형은 개발자가 주입 될 것입니다 무엇을 알고 무엇을 위해 있는지 확인합니다.).

이 작업이 완료되면 String.prototype.replace의 두 번째 매개변수를 업데이트하여 마침내 Typescript에 적절한 입력이 가능하도록 업데이트하십시오!

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter

@Griffork 얼마나 많은 캡처 그룹이 있는지 알아내기 위해 정규식을 구문 분석해야 한다는 것을 알고 있습니까?

이것은 현재 any angular/angular#37264 를 사용해야 하는 Angular Injector에서 정말 유용할 것입니다.

이 예에서 A, B, C는 단일 ...A 가변 제네릭 유형으로 표시될 수 있습니다. 그러나 가변 제네릭의 각 요소가 다른 유형( Type )으로 묶이는 위치에 이것이 어떻게 매핑되는지 모르겠습니다. 아마도 도우미 유형으로? 아니면 구문이 ...Type<A> 와 같은 것을 허용해야 합니까?

export declare interface TypedFactoryProvider<T, A, B, C> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (a: A, b: B, c: C) => T;
  deps: [Type<A>, Type<B>, Type<C>];
}

(컨텍스트 :의 구현 Provider 인스턴스 주입 것이다 deps 순서대로 그 공장 기능에 고정 유형은 개발자가 주입 될 것입니다 무엇을 알고 무엇을 위해 있는지 확인합니다.).

@AlexAegis

나는 그것이 다음과 같이 입력 될 것이라고 생각합니다.

export declare interface TypedFactoryProvider<T, ...P> {
  provide: Type<T | T[]> | InjectionToken<T | T[]>;
  multi?: boolean;
  useFactory: (...providers: ...P) => T;
  deps: [...Type<P>];
}

이 문제는 이제 TS 4.0용으로 예정된 #39094로 수정되었습니다.

이것이 4.0과 함께 나온다면 이제 4.0으로 이름을 붙일 이유가 생겼습니다 😃
이것은 정말 중요한 새로운 기능입니다 🎉

이것은 훌륭하다! "왼쪽"만 리터럴 문자열 유형에서 동일합니다.

@sandersn 이 구문이 RxJS 와 같은 것에서 어떻게 사용되는지 생각하려고 합니다. 여기서 pipe 메소드 매개변수는 서로 일종의 종속적입니다.

pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) 에서처럼. 지금은 하지 않는 방식으로 어떻게 입력하시겠습니까? (다양한 길이의 수십 줄 입력)

@gioragutt @ahejlsberg가 제출한 PR을 사용하여 이것이 효과가 있을 것이라고 생각하지만 틀릴 수 있습니다 😄

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = R extends readonly [infer U] ? [UnaryFunction<T, U>, ...PipeParams<R>] : [];

function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@tylorr 원형 타입 에러로 인해하지 않습니다 작동.

그러나 일반적인 해결 방법이 작동합니다 .

type Last<T extends readonly unknown[]> = T extends readonly [...infer _, infer U] ? U : undefined;

interface UnaryFunction<T, R> { (source: T): R; }

type PipeParams<T, R extends unknown[]> = {
    0: [],
    1: R extends readonly [infer U, ...infer V]
    ? [UnaryFunction<T, U>, ...PipeParams<U, V>]
    : never
}[R extends readonly [unknown] ? 1 : 0];

declare function pipe<T, R extends unknown[]>(...fns: PipeParams<T, R>): UnaryFunction<T, Last<R>>;

@isiahmeadows 그것은 나를 위해 작동하지 않는 것 같습니다. 😢
놀이터 예 .

나는 일에 더 가까운 것을 얻었지만 유형을 추론하지 않을 것입니다.
놀이터 예

나는 변해야만 했다
R extends readonly [unknown] ? 1 : 0
에게
R extends readonly [infer _, ...infer __] ? 1 : 0

이유를 모르겠다

@tylorr @treybrisbane 관련될 수 있음: https://github.com/microsoft/TypeScript/pull/39094#issuecomment -645730082

또한 두 경우 모두 해당 댓글이 있는 풀 리퀘스트에서 이를 공유하는 것이 좋습니다.

Variadic 튜플 유형은 언어에 대한 멋진 추가 기능입니다. 노력에 감사드립니다!

curry 와 같은 구성도 도움이 될 것 같습니다( 스테이징 플레이그라운드에서 테스트한 것뿐).

// curry with max. three nestable curried function calls (extendable)
declare function curry<T extends unknown[], R>(fn: (...ts: T) => R):
  <U extends unknown[]>(...args: SubTuple<U, T>) => ((...ts: T) => R) extends ((...args: [...U, ...infer V]) => R) ?
    V["length"] extends 0 ? R :
    <W extends unknown[]>(...args: SubTuple<W, V>) => ((...ts: V) => R) extends ((...args: [...W, ...infer X]) => R) ?
      X["length"] extends 0 ? R :
      <Y extends unknown[]>(...args: SubTuple<Y, X>) => ((...ts: X) => R) extends ((...args: [...Y, ...infer Z]) => R) ?
        Z["length"] extends 0 ? R : never
        : never
      : never
    : never

type SubTuple<T extends unknown[], U extends unknown[]> = {
  [K in keyof T]: Extract<keyof U, K> extends never ?
  never :
  T[K] extends U[Extract<keyof U, K>] ?
  T[K]
  : never
}

type T1 = SubTuple<[string], [string, number]> // [string]
type T2 = SubTuple<[string, number], [string]> // [string, never]

const fn = (a1: number, a2: string, a3: boolean) => 42

const curried31 = curry(fn)(3)("dlsajf")(true) // number
const curried32 = curry(fn)(3, "dlsajf")(true) // number
const curried33 = curry(fn)(3, "dlsajf", true) // number
const curried34 = curry(fn)(3, "dlsajf", "foo!11") // error

그러나 위의 카레에서는 일반 기능이 작동하지 않습니다.

이 PR이 이 특정 문제를 해결한다고 생각하지 않습니다.

PR을 사용하면 작동합니다.

function foo<T extends any[]>(a: [...T]) {
  console.log(a)
}

foo<[number, string]>([12, '13']);

그러나 이 문제는 내가 보는 한 이에 대한 구현을 보고 싶습니다.

function bar<...T>(...b: ...T) {
  console.log(b)
}

bar<number, string>(12, '13');

거기에 많은 꺾쇠 괄호가 있고 약간 중복되어 보입니다.

@AlexAegis 그런 "휴식 유형 매개 변수"에 많은 가치가 있는지 잘 모르겠습니다. 당신은 이미 이것을 할 수 있습니다:

declare function foo<T extends any[]>(...a: T): void;

foo(12, '13');  // Just have inference figure it out
foo<[number, string]>(12, '13');  // Expclitly, but no need to

추론이 알아낼 수 없는 드문 경우에 대괄호를 피할 수 있도록 완전히 새로운 개념(예: 나머지 유형 매개변수)을 원한다고 생각하지 마십시오.

@ahejlsberg 알겠습니다 . 일부 라이브러리(언급된 RxJS)가 이 기능을 제공하기 위해 해결 방법을 사용했기 때문에 질문했습니다. 그러나 그것은 유한합니다.

bar<T1>(t1: T1);
bar<T1, T2>(t1: T1, t2:T2);
bar<T1, T2, T3>(t1: T1, t2:T2, t3: T3, ...t: unknown) { ... }

그래서 이제 그들은 그것을 고수하거나 사용자가 대괄호를 입력하도록 합니다. 이는 획기적인 변경이며 직관적이지 않습니다.

이 예제를 사용한 이유는 여기에서 해당 튜플의 유형을 정의한 것이 간단하기 때문입니다. 여기 대괄호 하나, 저기 하나

foo<[number, string]>([12, '13']);

여기서 튜플이 외부에서 보면 나머지 매개변수를 참조한다는 것이 그렇게 분명하지 않습니다.

foo<[number, string]>(12, '13'); 

하지만 네 말대로 추론을 통해 알아낼 수 있다면 이러한 사소한 경우에는 사용자가 수정할 필요가 없습니다. 그러나 명시적으로 설정했는지 여부는 알 수 없으며, 이는 사용자에게 달려 있으므로 여전히 주요 변경 사항으로 간주됩니다. 그러나 그것은 lib의 관심사이지 이 변화의 관심사는 아닙니다.

즉, ... 구별되는 내부의 단일 배열인 나머지 매개변수가 외부에서 하나씩 정의되면 동일한 방식으로 일반화될 수 없다는 것이 이상합니다. 하나씩: 외부에는 단일 배열, 내부에는 ... 됩니다.

사소한 구문 불일치는 별도의 지원 비용이 실제로 가치가 없습니다.
친절한. TS가 계획할 때 종류를 사용하는 것이 올바른 디자인 결정이 될 것입니다.
나머지 매개변수에 대한 지원, 하지만 이제 더 많은
언어 개발자와 사용자 모두에게 혼란을 줍니다. 에 대한 솔루션이 필요했습니다.
이 문제와 Anders는 그 일을 매우 잘 피했습니다.
고수하여 복잡성 [...T] 대신 T . 오프 모자!

(이제 교차 유형을 통합하는 버그를 살펴볼 수 있습니까?
조건부 유형의 유추 변수는 맨 오른쪽 교차 유형을 반환합니다.
인수 또는 배열의 합집합이 합집합의 배열이 아닙니다. 우리는 여전히
유형 시스템에 주요 쇼스토퍼가 있습니다.)

2020년 6월 19일 금요일 10시 41분 Győri Sándor [email protected] 작성:

@ahejlsberg https://github.com/ahejlsberg 알겠습니다 . 때문에 물어봤다.
일부 라이브러리(언급된 RxJS)는 이를 제공하기 위해 해결 방법을 사용했습니다.
기능. 그러나 그것은 유한합니다.

술집(t1: T1);바(t1: T1, t2:T2);바(t1: T1, t2:T2, t3: T3, ...t: 불명) { ... }

이제 그들은 그것을 고수하거나 사용자가 대괄호를 입력하도록 합니다.
그렇게 직관적이지 않습니다.

이 예제를 사용한 이유는 여기에서 간단하기 때문입니다.
그 튜플의 유형을 정의했습니다. 여기 대괄호 하나, 저기 하나

foo<[숫자, 문자열]>([12, '13']);

여기서 튜플이 다음과 같은 경우 나머지 매개변수를 참조하는 것은 그리 분명하지 않습니다.
당신은 그것을 외부에서 봅니다

foo<[숫자, 문자열]>(12, '13');


당신이 언급되었기 때문에 이것을 받는 것입니다.
이 이메일에 직접 답장하고 GitHub에서 확인
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646490130 ,
또는 구독 취소
https://github.com/notifications/unsubscribe-auth/AAWYQIMTTB6JEPSQFUMMDTRXMJD5ANCNFSM4BTBQ7DQ
.

물론 나는 그의 능력에 가깝지 않지만 @ahejlsberg에 정중하게 동의하지
내 경험에 따르면, 타이프스크립트의 복잡성은 많은 (흥미롭고 확실히 유용한) 기능이 고유한 개념으로 특수한 경우에 사용된다는 사실에서 비롯됩니다 .

이 복잡성은 본질적으로 기능 수의 함수가 아닙니다!
대신, 언어는 이러한 특별한 경우를 사소하게 추론하거나 std(유형) 라이브러리에서 구현할 수 있는 더 크고 더 중요한 개념을 중심으로 설계될 수 있습니다.

가장 일반적인 개념은 물론 다른 모든 것이 파생될 수 있는 종속 유형을 완전히 구현하는 것이지만 그렇게까지 갈 필요는 없습니다.
C++와 Rust가 보여주듯이 몇 가지 대규모의 일관된 개념은 수많은 기능을 무료로 제공합니다.
이것은 OCaml과 Haskell(F#?로 가정)이 값 수준에서 수행한 작업과 유사합니다. 바로 유형 수준입니다.

유형 수준 프로그래밍은 특정 기능을 제공하기 위해 고정되는 대신 언어에 맞게 설계된 한 두려울 것이 없습니다.
C++ 14/17의 기능은 순전히 역사적 수하물로 인한 구문을 제외하고는 매우 직관적입니다.

가장 중요한 개념은 원래 디자인에 추가될 수 있습니다. 디자인 후
실수는 이미 이루어졌으며 엄청난 위험을 감수하지 않고 일관성을 추가할 수 없습니다.
백 비호환성. 나는 다음과 같이 언어 디자인에 대한 의심에 동의합니다.
전체(TS는 학계에서 설정한 표준과는 거리가 멀고 아무도 할 수 없습니다.
그것에 동의하지 않습니다). 버그와 모순이 많다.
수백만 개의 프로덕션 코드 베이스의 기초입니다. 단순한 사실
개발자는 언어에 유용한 추가 사항을 생각해 낼 수 있습니다.
실수로 버그를 수정하지 않고 내 겸손한 의견으로는 굉장합니다
그리고 존경받을 가치가 있습니다. TS는 여기에서 C++와 동일한 설계 복잡성을 가지고 있지만,
표현형 체계는 상황을 악화시킨다.

2020년 6월 19일 금요일 12:47 Bennett Piater [email protected]에서 다음과 같이 썼습니다.

물론 나는 그의 능력에 가깝지 않지만 정중하게 동의하지 않습니다.
@ahejlsberg https://github.com/ahejlsberg .
내 경험상 타이프스크립트의 복잡성은 대부분많은 (흥미롭고 유용한) 기능이자신의 개념으로 특수 케이스.

이 복잡성은 본질적으로 기능 수의 함수가 아닙니다.
그렇지만!
대신, 언어는 더 크고 더 포괄적으로 설계될 수 있습니다.
이러한 특별한 경우를 사소하게 추론할 수 있는 개념, 또는
std(유형) 라이브러리에서 구현됩니다.

가장 일반적인 개념은 물론 완전히 구현하는 것입니다.
다른 모든 것이 파생될 수 있는 종속 유형이지만
멀리 갈 필요가 없습니다:
C++와 Rust가 보여주듯이, 몇 가지 대규모,
일관된 개념은 수많은 기능을 무료로 제공합니다.
이것은 OCaml과 Haskell(F#이라고 가정함)이 수행한 작업과 유사합니다.
값 수준, 유형 수준에만 있습니다.

유형 수준 프로그래밍은
특정 언어를 제공하기 위해 고정되는 대신 언어로 설계된
특징.
C++ 14/17의 기능은 구문을 제외하고는 매우 직관적입니다.
순전히 역사적 수하물 때문입니다.


당신이 언급되었기 때문에 이것을 받는 것입니다.
이 이메일에 직접 답장하고 GitHub에서 확인
https://github.com/microsoft/TypeScript/issues/5453#issuecomment-646543896 ,
또는 구독 취소
https://github.com/notifications/unsubscribe-auth/AAWYQIMWYLGGCWPTDBZJR4TRXMX4RANCNFSM4BTBQ7DQ
.

@polkovnikov-ph 당면한 문제에 동의하게 되어 기쁩니다 :)

솔루션에 관해서는 더 신중하게 설계된 유형 시스템으로 점진적으로 이동하는 것을 고려할 가치가 있다고 생각합니다. 메이저 버전은 결국 문제이며 대안은 클러스터에 들어가는 것입니다. * * C++ 20 - 제거할 수 없는 이전 시도의 2개 레이어 위에 훨씬 더 멋지게 디자인된 기능을 추가하려는 훌륭한 시도입니다. 이미 결정적으로 구문 분석할 수 없는 구문입니다.

이 모든 것은 이 스레드와 관련이 없으며 여기 에서 논의 중입니다. 그래서 나는 솔직히 말하려고 노력할 것입니다.

학계에서 하위 유형 지정에 대한 올바른 접근 방식을 파악하는 데 수십 년이 걸렸습니다. mlsub 유형 시스템은 TypeScript가 처음 출시된 후 불과 6년 전에 만들어졌습니다. 클래스, 인터페이스, 통합 및 교차 유형에 대한 기초가 될 수 있으며, 이는 가장 중요한 개념입니다.

그러나 조건부 유형이 있음을 기억하십시오. 나는 그들에게 공식적인 의미론을 제공하거나 진행/보존 증명이 있는 조건부 유형이 있는 최소 유형 ​​시스템을 설명하는 논문을 알지 못합니다. 나는 과학자들이 실패한 시도를 인쇄하는 것을 여전히 부끄러워하는 것과 관련이 있을 수 있다고 생각합니다. 당신의 제안이 그러한 주요 호환되지 않는 버전이 2040년대에 만들어질 것이라고 가정한다면 학계가 조건부 유형에 익숙해지면 동의할 수 있습니다.

그렇지 않으면 "신중하게 설계된 유형 시스템"이 언어에서 조건부 유형을 제거해야 하며, 이를 대체하기 위해 선택된 대안을 사용하기 위해 확실히 유형의 60%를 변환하는 작업을 하는 사람은 아무도 없다고 생각합니다. (그런 다음 몇 번 더 수행하십시오. 유일한 문제가 아니기 때문입니다.)

실행 가능한 유일한 솔루션은 어떻게든 TS와 유사한 별도의 프로그래밍 언어를 만들고 어떻게든(코드를 작성하는 것이 더 즐겁기 때문에) 개발자가 이를 사용하도록 유인하는 것뿐입니다. 라이언 된 매우 보컬 이전에 TS 개선을 위해이 방법을 추천.

이 페이지가 도움이 되었나요?
0 / 5 - 0 등급