Commit d6217b2f authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

support using co_await with folly::Optional when it is available

Summary:
Coroutines can be used to simulate the monadic "do" notion of Haskell. Use coroutines to enable monadic composition with folly::Optional.

```
Optional<int> f() {
  return 7;
}
Optional<int> g(int) {
  return folly::none;
}

void MonadicOptional() {
  Optional<int> r = []() -> Optional<int> {
    int x = co_await f();
    assert(x == 7);
    int y = co_await g(x);
    assert(false); // not executed
    co_return y;
  }();
  assert(!r.hasValue());
}
```

Reviewed By: yfeldblum

Differential Revision: D5763371

fbshipit-source-id: 9babc682244f38da7006d0b3a8444fd4efec1747
parent e74c15ff
...@@ -67,6 +67,9 @@ ...@@ -67,6 +67,9 @@
namespace folly { namespace folly {
template <class Value>
class Optional;
namespace detail { namespace detail {
struct NoneHelper {}; struct NoneHelper {};
...@@ -74,7 +77,10 @@ struct NoneHelper {}; ...@@ -74,7 +77,10 @@ struct NoneHelper {};
// If exceptions are disabled, std::terminate() will be called instead of // If exceptions are disabled, std::terminate() will be called instead of
// throwing OptionalEmptyException when the condition fails. // throwing OptionalEmptyException when the condition fails.
[[noreturn]] void throw_optional_empty_exception(); [[noreturn]] void throw_optional_empty_exception();
}
template <class Value>
struct OptionalPromiseReturn;
} // namespace detail
typedef int detail::NoneHelper::*None; typedef int detail::NoneHelper::*None;
...@@ -133,6 +139,12 @@ class Optional { ...@@ -133,6 +139,12 @@ class Optional {
storage_.construct(std::forward<Args>(args)...); storage_.construct(std::forward<Args>(args)...);
} }
// Used only when an Optional is used with coroutines on MSVC
/* implicit */ Optional(const detail::OptionalPromiseReturn<Value>& p)
: Optional{} {
p.promise_->value_ = this;
}
void assign(const None&) { void assign(const None&) {
clear(); clear();
} }
...@@ -519,3 +531,95 @@ struct hash<folly::Optional<T>> { ...@@ -519,3 +531,95 @@ struct hash<folly::Optional<T>> {
} }
}; };
FOLLY_NAMESPACE_STD_END FOLLY_NAMESPACE_STD_END
// Enable the use of folly::Optional with `co_await`
// Inspired by https://github.com/toby-allsopp/coroutine_monad
#if FOLLY_HAS_COROUTINES
#include <experimental/coroutine>
namespace folly {
namespace detail {
template <typename Value>
struct OptionalPromise;
template <typename Value>
struct OptionalPromiseReturn {
Optional<Value> storage_;
OptionalPromise<Value>* promise_;
/* implicit */ OptionalPromiseReturn(OptionalPromise<Value>& promise) noexcept
: promise_(&promise) {
promise.value_ = &storage_;
}
OptionalPromiseReturn(OptionalPromiseReturn&& that) noexcept
: OptionalPromiseReturn{*that.promise_} {}
~OptionalPromiseReturn() {}
/* implicit */ operator Optional<Value>() & {
return std::move(storage_);
}
};
template <typename Value>
struct OptionalPromise {
Optional<Value>* value_ = nullptr;
OptionalPromise() = default;
OptionalPromise(OptionalPromise const&) = delete;
// This should work regardless of whether the compiler generates:
// folly::Optional<Value> retobj{ p.get_return_object(); } // MSVC
// or:
// auto retobj = p.get_return_object(); // clang
OptionalPromiseReturn<Value> get_return_object() noexcept {
return *this;
}
std::experimental::suspend_never initial_suspend() const noexcept {
return {};
}
std::experimental::suspend_never final_suspend() const {
return {};
}
template <typename U>
void return_value(U&& u) {
*value_ = static_cast<U&&>(u);
}
void unhandled_exception() {
// Technically, throwing from unhandled_exception is underspecified:
// https://github.com/GorNishanov/CoroutineWording/issues/17
throw;
}
};
template <typename Value>
struct OptionalAwaitable {
Optional<Value> o_;
bool await_ready() const noexcept {
return o_.hasValue();
}
Value await_resume() {
return o_.value();
}
template <typename CoroHandle>
void await_suspend(CoroHandle h) const {
// make sure the coroutine returns an empty Optional:
h.promise().value_->clear();
// Abort the rest of the coroutine:
h.destroy();
}
};
} // namespace detail
template <typename Value>
detail::OptionalAwaitable<Value>
/* implicit */ operator co_await(Optional<Value> o) {
return {std::move(o)};
}
} // namespace folly
// This makes std::optional<Value> useable as a coroutine return type..
FOLLY_NAMESPACE_STD_BEGIN
namespace experimental {
template <typename Value, typename... Args>
struct coroutine_traits<folly::Optional<Value>, Args...> {
using promise_type = folly::detail::OptionalPromise<Value>;
};
} // experimental
FOLLY_NAMESPACE_STD_END
#endif // FOLLY_HAS_COROUTINES
...@@ -425,3 +425,7 @@ constexpr auto kMscVer = 0; ...@@ -425,3 +425,7 @@ constexpr auto kMscVer = 0;
#else #else
#define FOLLY_CPP14_CONSTEXPR inline #define FOLLY_CPP14_CONSTEXPR inline
#endif #endif
#if __cpp_coroutines >= 201703L || _MSC_VER
#define FOLLY_HAS_COROUTINES 1
#endif
/*
* Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Optional.h>
#include <folly/Portability.h>
#include <folly/portability/GTest.h>
#if FOLLY_HAS_COROUTINES
using folly::Optional;
Optional<int> f1() {
return 7;
}
Optional<double> f2(int x) {
return 2.0 * x;
}
Optional<int> f3(int x, double y) {
return (int)(x + y);
}
TEST(Optional, CoroutineSuccess) {
auto r0 = []() -> Optional<int> {
auto x = co_await f1();
EXPECT_EQ(7, x);
auto y = co_await f2(x);
EXPECT_EQ(2.0 * 7, y);
auto z = co_await f3(x, y);
EXPECT_EQ((int)(2.0 * 7 + 7), z);
co_return z;
}();
EXPECT_TRUE(r0.hasValue());
EXPECT_EQ(21, *r0);
}
Optional<int> f4(int, double) {
return folly::none;
}
TEST(Optional, CoroutineFailure) {
auto r1 = []() -> Optional<int> {
auto x = co_await f1();
auto y = co_await f2(x);
auto z = co_await f4(x, y);
EXPECT_FALSE(true);
co_return z;
}();
EXPECT_TRUE(!r1.hasValue());
}
Optional<int> throws() {
throw 42;
}
TEST(Optional, CoroutineException) {
try {
auto r2 = []() -> Optional<int> {
auto x = co_await throws();
EXPECT_FALSE(true);
co_return x;
}();
EXPECT_FALSE(true);
} catch (/* nolint */ int i) {
EXPECT_EQ(42, i);
} catch (...) {
EXPECT_FALSE(true);
}
}
#endif
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