Typescript: Предложение: Variadic Kinds - Придавайте определенные типы вариативным функциям

Созданный на 29 окт. 2015  ·  265Комментарии  ·  Источник: microsoft/TypeScript

Вариативные виды

Предоставление конкретных типов функциям с переменным числом переменных

Это предложение позволяет Typescript присваивать типы функциям более высокого порядка, которые принимают переменное количество параметров.
К таким функциям относятся concat , apply , curry , compose и почти любой декоратор, который является оболочкой для функции.
Ожидается, что в Javascript эти функции высшего порядка будут принимать функции с переменным числом аргументов в качестве аргументов.
Со стандартами ES2015 и ES2017 это использование станет еще более распространенным, поскольку программисты начнут использовать аргументы распространения и параметры отдыха как для массивов, так и для объектов.
В этом предложении эти варианты использования рассматриваются с помощью единой, очень общей стратегии типизации, основанной на типах более высокого порядка.

Это предложение полностью или частично решит несколько вопросов, в том числе:

  1. # 5331 - Кортежи как типы для отдыха ... аргументы
  2. # 4130 - Компилятор неправильно сообщает о несоответствии сигнатуры параметра / цели вызова при использовании оператора спреда
  3. # 4988 - Кортежи должны быть клонированы с помощью Array.prototype.slice ()
  4. # 1773 - Вариативные дженерики?
  5. # 3870 - Остальные типы в дженериках для типов пересечений.
  6. # 212 - bind, call и apply нетипизированы (требуются типы this-function # 3694).
  7. # 1024 - Типизировано ... остальные параметры с дженериками

Я буду обновлять это предложение в своем ответвлении на Typescript-Handbook: sandersn / TypeScript-Handbook @ 76f5a75868de3fb1ad4dbed5db437a8ab61a2698
У меня есть незавершенная реализация на sandersn / TypeScript @ f3c327aef22f6251532309ba046874133c32f4c7, в которой в настоящее время реализованы простые части предложения.
Он заменяет часть 2 моего предыдущего предложения, № 5296.
Изменить: добавлен раздел о возможности назначения. Я уже не уверен, что он строго заменяет # 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];
}

Переменные типа Variadic, как и переменные типа, довольно непрозрачны.
У них есть одна операция, в отличие от переменных типа.
Их можно объединять с другими видами или с собственными кортежами.
Синтаксис, используемый для этого, идентичен синтаксису распределения кортежей, но в расположении аннотации типа:

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);

Семантика

Переменная вариативного типа представляет собой кортеж любой длины.
Поскольку он представляет собой набор типов, мы используем термин «вид» для его обозначения после его использования в теории типов.
Поскольку набор типов, которые он представляет, представляет собой кортежи любой длины, мы квалифицируем «вид» как «вариативный».

Следовательно, объявление переменной типа вариативного кортежа позволяет ей принимать любой _один_ кортежный тип.
Как и переменные типа, переменные вида могут быть объявлены только как параметры для функций, классов и т. Д., Что затем позволяет использовать их внутри тела:

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: [число, число, строка] Допускается . 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 должно быть истинным, чтобы тип был правильным.
Я не уверен, действительно ли этот код должен быть разрешен.

Семантика классов и интерфейсов

Семантика одинакова для классов и интерфейсов.

ЗАДАЧИ: Вероятно, есть некоторые недостатки в семантике, специфичные для классов.

Возможность присвоения кортежей и списков параметров

Виды кортежей могут использоваться для присвоения типа остальным аргументам функций внутри их области видимости:

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 .
Я думаю, что это можно было бы поддержать, если бы был особый случай для функций с параметром rest кортежа, где последний элемент кортежа является массивом.
В этом случае вызов функции позволит дополнительным аргументам правильного типа соответствовать массиву.
Однако это кажется слишком сложным, чтобы иметь смысл.

Расширения к другим частям машинописного текста

  1. Typescript не позволяет пользователям писать пустой тип кортежа.
    Однако это предложение требует, чтобы переменные виды были привязаны к пустому кортежу.
    Таким образом, Typescript должен будет поддерживать пустые кортежи, даже если только внутренне.

    Примеры

Большинство этих примеров возможны как функции с фиксированным аргументом в текущем TypeScript, но с этим предложением они могут быть записаны как переменные.
Некоторые из них, такие как cons и concat , могут быть написаны для однородных массивов в текущем 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);

ЗАДАЧА: Может ли 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. Сохраняется ли история о назначении кортежа списку параметров? Особенно шатко идет с необязательными и остальными параметрами.
  2. Будет ли выведенный тип объединением кортежей, как в случае с необязательным параметром? Поскольку bind , call и apply - это методы, определенные в Function, их аргументы типа должны быть привязаны во время создания функции, а не на сайте вызова bind (Например). Но это означает, что функции с перегрузками не могут принимать или возвращать типы, специфичные для их аргументов - они должны быть объединением типов перегрузки. Кроме того, у Function нет конструктора, который напрямую определяет аргументы типа, поэтому на самом деле нет никакого способа предоставить правильные типы для bind и др. ЗАДАЧИ: Добавьте сюда пример. Обратите внимание, что эта проблема не обязательно уникальна для вариативных функций.
  3. Должны ли остальные параметры иметь специальный регистр, чтобы сохранить красивый синтаксис вызова, даже если они сгенерированы из типа кортежа? (В этом предложении функции, типизированные кортежем, должны передавать массивы своим остальным параметрам, они не могут иметь дополнительных параметров.)
Fix Available In Discussion Suggestion

Самый полезный комментарий

Эта проблема теперь исправлена ​​номером 39094, запланированным на TS 4.0.

Все 265 Комментарий

+1, это действительно полезно для функционального программирования на TypeScript! Как это будет работать с необязательными или остальными аргументами? Более конкретно, может ли функция compose использоваться для функций с остальными аргументами или необязательными аргументами?

Хорошая точка зрения. Я думаю, вы могли бы назначить наименьший допустимый тип кортежа функции optional-param, поскольку кортеж - это просто объекты, которые допускают дополнительные члены. Но это не идеально. Я посмотрю, смогу ли я разобраться в примере compose а затем обновлю предложение.

На самом деле типы союзов, вероятно, подойдут лучше. Что-то вроде

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. Мы попытались реализовать API типа составления в TypeScript, но нам нужно обойти некоторые ограничения, указанные в этом предложении. Это определенно решило бы некоторые из этих проблем!

Однако кажется, что иногда вы можете захотеть «объединить» такие типы кортежей вместо того, чтобы сохранять их, особенно с чем-то вроде compose. Например:

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

Кроме того, во многих ваших примерах вы использовали примитивы. Как бы вы увидели, как работает что-то более сложное, особенно если есть конфликты?

К сожалению, это предложение как есть не касается # 3870 или композиции типов, поскольку единственный оператор композиции для типов кортежей - это [T,...U] . Вы также можете записать это как T + ...U (что более указывает на то, что происходит с типами), но # 3870 и вашей библиотеке композиции типов требуется T & ...U . Я думаю, что это возможно, но сначала мне нужно понять @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] уже делает это для типов).

@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

