Commit fbba622d authored by Lewis Baker's avatar Lewis Baker Committed by Facebook Github Bot

Remove iterator interface for folly::coro::AsyncGenerator

Summary:
Simplified the interface for consuming an AsyncGenerator to now just have a single async next() method instead of an iterator-based API.

The iterator-based API was added to integrate with the 'for co_await' syntax present in the Coroutines TS, however this syntax has been removed from C++20 to allow for future exploration of the AsyncRanges design-space.

The `co_await gen.next()` expression produces an optional-like object that lets you query whether the result is a sentinel or contains a value.

Old:
```
Task<void> consume(AsyncGenerator<T> gen) {
  for (auto it = co_await gen.begin();
       it != gen.end();
       co_await ++it) {
    use(*it);
  }
}
```

New:
```
Task<void> consume(AsyncGenerator<T> gen) {
  while (auto item = co_await gen.next()) {
    use(*item);
  }
}
```

Reviewed By: andriigrynenko, kirkshoop

Differential Revision: D16586151

fbshipit-source-id: 4b0bf31ba9291d894a18e9553513eddee0cde33a
parent 9cd581b6
This diff is collapsed.
...@@ -32,17 +32,11 @@ ...@@ -32,17 +32,11 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
// AsyncGenerator's iterator type is move-only.
static_assert(!std::is_copy_constructible_v<
folly::coro::AsyncGenerator<int>::async_iterator>);
static_assert(std::is_move_constructible_v<
folly::coro::AsyncGenerator<int>::async_iterator>);
TEST(AsyncGenerator, DefaultConstructedGeneratorIsEmpty) { TEST(AsyncGenerator, DefaultConstructedGeneratorIsEmpty) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> { folly::coro::blockingWait([]() -> folly::coro::Task<void> {
folly::coro::AsyncGenerator<int> g; folly::coro::AsyncGenerator<int> g;
auto it = co_await g.begin(); auto result = co_await g.next();
CHECK(it == g.end()); CHECK(!result);
}()); }());
} }
...@@ -79,11 +73,11 @@ TEST(AsyncGenerator, PartiallyConsumingSequenceDestroysObjectsInScope) { ...@@ -79,11 +73,11 @@ TEST(AsyncGenerator, PartiallyConsumingSequenceDestroysObjectsInScope) {
auto gen = makeGenerator(); auto gen = makeGenerator();
CHECK(!started); CHECK(!started);
CHECK(!destroyed); CHECK(!destroyed);
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK(started); CHECK(started);
CHECK(!destroyed); CHECK(!destroyed);
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(1, *it); CHECK_EQ(1, *result);
} }
CHECK(destroyed); CHECK(destroyed);
}()); }());
...@@ -98,20 +92,20 @@ TEST(AsyncGenerator, FullyConsumeSequence) { ...@@ -98,20 +92,20 @@ TEST(AsyncGenerator, FullyConsumeSequence) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(0, *it); CHECK_EQ(0, *result);
co_await(++it); result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(1, *it); CHECK_EQ(1, *result);
co_await(++it); result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(2, *it); CHECK_EQ(2, *result);
co_await(++it); result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(3, *it); CHECK_EQ(3, *result);
co_await(++it); result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -131,7 +125,7 @@ TEST(AsyncGenerator, ThrowExceptionBeforeFirstYield) { ...@@ -131,7 +125,7 @@ TEST(AsyncGenerator, ThrowExceptionBeforeFirstYield) {
auto gen = makeGenerator(); auto gen = makeGenerator();
bool caughtException = false; bool caughtException = false;
try { try {
(void)co_await gen.begin(); (void)co_await gen.next();
CHECK(false); CHECK(false);
} catch (const SomeError&) { } catch (const SomeError&) {
caughtException = true; caughtException = true;
...@@ -148,12 +142,12 @@ TEST(AsyncGenerator, ThrowExceptionAfterFirstYield) { ...@@ -148,12 +142,12 @@ TEST(AsyncGenerator, ThrowExceptionAfterFirstYield) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
CHECK_EQ(42, *it); CHECK_EQ(42, *result);
bool caughtException = false; bool caughtException = false;
try { try {
(void)co_await++ it; (void)co_await gen.next();
CHECK(false); CHECK(false);
} catch (const SomeError&) { } catch (const SomeError&) {
caughtException = true; caughtException = true;
...@@ -172,8 +166,8 @@ TEST(AsyncGenerator, ConsumingManySynchronousElementsDoesNotOverflowStack) { ...@@ -172,8 +166,8 @@ TEST(AsyncGenerator, ConsumingManySynchronousElementsDoesNotOverflowStack) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
std::uint64_t sum = 0; std::uint64_t sum = 0;
for (auto it = co_await gen.begin(); it != gen.end(); co_await++ it) { while (auto result = co_await gen.next()) {
sum += *it; sum += *result;
} }
CHECK_EQ(499999500000u, sum); CHECK_EQ(499999500000u, sum);
}()); }());
...@@ -198,12 +192,12 @@ TEST(AsyncGenerator, ProduceResultsAsynchronously) { ...@@ -198,12 +192,12 @@ TEST(AsyncGenerator, ProduceResultsAsynchronously) {
}; };
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK_EQ(1, *it); CHECK_EQ(1, *result);
co_await++ it; result = co_await gen.next();
CHECK_EQ(2, *it); CHECK_EQ(2, *result);
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -229,13 +223,13 @@ TEST(AsyncGenerator, GeneratorOfLValueReference) { ...@@ -229,13 +223,13 @@ TEST(AsyncGenerator, GeneratorOfLValueReference) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK_EQ(10, *it); CHECK_EQ(10, result.value());
*it = 20; *result = 20;
co_await++ it; result = co_await gen.next();
CHECK_EQ(30, *it); CHECK_EQ(30, result.value());
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result.has_value());
}()); }());
} }
...@@ -259,14 +253,14 @@ TEST(AsyncGenerator, GeneratorOfConstLValueReference) { ...@@ -259,14 +253,14 @@ TEST(AsyncGenerator, GeneratorOfConstLValueReference) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK_EQ(10, *it); CHECK_EQ(10, *result);
co_await++ it; result = co_await gen.next();
CHECK_EQ(30, *it); CHECK_EQ(30, *result);
co_await++ it; result = co_await gen.next();
CHECK_EQ(99, *it); CHECK_EQ(99, *result);
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -283,16 +277,16 @@ TEST(AsyncGenerator, GeneratorOfRValueReference) { ...@@ -283,16 +277,16 @@ TEST(AsyncGenerator, GeneratorOfRValueReference) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK_EQ(10, **it); CHECK_EQ(10, **result);
// Don't move it to a local var. // Don't move it to a local var.
co_await++ it; result = co_await gen.next();
CHECK_EQ(20, **it); CHECK_EQ(20, **result);
auto ptr = *it; // Move it to a local var. auto ptr = *result; // Move it to a local var.
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -321,17 +315,17 @@ TEST(AsyncGenerator, GeneratorOfMoveOnlyType) { ...@@ -321,17 +315,17 @@ TEST(AsyncGenerator, GeneratorOfMoveOnlyType) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
// NOTE: It's an error to dereference using '*it' as this returns a copy // NOTE: It's an error to dereference using '*it' as this returns a copy
// of the iterator's reference type, which in this case is 'MoveOnly'. // of the iterator's reference type, which in this case is 'MoveOnly'.
CHECK_EQ(1, it->value()); CHECK_EQ(1, result->value());
co_await++ it; result = co_await gen.next();
CHECK_EQ(2, it->value()); CHECK_EQ(2, result->value());
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -349,15 +343,15 @@ TEST(AsyncGenerator, GeneratorOfConstValue) { ...@@ -349,15 +343,15 @@ TEST(AsyncGenerator, GeneratorOfConstValue) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK_EQ(42, *it); CHECK_EQ(42, *result);
static_assert(std::is_same_v<decltype(*it), int>); static_assert(std::is_same_v<decltype(*result), const int&>);
co_await++ it; result = co_await gen.next();
CHECK_EQ(123, *it); CHECK_EQ(123, *result);
co_await++ it; result = co_await gen.next();
CHECK_EQ(99, *it); CHECK_EQ(99, *result);
co_await++ it; result = co_await gen.next();
CHECK(it == gen.end()); CHECK(!result);
}()); }());
} }
...@@ -376,15 +370,15 @@ TEST(AsyncGenerator, ExplicitValueType) { ...@@ -376,15 +370,15 @@ TEST(AsyncGenerator, ExplicitValueType) {
folly::coro::blockingWait([&]() -> folly::coro::Task<void> { folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
auto gen = makeGenerator(); auto gen = makeGenerator();
auto it = co_await gen.begin(); auto result = co_await gen.next();
{ {
auto [kRef, vRef] = *it; auto [kRef, vRef] = *result;
CHECK_EQ("bar", kRef); CHECK_EQ("bar", kRef);
CHECK_EQ("goodbye", vRef); CHECK_EQ("goodbye", vRef);
decltype(gen)::value_type copy = *it; decltype(gen)::value_type copy = *result;
vRef = "au revoir"; vRef = "au revoir";
CHECK_EQ("goodbye", std::get<1>(copy)); CHECK_EQ("goodbye", std::get<1>(copy));
CHECK_EQ("au revoir", std::get<1>(*it)); CHECK_EQ("au revoir", std::get<1>(*result));
} }
}()); }());
...@@ -400,9 +394,9 @@ TEST(AsyncGenerator, InvokeLambda) { ...@@ -400,9 +394,9 @@ TEST(AsyncGenerator, InvokeLambda) {
co_yield std::move(p); co_yield std::move(p);
}); });
auto it = co_await gen.begin(); auto result = co_await gen.next();
CHECK(it != gen.end()); CHECK(result);
ptr = *it; ptr = *result;
CHECK(ptr); CHECK(ptr);
CHECK(*ptr == 123); CHECK(*ptr == 123);
}()); }());
......
...@@ -30,20 +30,16 @@ class AsyncGeneratorWrapper { ...@@ -30,20 +30,16 @@ class AsyncGeneratorWrapper {
: gen_(std::move(gen)) {} : gen_(std::move(gen)) {}
coro::Task<Optional<T>> getNext() { coro::Task<Optional<T>> getNext() {
if (!iter_) { auto item = co_await gen_.next();
iter_ = co_await gen_.begin(); if (item) {
co_return std::move(item).value();
} else { } else {
co_await(++*iter_);
}
if (iter_ == gen_.end()) {
co_return none; co_return none;
} }
co_return(**iter_);
} }
private: private:
coro::AsyncGenerator<T> gen_; coro::AsyncGenerator<T> gen_;
Optional<typename coro::AsyncGenerator<T>::async_iterator> iter_;
}; };
} // namespace python } // namespace python
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment