ECMAScript 6 имеет «генераторы», которые позволяют вам легко программировать в асинхронном стиле.
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
Для запуска вышеуказанного кода вы делаете это:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
Если вам нужно настроить таргетинг на браузеры, которые не поддерживают ES6, вы можете запустить код через Babel или short-compiler для генерации ECMAScript 5.
Обратный вызов ...args
завернут в массив и разрушен, когда вы их читаете так что шаблон может справиться с обратными вызовами, которые имеют несколько аргументов. Например, с узлом fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
Они вводят неоднозначность для многих алгоритмов. Многие <algorithm>
выглядят как
template<class iterator>
void do_something(iterator, iterator);
template<class iterator, class funct>
void do_something(iterator, iterator, funct);
Если вы добавите дополнительные перегрузки
template<class container, class funct>
void do_something(container, funct);
, компилятор будет иметь некоторые проблемы с выяснением того, что означает do_something(x, y)
. Если x
и y
имеют одинаковый type
, он будет соответствовать как iterator = type
, так и container = type, funct = type
. *)
C ++ 11 попытался решить эту проблему с помощью " концепций ", которые могли бы распознать разницу между контейнером и итератором. Однако эти «концепции» оказались слишком сложными, чтобы превратить их в стандарт, поэтому и эти перегрузки.
*) компиляторы не согласны с этим, компилятор Comeau утверждает, что он неоднозначен, g ++ 4.5 и MSVC 10 вызывает первую функцию.
После чрезвычайно продолжительного обсуждения комментариев, вот один пример, когда он не работает должным образом - с помощью адаптера контейнера который также может удваиваться как предикат.
#include <iostream>
#include <vector>
template<class iterator>
void test(iterator, iterator)
{
std::cout << "test iterator\n";
}
template<class iterator, class predicate>
void test(iterator, iterator, predicate)
{
std::cout << "test iterator, predicate\n";
}
template<class container, class predicate>
void test(const container& cont, predicate compare)
{
std::cout << "test container, predicate\n";
test(cont.begin(), cont.end(), compare);
}
template<class container>
class adapter
{
public:
typedef typename container::iterator iterator;
adapter(container* cont) : cont(cont)
{ }
iterator begin() const
{ return cont->begin(); }
iterator end() const
{ return cont->end(); }
bool operator()(const iterator& one, const iterator& two)
{ return *one < *two; }
private:
container* cont;
};
int main()
{
std::vector<int> v;
adapter<std::vector<int>> a(&v);
test(a, a);
}
Выход:
test iterator
blockquote>
К сожалению, это гораздо более общая проблема; а именно, что итераторы были разработаны для того, чтобы победить эти дерьмовые API-интерфейсы API и Java-стиль «Поместите алгоритмы как методы каждого отдельного контейнера». Они являются универсальным решением первого поколения, и нет ничего удивительного в том, что при отражении они были не так хороши, как другие возможные общие решения, которые можно было получить после того, как мы потратим на это двадцать лет.
Добавление этих перегрузок контейнеров будет просто помогая группе по крошечной части проблемного пространства; и это может даже ухудшить ситуацию в будущем. Решение - это диапазоны, которые C ++ ищет, чтобы ввести ASAP.
Следует отметить, что очень легко определить ваши собственные тривиальные обертки для добавления контейнеризованных версий.
Например:
template<typename Container, typename Func>
Func for_each(Container& c, Func f) {
return std::for_each(c.begin(), c.end(), f);
}
Теперь вы можете сделать простой вызов вы хотите. Нет никакой двусмысленности, потому что ваши обертки не находятся в пространстве имен std. Вы можете определить перегрузки, которые принимают const Container & amp ;. Если вам нужны версии, которые вызывают методы итератора C ++-11 const (например, cbegin ()), я думаю, вам нужно будет назвать оболочку по-разному. Я использую for_each_const.
Существует библиотека операторов диапазона с намерением исправить это. Многословие было разрезано несколько раз.
Ваш пример выглядел бы примерно так:
auto newVector = myVector * doSomething;
Да, doSomething
- без круглых скобок.
Знакомая идиома из оболочки (с помощью std-алгоритма):
auto t = vector<int>{3,2,1,4} | sort | unique;
Чтобы понять, что я думаю, нужно понять философию алгоритмов C ++. Давайте сначала зададим этот вопрос:
Почему алгоритмы C ++ реализованы как свободные функции вместо функций-членов?
Ну, ответ довольно прост: избежать взрывов внедрения. Предположим, у вас есть M
контейнеры и N
алгоритмы, и если вы реализуете их как члены контейнеров, то будут M*N
реализации. В этом подходе есть две (связанные) проблемы:
C ++ решает эти проблемы, реализуя их как свободные функции , так что у вас есть только N
реализации. Каждый из алгоритмов, работающих на контейнере , принимает пару итераторов, которые определяют диапазон . Если вы хотите перегрузки, которые принимают контейнер, а не пару итераторов, то Стандарт должен обеспечить такие перегрузки для каждого из алгоритмов, и будут 2*N
реализации, которые в значительной степени победят ту самую цель, почему C ++ отделил алгоритмы от контейнеры в первую очередь, а половина из этих функций не делает ничего, что не может быть сделано другой половиной.
Поэтому я не думаю, что это такая проблема. Чтобы избежать одного единственного аргумента, зачем использовать N
больше функций (которые также налагают ограничение на его использование, например, вы не можете передать ему указатели )? Однако, если программисты хотят использовать такие функции в своей утилите, они могут реализовать их в любое время вместе со многими другими на основе стандартного алгоритма!
Вы прокомментировали:
Well , реализация 2 * N фактически является только N реализациями. Остальные N являются встроенными перегрузками, которые прямо называют «реальную» версию алгоритма, поэтому они являются только заголовками. Предоставление перегрузок контейнеров не отменяет цели разделения алгоритмов из контейнеров, поскольку (как вы можете видеть в моем примере) они могут использовать шаблоны для обработки всех типов контейнеров.
blockquote>Исходя из этого логики, вполне можно спорить по алгоритмам
M*N
. Так что они также выполняют функции-члены (и вызывают внутренние функции)? Я уверен, что многие ребята из ООП предпочли быauto result = container.accumulate(val);
над
auto result = std::accumulate(container.begin(), container.end(), val);
Очевидно, что, как отметили другие пользователи, это сложная проблема, поэтому, к сожалению, это было долгое время, и в стандартной библиотеке все еще нет решения. Тем не менее, существуют уже имеющиеся библиотеки библиотек, такие как Boost :: Range и один в библиотечных библиотеках Adobe, которые обеспечивают не только простоту интерфейса, который вы описываете в своем вопросе, но и некоторые особенности fancier.
Ваш пример отлично работает с Boost (мы using namespace boost::range::adaptors
ниже):
boost::for_each(myVector, doSomething);
Мы также можем быстро и легко срезать myVector
:
boost::for_each(myVector | sliced(10, 20), doSomething)
Мы можем даже zip myVector
с другим, фильтровать по предикату и пробовать каждый другой элемент результирующих пар в одном простом заявлении (для этого требуется, чтобы вы распаковывали в doSomethingElse кортежи, созданные boost::combined
):
boost::for_each(
boost::combined(myVector, myOtherVector) | strided(2), doSomethingElse)
Вот важный ответ из блога Herb Sutter: Почему нет алгоритмов на основе контейнеров . Он показывает контрпримеры, как это сделал Бо Перссон в своем ответе выше.