Я думаю, что реальные библиотеки (например, реактивные расширения, с которыми связан

В приведенных выше примерах сложнее всего вывести curry - вам нужно пропустить f: (...args:[...T,...U]) => V , вывести ...ts:...T , затем вернуться и установить ...U на то, что осталось после использования ...T из параметров f .

Я начал создавать его прототип (sandersn / TypeScript @ 1d5725d), но еще не дошел до этого. Есть идеи, если это сработает?

Я бы ошибся в том, чтобы запретить что-либо, где семантика не ясна (например, повторяющиеся выводы для одного и того же параметра распространенного типа). Это также снимает мою обеспокоенность выше.

Я не могу придумать хороший механизм для набора карри. Как вы указываете, вы должны пропустить список параметров первой функции, которая потребляет аргумент ...T а затем посмотреть, что осталось. Должна быть какая-то политика, чтобы отложить выводы для параметра расширенного типа, если он не является окончательным в своем списке. Это может запутаться.

Тем не менее, я думаю, что это стоит попробовать. Эта функция пользуется большим спросом.

Я думаю, вам придется пропустить несколько видов кортежей, которые встречаются в одном контексте (например, верхнего уровня, например (...T,string,...U) => V или конкатенированных, например, [...T,...U,...T] ). Затем вы можете выполнить несколько проходов для пропущенных видов, исключив уже выведенные виды и повторно пропустив виды, которые все еще остаются неоднозначными. Если в какой-то момент для вывода недоступен какой-либо один вид, остановитесь и верните ошибку.

Так что да. Сложный.

Возможно, вам удастся почерпнуть вдохновение из подобной проблемы. На самом деле это несколько похоже на проблему вывода на объединение или пересечение. При выводе типа объединения, который включает параметр типа, который является членом контекста вывода, как в function f<T>(x: T | string[]) , вы не знаете, следует ли делать вывод о T. Предполагаемое проявление типа объединения могло быть string[] . Таким образом, машинописный текст сначала выводит все остальные составляющие, а затем, если выводы не были сделаны, выводит T.

В случае пересечения это еще сложнее, потому что вам, возможно, придется разделить тип аргумента на разные составляющие пересечения. TypeScript вообще не делает выводов о типах пересечений.

Что, если бы вы разрешили распространение кортежа только в том случае, если это последний тип в своей последовательности? Итак, [string, ...T] будет разрешено, а [...T, string] нет?

Если я правильно понимаю, это действительно решит историю миксинов в TypeScript. Прав ли я в этом понимании?

Может быть. Вы можете привести пример? Я плохо разбираюсь в паттернах миксинов.

Синтаксис переменной переменного типа ... T, где T - идентификатор, который по соглашению представляет собой одну заглавную букву, или T, за которым следует идентификатор PascalCase.

Можем ли мы оставить дело с идентификатором параметра типа на усмотрение разработчика?

@ aleksey-bykov +1. Я не вижу причин, по которым этого не должно быть.

Разработчики с опытом работы на Haskell это оценят.

Извините, это предложение можно разобрать неоднозначно. Я имел в виду «или» для тщательного анализа: «по соглашению (одна заглавная буква || T, за которой следует идентификатор PascalCase)». Я не предлагаю ограничивать регистр идентификаторов, просто указываю на соглашение.

Тем не менее, чего бы это ни стоило, у меня есть опыт работы с 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 Мой assign в TypeScript было бы тривиально изменить с помощью этого.

Несмотря на то что:

  1. Пересечение было бы похоже на конкатенацию, хотя это больше похоже на конкатенацию словарей, чем на конкатенацию списков. Вариативные типы могут быть реализованы там поверх существующего оборудования.
  2. Союз был бы похож на перекресток, за исключением того, что он сохраняет только общие части. Еще раз, вариативные типы могут быть реализованы поверх существующего оборудования.

@isiahmeadows Пересечение, как правило, не является объединением словарей. Это верно только для пересечения типов объектов, но не, например, для пересечения объединений. Объединения - это не то же самое, что просто брать общие свойства объектов. Эти двое лучше характеризуются набором присущих им ценностей.

@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 notion:

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; }

тип a - number[] (по типу возвращаемого значения), такой же, как он был определен в подписи.

@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;

Это на 1 символ короче, чем ...T и (на мой взгляд) создает меньше визуального шума.

@ 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]
}

Обновление теперь выглядит лучше, ИМО. Списки для представления типов звучат очень красиво, но даже типизированные Лиспы не заходят так далеко (гомоиконные типы, кто-нибудь?: 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);

Я думаю, вы захотите вывести [number, number] для T, учитывая намерение вашего предложения, как я его понимаю. Но из-за наличия в строке 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]

Кстати, можем ли мы сделать вывод, что x => x имеет тип { <T>(x: T): T; } ?

@Igorbek Я думаю, что ваше предложение о параметрах типа изготовления (по крайней мере, интуитивно, независимо от того, как оно реализовано) - правильный способ сделать это. Вы можете вывести последовательность типов для T , где каждый элемент в последовательности имеет индекс и список кандидатов (это альтернативный способ реализации того, что вы упомянули).

Однако я хотел сказать, что я не думаю, что это могло бы произойти естественным образом, если бы вы просто перепрофилировали список кандидатов на вывод как предполагаемый кортеж. Для того, чтобы все произошло правильно, потребуется явная механика.

Что касается вашей точки зрения о { <T>(x: T): T; } , это не очень хорошо подходит для ввода таких вещей, как x => foo(x) где foo - некоторая функция. Вам нужно знать тип x чтобы выполнить разрешение перегрузки для foo .

Небольшой шаг в сторону от битвы с правилами вывода для проверки типов.
У меня есть комментарий / предложение по синтаксису. Думаю, есть два последовательных, но взаимоисключающих варианта:

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 должно быть разрешено быть только типом массива или параметром типа rest, потому что он должен иметь переменную длину. Мне также нравится концепция параметра типа отдыха, представляющего собой абстрактную последовательность типов, не связанную с чем-то, что уже существует в системе типов.

Моя философия кортежей заключается в том, что они представляют собой подмножество значений массива, длина которых известна. Эти значения массива являются реальными объектами времени выполнения. Мне не нравится идея использовать их как своего рода устройство системы типов для представления абстрактной последовательности типов (например, последовательности параметров в подписи). Но разрешено ли вам распространять параметр типа отдыха в кортеж - это другая история.

Мне нравится предложение кортежа, потому что оно более мощное и решает больше вариантов использования, также очень интуитивно понятно, что я могу распространять кортеж в качестве параметра отдыха, потому что кортежи - это просто массивы, а при вызове функции с параметром отдыха я могу распространять массив. Тогда система типов будет лучше соответствовать моему пониманию кода.

@JsonFreeman в вашем случае foo вернет [string, number, number] поскольку это будет выведено из ...args , предполагаемый тип 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 , существует 2 возможных экземпляра подписи. А именно (arg1: number, arg2: string) => [number, string] а также (arg1: [number, string]) => [number, string] (с использованием неявной интерпретации кортежа для примера).

Другая странность неявной интерпретации кортежа заключается в следующем: допустим, у вас есть параметр типа покоя T, экземпляр которого создается в number, string . Теперь предположим, что вы передаете их как аргументы типа Foo<T> . Это следует интерпретировать как Foo<[number, string]> тогда как Foo<...T> - это 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)

Первый пример имеет прямое отношение к функции # apply (и может быть
error), а вторая - неочевидная ошибка, которая не может быть скомпилирована,
и нетривиально обнаружить с помощью Intellisense.

Вс, 28 февраля 2016 г., 03:04 Джейсон Фриман [email protected] написал:

Другая странность в неявной интерпретации кортежей заключается в следующем: say
у вас есть параметр типа отдыха T, который создается для числа, строки.
Теперь предположим, что вы передаете их как аргументы типа, Foo. Это должно быть
интерпретируется как Foo <[число, строка]>, тогда как Foo <... T> - это Foo строка>?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -189817561
.

@JsonFreeman

