From d6217b2ffca0cfa0cc50c0321e067296a162938d Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Fri, 8 Sep 2017 12:36:10 -0700 Subject: [PATCH] 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 f() { return 7; } Optional g(int) { return folly::none; } void MonadicOptional() { Optional r = []() -> Optional { 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 --- folly/Optional.h | 106 +++++++++++++++++++++++++- folly/Portability.h | 4 + folly/test/OptionalCoroutinesTest.cpp | 81 ++++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 folly/test/OptionalCoroutinesTest.cpp diff --git a/folly/Optional.h b/folly/Optional.h index 049251ba..a57f065a 100644 --- a/folly/Optional.h +++ b/folly/Optional.h @@ -67,6 +67,9 @@ namespace folly { +template +class Optional; + namespace detail { struct NoneHelper {}; @@ -74,7 +77,10 @@ struct NoneHelper {}; // If exceptions are disabled, std::terminate() will be called instead of // throwing OptionalEmptyException when the condition fails. [[noreturn]] void throw_optional_empty_exception(); -} + +template +struct OptionalPromiseReturn; +} // namespace detail typedef int detail::NoneHelper::*None; @@ -133,6 +139,12 @@ class Optional { storage_.construct(std::forward(args)...); } + // Used only when an Optional is used with coroutines on MSVC + /* implicit */ Optional(const detail::OptionalPromiseReturn& p) + : Optional{} { + p.promise_->value_ = this; + } + void assign(const None&) { clear(); } @@ -519,3 +531,95 @@ struct hash> { } }; 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 + +namespace folly { +namespace detail { +template +struct OptionalPromise; + +template +struct OptionalPromiseReturn { + Optional storage_; + OptionalPromise* promise_; + /* implicit */ OptionalPromiseReturn(OptionalPromise& promise) noexcept + : promise_(&promise) { + promise.value_ = &storage_; + } + OptionalPromiseReturn(OptionalPromiseReturn&& that) noexcept + : OptionalPromiseReturn{*that.promise_} {} + ~OptionalPromiseReturn() {} + /* implicit */ operator Optional() & { + return std::move(storage_); + } +}; + +template +struct OptionalPromise { + Optional* value_ = nullptr; + OptionalPromise() = default; + OptionalPromise(OptionalPromise const&) = delete; + // This should work regardless of whether the compiler generates: + // folly::Optional retobj{ p.get_return_object(); } // MSVC + // or: + // auto retobj = p.get_return_object(); // clang + OptionalPromiseReturn get_return_object() noexcept { + return *this; + } + std::experimental::suspend_never initial_suspend() const noexcept { + return {}; + } + std::experimental::suspend_never final_suspend() const { + return {}; + } + template + void return_value(U&& u) { + *value_ = static_cast(u); + } + void unhandled_exception() { + // Technically, throwing from unhandled_exception is underspecified: + // https://github.com/GorNishanov/CoroutineWording/issues/17 + throw; + } +}; + +template +struct OptionalAwaitable { + Optional o_; + bool await_ready() const noexcept { + return o_.hasValue(); + } + Value await_resume() { + return o_.value(); + } + template + 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 +detail::OptionalAwaitable +/* implicit */ operator co_await(Optional o) { + return {std::move(o)}; +} +} // namespace folly + +// This makes std::optional useable as a coroutine return type.. +FOLLY_NAMESPACE_STD_BEGIN +namespace experimental { +template +struct coroutine_traits, Args...> { + using promise_type = folly::detail::OptionalPromise; +}; +} // experimental +FOLLY_NAMESPACE_STD_END +#endif // FOLLY_HAS_COROUTINES diff --git a/folly/Portability.h b/folly/Portability.h index a180bea4..87be0b29 100644 --- a/folly/Portability.h +++ b/folly/Portability.h @@ -425,3 +425,7 @@ constexpr auto kMscVer = 0; #else #define FOLLY_CPP14_CONSTEXPR inline #endif + +#if __cpp_coroutines >= 201703L || _MSC_VER +#define FOLLY_HAS_COROUTINES 1 +#endif diff --git a/folly/test/OptionalCoroutinesTest.cpp b/folly/test/OptionalCoroutinesTest.cpp new file mode 100644 index 00000000..92c6ba60 --- /dev/null +++ b/folly/test/OptionalCoroutinesTest.cpp @@ -0,0 +1,81 @@ +/* + * 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 +#include +#include + +#if FOLLY_HAS_COROUTINES +using folly::Optional; + +Optional f1() { + return 7; +} +Optional f2(int x) { + return 2.0 * x; +} +Optional f3(int x, double y) { + return (int)(x + y); +} + +TEST(Optional, CoroutineSuccess) { + auto r0 = []() -> Optional { + 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 f4(int, double) { + return folly::none; +} + +TEST(Optional, CoroutineFailure) { + auto r1 = []() -> Optional { + 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 throws() { + throw 42; +} + +TEST(Optional, CoroutineException) { + try { + auto r2 = []() -> Optional { + 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 -- 2.34.1