Как указать список дженериков неизвестного/произвольного размера

Примечание. Я начал дискуссию на Github по этому вопросу. .

У меня есть функция zip, пока она типизирована для итераций того же типа T. Я хотел бы, чтобы это было напечатано для произвольного смешанного типа ввода, но при этом сохраняло соответствующий тип вывода, например, если тип ввода [Iterable<T>, Iterable<U>], я хочу, чтобы тип вывода был Iterable<[T, U]>. Возможно ли иметь это для произвольного размера ввода? По сути, я хочу сказать, если у вас есть этот список типов в качестве входных данных, вы получите их и в качестве выходных.

Вот текущая версия моего zip:

export function *zip<T>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
   const iterators = iterables.map(iterable => iter(iterable));
   while(true){
      const items = iterators.map(iterator => iterator.next());
      if (items.some(item => item.done)){
         return;
      }
      yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
  }
}

export function *iter<T>(iterable:Iterable<T>): Iterator<T> {
   yield* iterable;
}

Текущее лучшее решение от AndrewSouthpaw< /а>:

declare function zip<A, B>(Iterable<A>, Iterable<B>): Iterable<[A, B]>;
declare function zip<A, B, C>(Iterable<A>, Iterable<B>, Iterable<C>): Iterable<[A, B, C]>;
declare function zip<A, B, C, D>(Iterable<A>, Iterable<B>, Iterable<C>, Iterable<D>): Iterable<[A, B, C, D]>;
export function *zip<T>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
   const iterators = iterables.map(iterable => iter(iterable));
   while(true){
      const items = iterators.map(iterator => iterator.next());
      if (items.some(item => item.done)){
         return;
      }
      yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
  }
}

Он работает, как и ожидалось, при вызове с 4, 3 или 2 итерируемыми объектами, при вызове с 5 или более аргументами поток просто скажет, что zip может быть вызван только с 4 или менее аргументами. Конечно, мы можем добавить столько сигнатур функций, сколько захотим, чтобы заставить ее работать с 5, 6 или любым количеством N аргументов, но для этого потребуется объявить N различных сигнатур (что немного некрасиво). С другой стороны, эта стратегия не позволяет иметь неограниченное количество аргументов (как это делает оператор спреда). Я все еще ищу это.


Это подняло более общий вопрос, существует ли какой-либо язык, в котором это существует?

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

Чтобы быть немного более конкретным, я считаю, что если у вас есть система проверки типов, в которой (по определению) все типы известны статически (любая переменная имеет известный тип x), то функция f: Array<Iterable<x>> -> Iterable<Array<x>> всегда вызывается для известного типа x. Следовательно, мы должны быть в состоянии статически решить, какой тип f будет возвращен при заданном x (будь то x одиночный универсальный тип или список универсальных типов).

То же самое касается и самой функции, если у вас на входе тип x, то вам нужно только проверить, что ваша функция сохраняет тип x.

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


person cglacet    schedule 14.02.2019    source источник


Ответы (2)


Мы смогли добиться этого только путем переопределения объявления сигнатуры функции. Это может помочь:

declare function zip<A, B>(Iterable<A>, Iterable<B>): Iterable<[A, B]>
declare function zip<A, B, C>(Iterable<A>, Iterable<B>, Iterable<C>): Iterable<[A, B, C]>
declare function zip<A, B, C, D>(Iterable<A>, Iterable<B>, Iterable<C>, Iterable<D>): Iterable<[A, B, C, D]>
export function zip(a, b, c, d) {
  /* ... */
}
person Andrew Smith    schedule 15.02.2019
comment
Спасибо. Это почти то, что я ищу. Как вы думаете, возможно ли иметь одно и то же не только для 3, но и для любого количества (потенциально большого) итераций? В идеале я бы даже хотел что-то, что работало бы для неограниченного размера ввода, но я думаю, что этого было бы трудно достичь (и, возможно, излишним). - person cglacet; 15.02.2019
comment
Я еще не нашел способ. Вы всегда можете попробовать открыть проблему с потоком, но это смешанная сумка. В таких случаях, как ваш, я просто создаю глупое количество перегрузок, которые вмещают большое количество итераций. Если вы говорите о 10+, или 100, или 1000, то я просто согласен с тем, что мы пытаемся поместить типы в динамический язык, и по самой своей природе у нас не может быть всего. - person Andrew Smith; 15.02.2019
comment
Я не уверен, почему это было бы невозможно (не с потоком, а в теории). Думаю, я попытаюсь открыть вопрос по этому поводу, просто чтобы понять, почему это невозможно, если это так. Я обновлю свой вопрос по ходу. - person cglacet; 20.02.2019

Вот рабочее решение. Вся заслуга принадлежит jbrown215 из команды Flow, он нашел идею используя $ReadOnlyArray<mixed> здесь:

export function *zip<T: $ReadOnlyArray<mixed>>(...iterables:Array<Iterable<T>>): Iterable<Array<T>> {
   const iterators = iterables.map(iterable => iter(iterable));
   while(true){
      const items = iterators.map(iterator => iterator.next());
      if (items.some(item => item.done)){
         return;
      }
      yield ((items.map(item => { return item.value }): Array<any>): Array<T>);
  }
}

export function *iter<T>(iterable:Iterable<T>): Iterator<T> {
   yield* iterable;
}
person cglacet    schedule 25.02.2019