Однако ваш третий пример тоже проблематичен. Для такой последовательности, как number, string , существует 2 возможных экземпляра подписи. А именно (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<[number, string]> тогда как Foo<...T> - это 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> . Как я уже сказал, я не считаю типы кортежей подходящим устройством для использования с последовательностями типов произвольной длины в системе типов. Я до сих пор не уверен, что это требуется для той выразительности, которую хотят люди.

Я согласен, что это могло бы быть более выразительно, но наличие оператора 'spread' в позиции параметров типа ограничит нас возможностью перехватывать аргументы отдыха только один раз, так же как мы не можем иметь параметры отдыха дважды. Таким образом, учитывая <...T> и <A, B, C> , T поймает их как [A, B, C] . И мы не сможем выразить <...T, ...U> как это будет двусмысленно - [A, B, C], [] или [A, B], [C] или ... и т. Д.

Скажем, я хотел выразить функцию со следующим поведением:

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

Хорошо, теперь я понимаю, как вы об этом думаете. Похоже, то, что вы предлагаете, на самом деле отличается от того, что я думал. Вместо добавления новой конструкции для захвата последовательности параметров типа вы просто хотите, чтобы типы кортежей были распространяемыми, потому что они уже представляют последовательность типов. Таким образом можно более прозрачно передавать несколько кортежей разной длины.

В javascript это больше похоже на function foo([...rest]) { } вместо function foo(...rest) { } .

Теперь это имеет для меня больше смысла, спасибо за объяснение. Я считаю, что это разумный подход.

@JsonFreeman Совершенно верно !

@JsonFreeman Вопрос: почему [1, 2] должно удовлетворять [number] ? Мне это кажется очень странным. Это действительно сработало бы очень удивительно. Это совсем небезопасно.

Не то чтобы я был против использования кортежей для вариативных типов (я тоже нейтрален, если честно).

@isiahmeadows. Каким образом [1, 2] нельзя заменить на [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.

В пн, 29 февраля 2016, 13:45 Анатолий Рессин [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))

Это очень распространено в функциональном программировании. И опуская последний
аргумент довольно распространен.

В пн, 29 февраля 2016, 13:52 Анатолий Рессин [email protected] написал:

@isiahmeadows https://github.com/isiahmeadows Я бы сказал, что каррирование
на основе aruments.length очень нестабилен и подвержен ошибкам во время выполнения.
Настоящее каррирование не требует дополнительных аргументов:

var plus = 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 (работа со списком
списков), вы, вероятно, захотите карри.

В понедельник, 29 февраля 2016 г., 13:59 Исайя Медоуз [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))

Это очень распространено в функциональном программировании. И опуская последний
аргумент довольно распространен.

В пн, 29 фев 2016, 13:52 Анатолий Рессин [email protected]
написал:

@isiahmeadows https://github.com/isiahmeadows Я бы сказал, что каррирование
на основе aruments.length очень нестабилен и подвержен ошибкам во время выполнения.
Настоящее каррирование не требует дополнительных аргументов:

var plus = 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)

В пн, 29 февраля 2016 г., 18:47 Джейсон Фриман [email protected] написал:

Вопрос о том, удовлетворяет ли [1, 2] [number], можно задать
и дискуссии. Но при чем здесь функция расширяемых кортежей?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

И поэтому из практических соображений я бы предпочел, чтобы параметры покоя были похожи на
вариативные типы.

В пн, 29 февраля 2016 г., 19:00 Исайя Медоуз [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)

В пн, 29 февраля 2016 г., 18:47 Джейсон Фриман [email protected]
написал:

Вопрос о том, удовлетворяет ли [1, 2] [number], можно задать
и дискуссии. Но при чем здесь функция расширяемых кортежей?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/5453#issuecomment -190453352
.

@JsonFreeman , потому что оператор распространения для типов и массивов / кортежей. Если оператор типа спреда разрешен в форме "заданных типов A , B и T = [A] , тогда [...T, B] построит [A, B] " (что неявно предлагается), тогда он не будет согласован с оператором распространения массива / кортежа. Учитывая var a: [A] и var b: B , нельзя доказать, что выражение [...a, b] относится к типу [A, B] . Согласно текущим правилам кортежей, можно доказать, что он имеет тип [A, A|B] .
Имеет ли это смысл для вас? Или я могу создать сравнительную таблицу, чтобы выделить это несоответствие.

@Igorbek Я понимаю, о чем вы говорите. В конечном итоге это связано с тем, что компилятор прекрасно знает типы, с которыми имеет дело, но не знает значения. В частности, в вашем примере значение a имеет неизвестную длину, тогда как тип [A] имеет известную длину. Это была одна из причин, по которой мне изначально было неудобно использовать для этой цели типы кортежей. Но я не уверен, что это серьезная проблема.

@isiahmeadows Я понимаю, о чем вы спрашиваете, но почему проблема яснее с параметрами типа отдыха? Если у вас больше аргументов, чем аргументов типа, можно задать тот же вопрос.

Типовое безопасное решение было бы более согласовано с остальными
язык, если он имитировал синтаксис аргумента.

Я хочу сказать, что если вы эффективно распространяете параметр отдыха, вы получаете
именно типы аргументов и не более того. Каррированные функции имеют возврат
тип зависит от типа аргумента. Итак, если вы примените слишком много аргументов
чтобы частично применить каррированную функцию, вы получите совершенно другой
тип. Обработка остальных типов, таких как кортежи, вместо этого приведет к ошибкам времени выполнения,
которые никогда не бывают хорошими.

Во вторник, 1 марта 2016 г., 06:07 Джейсон Фриман [email protected] написал:

@Igorbek https://github.com/Igorbek Я понимаю, о чем вы говорите.
В конечном итоге это связано с тем, что компилятор прекрасно знает
типов, с которыми он имеет дело, но не идеальное знание ценностей. В
в частности, в вашем примере значение a имеет неизвестную длину, тогда как
тип [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

Я тоже добавлю свой вариант. Давайте посмотрим на пример вариативного карри из предложения:

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. Для конструкторов и вызываемых объектов должно быть два отдельных типа, причем функции должны быть объединением двух. Вызываемые объекты должны реализовывать вызываемый интерфейс, конструкторы классов должны реализовывать конструируемый интерфейс, а функции ES5 должны реализовывать их объединение.
  2. Function.prototype.bind и друзья должны проверить все перегрузки для функции. Если таких работ несколько, он должен вернуть объединение всех из них.

Однако эти параметры типа в вашем примере на самом деле не являются параметрами типа сигнатуры привязки. Они относятся к типу Function. Но да, идея в том, что если бы вы могли использовать два параметра отдыха или распространить два параметра типа отдыха в кортеж, вы могли бы написать это.

Чтобы подпись привязки была достаточно гибкой, необходимо определять границу между ...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
}

Существует потенциальная другая проблема, которую будет намного сложнее решить (и почему я думаю, что ее следует ограничить до 2, а не _n _ делений):

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 ..._

Да, я думаю, это правильная идея. Это некрасиво, но я не могу придумать другого способа правильно ввести bind , зная аргументы типа Function . В конечном итоге необходимо установить границу. И я согласен с тем, что он должен быть ограничен двумя ведрами, чтобы вы могли вывести 1 границу вместо некоторого произвольного количества границ, которые могут комбинаторно взорваться.

Вероятно, есть другие проблемы, о которых мы не думали.

@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. Я также исправил ваш интерфейс промиса, чтобы он был немного ближе к моему предложению.

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,U>] синтаксической ошибкой или комбинаторным расширением?

@DerFlatulator

  1. Если ровно один из T или U является параметром отдыха, он обычно расширяется. Если предположить, что T - параметр отдыха, тогда это будет [Foo<T1, U>, Foo<T2, U>, /*... Foo<TN, U>*/] .
  2. Если оба являются остаточными параметрами, и их длина может быть правильно выведена, это должно быть комбинаторное расширение (ну ... длина T умноженная на длину U).
  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
}

_ (Если кто-то может придумать лучший синтаксис, я все слышу. Мне это не нравится, но я не могу придумать ничего, что визуально не противоречило бы.) _

@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, ...TUnbound] = [...TArgs] допустимо, потому что длина ...TBound известна из длины args . Он также позволяет изменять тип 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>

Здесь я согласен с @Igorbek . По крайней мере, на данном этапе отображение последовательностей типов не кажется приоритетом, учитывая, что мы все еще пытаемся решить более основную проблему параметров вариативного типа.

У меня нет особых проблем с его запретом (по крайней мере, на начальном этапе), поскольку поведение при этом довольно неясно, и два разных человека могут даже ожидать двух очень разных вещей. Я согласен с @Igorbek, по крайней мере, сейчас, поскольку TypeScript сначала должен иметь модель типа более высокого порядка (это в некотором смысле map ping тип). И типы более высокого порядка - это не то, на что можно просто надеться.

Так что определенно: +1: за запрет этого, наверное, на долгое время. Несмотря на то, что это приятно иметь, его чертовски сложно реализовать, и это было бы полным взломом, поскольку TypeScript не использует функциональную, типобезопасную систему типов.

Придет немного поздно, но я тоже согласен с

Упаковка типов в кортеж кажется совместимой с использованием оператора распространения в 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.apply vs Function.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 , верно?

Что касается apply и call , разве они не покрываются пересекающимися типами? (В любом случае, я не вижу смысла в наличии типов в интерфейсе 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 , и друзья, или вообще что-нибудь, использующее частичное приложение. Вы также должны иметь возможность выбирать, какой параметр отдыха отделять, и это действительно может быть сделано только для каждого метода.

call и apply довольно просты:

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 сделало бы его полным по Тьюрингу, но это было бы близко. Я думаю, что в первую очередь этому препятствует возможность сопоставления специализаций определенного типа (которые есть у C ++, Scala и Haskell, трех языков с полными по Тьюрингу системами типов).

@sandersn Я не видел примера выше, но могу ли я спросить об ограничениях на вариативные параметры?

Рассмотрим следующий пример:

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

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

_Кстати, см. Https://github.com/Microsoft/TypeScript/issues/7848 для обсуждения возможного отказа от требования о том, что X должен быть указан_

Теперь здесь потенциально есть некоторая двусмысленность относительно того, является ли ограничение:

  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> с приведенным ниже (с F-ограниченным T):

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

Это сделано для того, чтобы типы можно было разрешить локально. Вы не знаете типы заранее, и это значительно ускорит компиляцию, поскольку вам не нужно вычислять типы внутри функции для каждого ее вызова. Обратите внимание, что если вам нужен только тип одного аргумента, достаточно typeof arg , и он, вероятно, будет короче.

Извините, в исходном примере я имел в виду, что тип должен быть T . Собственно, для вашего второго примера я тоже считаю, что это должен быть T.

Я имел ввиду Item<any> во втором ... Простите.

Когда я сказал, что это должен быть T, я предполагал, что T - это тип, но я полагаю, что весь смысл этой функции в том, что T не является типом (я думаю). Так что да, я думаю, в ваших примерах должно быть any и Item<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 возможно, но маловероятно.

Если / когда мы обязуемся должным образом поддерживать объект rest / spread (# 2103), тогда вариативные типы могут быть достаточно близкими для распространения типов, чтобы оправдать выполнение их всех сразу. (Типы распространения - это вариант типов объектов, которые выглядят как { ...T, x: number, ...U, y: string, ...V } .)

Просто хочу упомянуть, что обходной путь n overloads не работает для классов или интерфейсов, что меня особенно интересует в этой функции.

@sandersn Будет ли предложен bind , apply и call в функциях с использованием набора this ? Я думаю, что это был бы приемлемый временный компромисс для многих и мог бы выявить довольно много ошибок в процессе для некоторых проектов.

@isiahmeadows

Причина, по которой я не думаю, что это обязательно должно быть T, заключается в том, что вы не знаете, что такое T.

Мне казалось, что существует согласие, что T является кортежным типом вариативных типов. В вашем исходном примере тип arg будет таким же, как тип элемента кортежа (как отметил @JsonFreeman , «тип объединения элементов»): а пока представьте, что машинописный текст поддерживает использование кортежа в качестве остатка. введите (# 5331).

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, если / когда он появится, зависит от этого, так что теперь на этом больше ездят, чем причудливые авторы библиотеки, которые все время вас придирают . :)

Не то чтобы это было особенно конструктивно для добавления, но я был бы ооочень счастлив, если бы эти две функции вошли в 2.1. Я знаю по крайней мере одну библиотеку (RxJS), где эти функции не только улучшили бы саму кодовую базу, но и использование кода было бы значительно менее неудобным и подверженным ошибкам (каждый третий человек, начинающий работать с Angular 2, укушается отсутствием импорта для операторов, которые вставлены в наблюдаемый прототип). Это действительно будет прорыв для людей, которые хотят писать поддерживаемый функциональный код.

Можно ли это использовать для предоставления полного определения типа для _.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 };
}

Обратите внимание, что синтаксис «шесть точек» - это разброс объектов переменной типа кортежа, который мы особо не обсуждали выше.

@sandersn

В частности, с Object.assign он может быть набран таким образом и технически захватывать подмножество (хотя и немного слишком невыносимо слабое), поскольку он мутирует свою цель (для этого вам понадобится ориентир):

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

Проблема в том, что он мутирует свою цель, изменяя ее структурный тип на месте.

@isiahmeadows, тогда вывод исправит T как тип target без учета типов sources . Вы можете попробовать это сейчас с безвариативной версией:

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 Кстати, есть ли какая-нибудь информация о том, когда будут высажены различные виды? Есть шанс увидеть это в 2.2?
Что касается синтаксиса, вы все еще принимаете отзывы о синтаксисе или все с этим согласны?

По синтаксису и семантике низкого уровня еще нет четкого консенсуса.

Вт, 13 декабря 2016, 13:26 Игорь Олейников [email protected] написал:

@sandersn https://github.com/sandersn Кстати, есть ли обновления о том, когда
собираются высаживаться вариативные виды? Есть шанс увидеть это в 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[]

Однако у меня нет возможности добавить types в Portable, потому что я не могу извлечь тип параметров из _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).

Кстати, afaik, вы еще не прокомментировали предложение, выложенное @Artazor и @Igorbek. Что ты думаешь об этом?

Я хотел бы поспорить с такой базовой реализацией (плюс # 6606), что мы можем делать что угодно. Я предлагаю здесь пару решений, чтобы проиллюстрировать это, но я открыт для дальнейших вопросов.

Прежде всего позвольте мне перейти на некоторых местах , где ... можно было бы реализовать оператор:

v ... для | определение (захват) | использование (распространение)
- | - | -
функция | 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, 3 ] в T ). весело, но не знаю, для какого варианта использования это требуется. | определить type Bar<A,B,C> для выполнения Bar<...[1,2,3]> ( A = 1 и т. д.). то же самое, не знаю сценариев использования, которые в этом нуждаются .
союзы (бонус) | ? | 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

Это всего лишь вариативные версии mergeAll Ramda. Шесть точек не нужны!

@isiahmeadows :

Есть идеи о статусе этой проблемы?
По синтаксису и семантике низкого уровня еще нет четкого консенсуса.

Если я правильно понимаю, вы в основном беспокоились о том, учитывает ли подход, предлагаемый некоторыми другими, решение более жестких типов, таких как curry и bind вы упомянули. Вот мое мнение об этом конкретном после их предложения.
Стратегия немного похожа, обманывая тот факт, что трудно сказать, извлечь требования к типу для 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*>> . Но ИМО, даже просто ... помогает решить реальную проблему прямо сейчас, и я бы хотел, чтобы она была решена.

Изменить: я решил ограничение аргумента для bind .

Просто наверное глупый вопрос. Это выглядит очень многообещающим для правильного ввода функций карри.
Но для некоторых реальных проектов нельзя тратить много времени на набор функционально ориентированного на программирование фрагмента кода.
Итак, мне интересно, поскольку --strict по умолчанию включен в _tsconfig.json_, есть ли способ отключить проверку типов для части кода (из-за лени или нехватки времени).
Но, как я уже сказал, наверное, глупый вопрос ... ^ _ ^

@ yahiko00 не по теме, но используйте раздел exclude в tsconfig или разные tsconfig s на разных уровнях проекта.

Я также хотел бы сделать еще одно предложение, не могли бы мы сделать так, чтобы & и | работали с одним аргументом кортежа с таким синтаксисом:

<...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;

Приведенное выше предложение

Я хочу добавить оговорку , что это предложение не активно ведется работа. Но я нашел именно тот вид «предшествующего уровня техники», который хотел прочитать, когда впервые начал рассматривать эту проблему: http://www.ccs.neu.edu/racket/pubs/esop09-sthf.pdf

Я оставлю это здесь на будущее.

Я открыл несколько PR, экспериментируя в этом направлении:

  • [] # 17884 спреда в типах кортежей (WIP)
  • [x] # 17898 извлечь остальные параметры (готово)
  • [] # 18007 спреды по типу звонка (WIP)
const c = 'a' + 'b';

Может решить проблему? Вывести тип c - 'ab' not string

Связанный вопрос о StackOverflow: явный последний параметр функции в TypeScript

@sandersn Ваше предложение касается этого случая, насколько я понимаю, это правильно?

Прошло более двух лет с момента первоначального предложения, должны ли мы по-прежнему сохранять надежду?

Привет!
Я пытаюсь напечатать генератор, который принимает переменное количество массивов и смешивает и сопоставляет их элементы для создания нового массива.
Я хочу использовать этот генератор в цикле 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 types
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 {

Во время выполнения параметр rest - это массив, и мы можем сделать это в настоящее время в TS:

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

Поэтому кажется логичным просто удалить ограничение args являющееся массивом из T и вместо этого позволить компилятору TS вывести тип кортежа для 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 - это динамически создаваемый тип кортежа, поэтому, если вы передадите string и int в функцию, тогда 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] .
Это то, что имеет в виду

Принудительно обрабатывает все параметры как один и тот же тип, и они не могут иметь более конкретную проверку типа. Например, в моем примере выше все возвращаемые значения будут иметь тип 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 будет массивом something (например, string[] ).

И читая это:

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

Я бы ожидал, что все аргументы можно будет присвоить типу T (это один тип, например string ).

Смысл вышеупомянутого предложения состоял в том, чтобы избежать неявного создания дженериков, способных представлять произвольное количество типов.

@felixfbecker
Редактировать:
Ох, хорошо. Все еще не думаю, что это интуитивно понятно.

Я ожидал, что T будет массивом something (например, string []).

Кортеж - это «массив чего-то», это просто массив с фиксированной длиной и определенными типами для каждого элемента, например [string, number] (vs (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]

конечно, в случае параметра rest известно, что это не просто Iterable, а массив.

Что, о чем мы говорим, уже было предложено: 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] в
Позиция аргумента уровня типа разрушает тип, если это кортеж уровня типа? Это также потребует
позволяя вводить параметры отдыха с кортежами, что сделало бы следующий эквивалент на месте вызова
в TypeScript:

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, и он нужен для конвейерных функций.

Я автор ppipe , который, как и конвейерные функции в RXJS, нуждается в этом. Думаю, я вижу здесь закономерность ^^

Я автор рунтипов , и эта функция отчаянно нужна для выражения объединений и пересечений. Единственный (неполный) обходной путь - гигантские перегрузки:

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

🤢

Я бы переписал типы рамды , которые тоже нужны, например, для pipe , линз и каррирования.
Нам нужен был кодогенератор, так как каррирование сделало неуправляемым прямое поддержание перегрузок. Наш тип 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[]] , вызов apply вызовет ошибку.

Это означает, что тип 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 участников этой проблемы (совет: используйте это ), но как мы можем отслеживать, если это все еще рассматривается, есть ли это в дорожной карте и т. Д.?

Печально, что еще никого не назначили через 2 с половиной года, кажется, довольно важная особенность :(

PS: Я прочитал пару десятков комментариев, пробовал 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 «s трюк , чтобы компилятор пропустить рекурсию.

Благодарность

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 теперь можно объявить остальные аргументы как tupple.

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);
  };
}

но у нас есть небольшая проблема: мы должны объявить функции, которые возвращают событие tupples для отдельных параметров, а также каким-то образом иметь дело с 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% 0 Функция% 20bar1 (a% 3A% 20string)% 3A% 20% 5Bstring% 5D% 20% 7B% 0D% 0A% 20% 20 возврат% 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% 20 возврат% 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% 20 Return% 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% 20 return% 20% 5Ba1% 2C% 20a2% 5D% 3B% 0D% 0A% 7D% 0D% 0A% 0D% 0A% 0D% 0Afunction% 20compose% 3CX% 20extends% 20any% 5B% 5D% 2C% 20Y% 20extends% 20any% 5B% 5D% 2C% 20Z% 20extends% 20any% 5B% 5D% 3E ( % 0D% 0A% 20% 20f% 3A% 20 (... args% 3A% 20X)% 20% 3D% 3E% 20Y% 2C% 0D% 0A% 20% 20g% 3A% 20 (... args% 3A% 20Y)% 20% 3D% 3E% 20Z% 0D% 0A )% 3A% 20 (... args% 3A% 20X)% 20% 3D% 3E% 20Z% 20% 7B% 0D% 0A% 20% 20 возврат% 20функция% 20 (... args)% 20% 7B% 0D% 0A% 20% 20% 20% 20const% 20y% 20% 3D% 20f (... args)% 3B% 0D% 0A% 20% 20% 20% 20 возврат% 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% 20compose (% 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) но я думаю, что это снижает ясность без реальной причины.

Если вариативные типы будут реализованы, я смогу обобщить некоторый код TypeScript, который я написал для своего собственного проекта, который позволил бы мне полностью определить форму моего REST API и применить эту форму к типам соответствующего сервера Node и JavaScript. клиентская библиотека для него.

Обобщение, которое позволило бы мне упростить мой собственный код, а также сделать то же самое для произвольных API, пойти дальше и, вероятно, сгенерировать определения Swagger и для других языковых клиентов ... может быть полезно для других! Просто мечтаю вслух, хотя хаха

Мне удалось набрать трубку
https://github.com/kgtkr/typepark/blob/master/src/pipe.ts

@kgtkr :

Тип трубы сбивает мне игровую площадку TS (хотя другие работают нормально), я думаю, ему нужна новейшая TS?

TS также показывает некоторые ошибки глубины рекурсии - похоже, @isiahmeadows для этого открыл # 26980.

@ tycho01

Тип трубы сбивает мне игровую площадку TS (хотя другие работают нормально), я думаю, ему нужна новейшая TS?

Он тоже зависает у меня, и мне пришлось взломать инструменты разработчика, чтобы заставить его выдать ошибку, чтобы выйти из него.

TS также показывает некоторые ошибки глубины рекурсии - похоже, @isiahmeadows для этого открыл # 26980.

Это для чего-то связанного, но другого: снятия ограничения с условными типами по двум причинам:

  • Чтобы упростить выполнение более сложной работы, например перебора списка. Это также заложило бы основу для открытия таких вещей, как целочисленная математика на уровне типов, без сбоя компилятора или в конечном итоге в какой-то спонтанной неразберихе.
  • Для более точного решения вопроса о том, что делает систему типов TS Turing-полной, чтобы она потенциально могла быть либо усилена с помощью инструментов, использующих ее, либо удалена путем принудительного обеспечения доказуемого завершения в будущем.

Если удаления индексированных типов недостаточно для того, чтобы система типов не была полной по Тьюрингу как есть, пожалуйста, оставьте там комментарий, чтобы я мог соответствующим образом обновить его. (Я, конечно, не предлагаю удалять его. Я просто предлагаю лучше справиться с этим изнутри, чтобы предупредить людей о потенциальных бесконечных циклах.)

Если подумать, этот тип трубы кажется действительно запутанным способом сделать (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 Кстати, я обновил эту ошибку исправленным фрагментом 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>);

Кстати, этот не разбивается на детской площадке. На самом деле он проверяет типы, и делает это довольно быстро.

Я еще не тестировал его на потенциальном типе _.flow или _.flowRight , но это должно работать как отправная точка.

@ tycho01
требуется
машинописный текст @ следующий
3.0.1 / 3.0.2 не работает

Благодаря этой теме я сделал это

Народ, пожалуйста, перестаньте публиковать информацию, мало относящуюся к обсуждению этого вопроса. За этим обсуждением следит много людей, потому что нам нужны вариативные виды. Я получил около 10+ писем за последние пару дней, которые не имеют отношения к причинам, по которым я слежу за этой проблемой.
Я думаю, есть и другие, которые со мной согласны. До этого момента я просто надеялся, что это прекратится, потому что я не хотел способствовать рассылке спама. А если серьезно, хватит.
PS Извините за это уведомление, всем, кто так устал от них, как и я

@Yuudaari Я _.flow , Ramda _.compose и т. Д. Является одним из движущих факторов этой ошибки, и успешный набор текста является частью решения этой проблемы. Фактически, это одна из причин, перечисленных в исходном описании проблемы.

На самом деле, на данный момент я придерживаюсь мнения, что 99% проблем, которые существуют сегодня с вариативностью, связаны с эргономикой, а не с функциональностью. Мы можем идеально ввести Function.prototype.bind и Promise.all со смесью индексированных типов, условных типов и рекурсии (вы можете повторить Append повторяя список для Function.prototype.bind , а Promise.all будет простой итерацией + Append ), просто это очень неудобно и шаблонно.

Я не пытаюсь добавить здесь шума, просто объясняя, что материал, начинающийся здесь , технически не по теме, поскольку касается некоторых причин, по которым существует ошибка, даже если они не те, которые беспокоят вас лично.

Я думаю, что люди, ожидающие здесь анонсов, упустили важную новость - оказалось, что теперь возможные функции Concat<T, U> точно такие [...T, ...U] .

Подпоток Pipe предназначен для демонстрации функциональности, которую мы здесь просили. Речь идет о достижении сути этой беседы сегодня.

Я думаю, это означает, что нам не будет хуже, если мы закроем эту ветку, так что, возможно, сейчас хороший момент, чтобы спросить - что люди все еще хотят от этого предложения?

[это] просто это очень неудобно и банально

Большинство типов, использующих это, будут сами использовать рекурсию, поэтому те, кто их пишет, обязательно будут с ними знакомы, в то время как конечные пользователи, скорее всего, будут просто использовать библиотеки с предопределенными типами и писать свой интерфейс, не зная, что существует итерация TS.

В этот момент, возможно, это предложение может в основном улучшить производительность?

Во-первых, используются ли объекты карты для того, чтобы обмануть систему типов и заставить ее выполнять даже запланированную рекурсию? Мне это кажется довольно хакерским. Если я использую такую ​​функциональность (я использую, но это не имеет значения), не будет ли она нарушена позже?

Во-вторых, использование этих обходных путей просто не ... дружественно. Он не очень удобочитаемый (особенно для тех, кто его не писал), и в результате его неудобно поддерживать.

Зачем мне прибегать к предложению, которое добавляет те же функции в предполагаемом, удобочитаемом и поддерживаемом виде только потому, что для них есть обходной путь?

Я не думаю, что существование этого обходного пути заставляет это предложение считаться синтаксическим сахаром, но даже если бы это было так, почему бы мне не захотеть синтаксического сахара для этого беспорядка?

@Yuudaari

Изменить: добавить ссылку для контекста.

Во-первых, используются ли объекты карты для того, чтобы обмануть систему типов и заставить ее выполнять даже запланированную рекурсию? Мне это кажется довольно хакерским. Если я использую такую ​​функциональность (я использую, но это не имеет значения), не будет ли она нарушена позже?

Взгляните на недавно зарегистрированную мной ошибку: # 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 как раз в начале этого года, я хотел написать _just that_! ( ...T ) в надежде, что это будет синтаксис для кортежей переменных типа. Что ж, надеюсь, это попадет :)

Только что нашел новое применение для [...T, ...U] : правильно набирать HTML-конструкторы. В конкретном примере дочерние элементы <video> должны быть следующими:

  • Если элемент имеет атрибут src :

    • Ноль или более элементов <track>

  • Если элемент не имеет атрибута src :

    • Ноль или более элементов <source>

  • Ноль или более элементов в соответствии с родительской моделью содержимого, за исключением элементов-потомков audio или video .

Это в основном приравнивается к этому типу, но сегодня нет способа выразить это в 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 всегда был первым аргументом для ввода promisify как function promisify<T, V extends any[]>(fn: (cb: (err: Error | null, res?: T) => void, ...args: V)): (...args: V) => T и вы могли бы, по крайней мере, получить типы объединения для подписей с тем же ответом возврата, но потому что это последний параметр. Здесь мало что можно сделать.

@ Qix - Ваш сценарий включен # 24897. Был реализован в TS 3.0.

@ahejlsberg Уф ! Отлично, спасибо ♥ ️

Долго ждали ... Но сегодня можно писать вариативные виды. TS достаточно зрел, чтобы писать работающие сложные типы. Итак, я нашел время, чтобы написать типы для curry, concat , compose и pipe для Ramda.

И теперь поставляются с ts-toolbelt .

Однако это предложение является прекрасным синтаксическим сахаром, значительно упрощающим общие манипуляции с кортежами.

он у вас уже есть на medium.com? URL?

На Medium есть оригинальная статья, но бонус в нее не включен, он находится в репо. В нем также объясняется, как я создал все маленькие инструменты для компоновки, пайпинга и карри: smile:

@ 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 выводит их на ближайший к ним тип (в этом случае T будет any ). Так что на данный момент нет возможности сделать это. Мы надеемся дождаться объединения этого предложения с 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 , B и C в вашем примере не используются.

-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 . Он выполняет Minus 216000 раз менее чем за 4 секунды. Это показывает, что TS очень хорошо справляется с рекурсивными типами. Но это совсем недавно.

Почему? Это спасибо Андерсу: tada: (https://github.com/microsoft/TypeScript/pull/30769). Он позволил мне переключаться с условных типов на индексированные условия (например, переключатель). Фактически, он увеличил производительность на x6 для ts-toolbelt. Огромное ему спасибо.

Таким образом, технически мы могли бы безопасно переписать тип @kimamula с помощью ts-toolbelt. Сложность следует за 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'},
]>

Библиотека также делает рекурсию безопасной с помощью Iteration что предотвратит любое переполнение из TypeScript. Другими словами, если I превышает 40 тогда происходит переполнение, и 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<> ?

Или здесь ключевая фраза «официально благословлена»?

Потому что я утверждаю, что в принципе вы можете делать все (или почти все?), Что могли бы захотеть в данный момент, даже если это не совсем «официально одобрено».

Но я думаю, что всегда следует отдавать предпочтение «официально благословленному» ... Хороший аргумент. Я слишком привык (ab) использовать эти псевдонимы рекурсивного типа

Я обычно предпочитаю настоящий элегантный синтаксис, чтобы каждый раз, когда я делаю что-то подобное, мне не приходилось постоянно объяснять моим (часто младшим) товарищам по команде, что происходит с запутанно заданными типами, которых требует злоупотребление статус-кво. Эта путаница вредит моей способности пропагандировать TypeScript или, по крайней мере, его использование в моей организации.

Большое на этом!

Эта функция ТАК важна.

@AnyhowStep

Потому что я утверждаю, что вы можете делать все (или почти все?), Которые вам нужны в данный момент.

Я не вижу доказательств того, что набор параметров распространения с несколькими одиночными параметрами рядом с ним сегодня легко достижим в 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[]] , что бесполезно.

Также я хотел бы отметить, что нам не нужно соблюдать десятое правило Гринспана в течение 15 лет, как это делал C ++, потому что C ++ уже прошел за нас все Head<> s и Cons<> , и разработал очень удобный и чистый синтаксис вариативного шаблона. Мы можем сэкономить (сотни лет) времени разработчика и просто взять оттуда лучшие части.

Например, вариативные типы имеют другой вид в C ++, поэтому вы не можете использовать переменные вариативного типа там, где ожидается тип, в отличие от типа, который extends any[] в TS. Это позволяет 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 */>;

Из вышесказанного вы можете написать функцию concat с перегрузками для 2,3,4,5,6 и т. Д. количество аргументов.

Можно даже написать Concat <> impl, который берет кортеж кортежей и объединяет их. Тип var-arg Concat <>.


Это не то, что нельзя сделать сегодня. Это можно сделать, даже если для этого потребуется написать рекурсивный тип и вспомогательную функцию.

Каждый раз, когда я использую эти рекурсивные типы в vscode, TS хочет убить процессор или просто зависает! Это основная проблема, я чувствую, что TS становится слишком тяжелым без уважительной причины.

Может быть, люди, пишущие типы, недостаточно стараются их оптимизировать?

Я не хочу спамить или перетаскивать старые разговоры, которые не добавят особой ценности этому потоку, но я помню, что в какой-то момент слышал, что вычисление рекурсивных условных типов в javascript стоит дорого (я нашел его здесь, в этом потоке )

Тем не менее (я знаю, что это может показаться безумным), возможно, пришло время переписать TS на другом языке, чтобы дать толчок системе типов, поскольку TS уже настолько вырос, что разумно попросить лучшую.

Вы можете легко написать тип Concat<> и создать тип Concat3<> , используя Concat<> .

Не могли бы вы предоставить реализацию для описываемого вами типа Concat<> ? Легко написать Cons<> , но Concat<> не так просто (для меня), и мне бы хотелось увидеть, что вы видите.

Что касается Concat3<> , Concat4<> и т. Д., Есть надежда, что в долгосрочной перспективе нам не нужно будет писать десятки подобных вариантов, потому что у нас будут вариативные виды. 🙂 Однако, если их хорошее внедрение возможно сегодня, это было бы разумной временной мерой.

Для регулярного объединения двух кортежей
https://github.com/AnyhowStep/ts-trampoline-test (использует батуты для создания очень больших кортежей, которые большинству людей не понадобятся)

Concat3 будет просто Concat, C>

VarArgConcat будет,
VarArgConcat<TuplesT extends readonly (readonly unknown[])[], ResultT extends readonly unknown[] = []>

Пока кортеж не пуст, VargArgConcat<PopFront<TuplesT>, Concat<ResultT, TuplesT[0]>>

Если TuplesT пуст, вернуть ResultT

Конечно, наивная рекурсия приведет к ошибкам максимальной глубины с кортежами приличной длины. Итак, либо используйте технику рекурсии в ts-toolbelt, либо используйте батуты с копированием и вставкой на желаемую глубину.


Это репо, с которым я связался, использует Reverse<> для реализации Concat<> . Я скопировал код из другого проекта, над которым работаю.

Я согласен, что это была бы чрезвычайно полезная функция.

Допустим, у нас есть тип T :

type T = {
  tags: ["a", "b", "c"];
};

И мы хотим создать новый тип с дополнительным тегом "d" , добавленным к кортежу T["tags"] . Сначала пользователи могут попробовать создать эту утилиту ( 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 .
Я думаю, что это можно было бы поддержать, если бы был особый случай для функций с параметром rest кортежа, где последний элемент кортежа является массивом.
В этом случае вызов функции позволит дополнительным аргументам правильного типа соответствовать массиву.
Однако это кажется слишком сложным, чтобы иметь смысл.

У этого есть две проблемы:

  1. Код неверный. curried() не принимает массив. Заполнение параметра c rest параметром [true, false] может быть выполнено с помощью curried('foo', ...[true, false]) но это не поможет в TypeScript с этим предложением. Возможно, в некоторых случаях мы не сможем предоставить решение для набора текста, но мы не рекомендуем указывать кого-то не так!
  2. Вы непреднамеренно объединили необязательные и остаточные параметры и обнаружили ошибку в своем предложении. curried() нельзя вызвать без b но с c . Это приведет к плохому поведению. TypeScript знает, что curried() - это (...items: [string, boolean[]] | [boolean[]]) но это не так . Поскольку JavaScript не набирает текст, передача [true, false] в c (при условии, что мы решили вышеуказанную проблему) с curried([true, false]) не установит b в undefined (или его значение по умолчанию) и c на [true, false] , но установит b на true и c на [false] !

Предлагаю следующие исправления:

  1. Для второй (и более простой) проблемы решение простое: не выводить объединенный кортеж для последнего необязательного аргумента (например, [number, string, boolean[]] | [number, boolean[]] в нашем случае), когда есть параметр rest. Вместо этого выведите [number, string, boolean[]] | [number] - то есть один случай для полной подписи, включая все опции и остальные, по одному для каждого варианта, кроме последнего , и один без последнего и остальных.
  2. Первая проблема сложнее: вы уже сказали, что думаете, что это слишком сложно, чтобы иметь смысл. Я считаю, что это стоит того, учитывая популярность остальных параметров, но это необходимо из-за первой проблемы (победа! 😄). Я думаю, было бы неплохо, если бы мы открыли интерфейс для массива tuple-with-last-rest (я думаю о синтаксисе [t1, t2, t3, ...arr] ), но нам это не нужно. Мы можем остаться с ним как с внутренним (ха-ха, вам все равно придется иметь дело с тем, как отображать тип в IDE 😈).

Но после всех жалоб и провокаций, отличное предложение! Спасибо 👍 (чтобы успокоить вас, это первая проблема в GitHub, на которую я ответил тремя смайликами - 👍, 🎉 и ❤️).

Это было бы действительно полезно в Angular Injector, который в настоящее время должен использовать any https://github.com/angular/angular/issues/37264

В этом примере 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. Вы ведь понимаете, что потребуется разобрать регулярное выражение, чтобы выяснить, сколько у него групп захвата, верно?

Это было бы действительно полезно в Angular Injector, который в настоящее время должен использовать any angular / angular # 37264

В этом примере 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>];
}

Эта проблема теперь исправлена ​​номером 39094, запланированным на TS 4.0.

Если это будет с 4.0, теперь у нас есть причина назвать его 4.0 😃
Это действительно серьезная новая функция 🎉

Отлично! Единственное, что «осталось» - то же самое для буквальных строковых типов.

@sandersn Я пытаюсь понять, как этот синтаксис можно использовать в таких вещах, как RxJS , где параметры метода pipe как бы зависят друг от друга,

как в pipe(map<T, V>(...), map<V, U>(...), filter(...), ...) . Как бы вы напечатали это так, как сейчас они не делают? (Десятки строк разной длины, набранные)

@gioragutt использует PR, который представил @ahejlsberg, я думаю, это сработает, но я могу ошибаться 😄

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

Кроме того, в любом случае я настоятельно рекомендую поделиться этим в запросе на перенос, в котором находится комментарий.

Типы кортежей с переменным числом аргументов - отличное дополнение к языку, спасибо за ваши усилия!

Похоже, что такие конструкции, как 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 это работает

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']);

Здесь не так очевидно, что кортеж ссылается на этот параметр rest, если посмотреть на него снаружи.

foo<[number, string]>(12, '13'); 

Но да, как вы сказали, если мы позволим логическому выводу понять это, тогда эти тривиальные случаи не требуют каких-либо изменений со стороны пользователя. Но мы не знаем, установили ли они их явно или нет, это их дело, так что это по-прежнему считается критическим изменением. Но это проблема библиотеки, а не этого изменения.

Тем не менее, мне просто кажется странным, что если есть параметры отдыха, определенные снаружи один за другим, которые представляют собой единственный массив внутри, дифференцированный ... , нельзя сделать универсальным таким же образом: один за другим снаружи, один массив внутри, различается по ... .

Незначительные расхождения в синтаксисе на самом деле не стоят затрат на поддержку для отдельного
Добрый. Использование видов было бы правильным дизайнерским решением, когда TS планировала
поддержка параметров отдыха, но теперь я думаю, что это может привести к большему количеству
путаница как для разработчиков языка, так и для пользователей. Нам нужно было решение для
этой проблемы, и Андерс отлично справился со своей работой, избегая этого
сложность, придерживаясь [...T] вместо T . Снимаю шляпу!

(Не могли бы мы теперь взглянуть на ошибку, которая объединяет тип пересечения с
выведенная переменная в условном типе возвращает тип крайнего правого пересечения
аргумент, или что объединение массивов не является массивом объединения, пожалуйста? Мы по-прежнему
имеют серьезные препятствия в системе типов.)

Пт, 19 июня 2020 г., 10:41 Дьёри Шандор [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/AAWYQIMTTB6JEPSQFUMTMDTRXMJD5ANCNFSM4BTBQ7DQ
.

Я, конечно, далек от его уровня, но я с уважением не согласен с
По моему опыту, большая часть сложности машинописного текста проистекает из того факта, что многие (интересные и полезные, конечно) функции выделены в особые формы как их собственные концепции.

Однако эта сложность не зависит от количества функций!
Вместо этого язык может быть спроектирован на основе более крупных, всеобъемлющих концепций, из которых затем могут быть тривиально выведены эти особые случаи или реализованы в библиотеке std (type).

Самая общая такая концепция, конечно, будет заключаться в полной реализации зависимых типов, из которых затем может быть получено все остальное, но заходить так далеко не обязательно:
Как показали C ++ и, в меньшей степени, Rust, несколько крупномасштабных, последовательных концепций дают вам массу возможностей бесплатно.
Это похоже на то, что OCaml и Haskell (и я предполагаю, что F #?) Сделали на уровне значений, только на уровне типа.

Программирования на уровне типов нечего бояться, если оно спроектировано на языке, а не привязано к конкретным функциям.
Возможности C ++ 14/17 очень интуитивно понятны, за исключением синтаксиса, который связан исключительно с историческим багажом.

Общие концепции могли быть добавлены в оригинальный дизайн. После дизайна
ошибка уже была сделана, последовательность не может быть добавлена ​​без большого риска
обратная несовместимость. Я согласен с подозрениями относительно языкового дизайна как
в целом (TS довольно далека от стандартов, установленных академическим сообществом, никто не может
не согласен с этим). Есть много ошибок и несоответствий, которые
является основополагающим для миллионов производственных кодовых баз. Просто факт, что
разработчики могут придумывать полезные дополнения к языку
без случайного исправления этих ошибок, по моему скромному мнению, здорово
и заслуживает уважения. TS имеет те же сложности дизайна, что и C ++ здесь, но его
система экспрессивных шрифтов усугубляет ситуацию.

Пт, 19 июня 2020 г., 12:47 Беннет Пиатер [email protected] написал:

Я, конечно, далек от его калибра, но я с уважением не согласен
с @ahejlsberg https://github.com/ahejlsberg .
По моему опыту, большая часть сложности машинописного текста связана сфакт, что многие (интересные и полезные) функцииособняком в виде собственных концепций.

Эта сложность не зависит от количества функций.
хотя!
Вместо этого язык может быть разработан вокруг более крупных и всеобъемлющих
концепции, из которых затем могут быть тривиально выведены эти частные случаи, или
реализован в библиотеке std (type).

Самой общей такой концепцией, конечно же, была бы полная реализация
зависимые типы, из которых затем может быть получено все остальное, но
заходить так далеко не обязательно:
Как 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 - замечательная попытка добавить еще более красиво оформленные функции поверх двух слоев предыдущих попыток, которые нельзя удалить, в синтаксис, который уже не поддается детерминированному синтаксическому анализу.

Все это не по теме этой ветки и обсуждается здесь . Так что я постараюсь быть откровенным:

Академическим сообществам потребовались десятилетия, чтобы найти правильный подход к подтипам: система типов mlsub была создана всего 6 лет назад, намного позже, чем был впервые выпущен TypeScript. Это может быть та основа для классов, интерфейсов, типов объединения и пересечения с всеобъемлющими концепциями.

Но также помните, что есть условные типы. Мне не известны какие-либо документы, дающие им формальную семантику или описывающие минимальную систему типов с условными типами с доказательствами прогресса / сохранения. Я считаю, что это может быть связано с тем, что ученые все еще стесняются печатать свои неудачные попытки. Если ваше предложение предполагает, что эти основные несовместимые версии будут сделаны в 2040-х годах, когда научные круги освоятся с условными типами, я могу согласиться.

В противном случае «тщательно разработанная система типов» должна была бы удалить условные типы из языка, и я не думаю, что кому-то по силам преобразовать 60% DefininiteTyped для использования любой альтернативы, выбранной для их замены. (А потом проделайте это еще несколько раз, потому что это не единственная проблема.)

Боюсь, что единственное жизнеспособное решение - это создать отдельный язык программирования, который чем-то напоминал бы TS, и каким-то образом (не только потому, что писать код на нем было бы приятнее), чтобы соблазнить разработчиков использовать его. Райан уже неоднократно рекомендовал этот подход для улучшения TS.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги