From: Tom Jackson Date: Mon, 20 Jul 2015 21:05:59 +0000 (-0700) Subject: Remove EmptySequence exception in favor of optional returns X-Git-Tag: v0.52.0~20 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=3c26e2a31438f52c8a665e3931f71d929c966216;p=folly.git Remove EmptySequence exception in favor of optional returns Summary: Forces consumers to handle the empty sequence case instead of cumbersome exceptions. Reviewed By: @​jrichey, @yfeldblum Differential Revision: D2219505 --- diff --git a/folly/gen/Base-inl.h b/folly/gen/Base-inl.h index fe3a78f4..0c4e92f4 100644 --- a/folly/gen/Base-inl.h +++ b/folly/gen/Base-inl.h @@ -1678,7 +1678,7 @@ class Cycle : public Operator> { explicit Cycle(off_t limit) : limit_(limit) { static_assert( !forever, - "Cycle limit consturctor should not be used when forever == true."); + "Cycle limit constructor should not be used when forever == true."); } template @@ -1790,16 +1790,13 @@ class First : public Operator { template ::type> - StorageType compose(const GenImpl& source) const { + Optional compose(const GenImpl& source) const { Optional accum; source | [&](Value v) -> bool { accum = std::forward(v); return false; }; - if (!accum.hasValue()) { - throw EmptySequence(); - } - return std::move(accum.value()); + return accum; } }; @@ -1862,7 +1859,7 @@ class Reduce : public Operator> { template ::type> - StorageType compose(const GenImpl& source) const { + Optional compose(const GenImpl& source) const { static_assert(!Source::infinite, "Cannot reduce infinite source"); Optional accum; source | [&](Value v) { @@ -1872,10 +1869,7 @@ class Reduce : public Operator> { accum = std::forward(v); } }; - if (!accum.hasValue()) { - throw EmptySequence(); - } - return accum.value(); + return accum; } }; @@ -1981,7 +1975,7 @@ class Min : public Operator> { class StorageType = typename std::decay::type, class Key = typename std::decay< typename std::result_of::type>::type> - StorageType compose(const GenImpl& source) const { + Optional compose(const GenImpl& source) const { static_assert(!Source::infinite, "Calling min or max on an infinite source will cause " "an infinite loop."); @@ -1994,10 +1988,7 @@ class Min : public Operator> { min = std::forward(v); } }; - if (!min.hasValue()) { - throw EmptySequence(); - } - return min.value(); + return min; } }; @@ -2066,7 +2057,7 @@ class Collect : public Operator> { * The allocator defaults to std::allocator, so this may be used for the STL * containers by simply using operators like 'as', 'as', * 'as'. 'as', here is the helper method which is the usual means of - * consturcting this operator. + * constructing this operator. * * Example: * @@ -2093,6 +2084,126 @@ class CollectTemplate : public Operator> { } }; +/** + * UnwrapOr - For unwrapping folly::Optional values, or providing the given + * fallback value. Usually used through the 'unwrapOr' helper like so: + * + * auto best = from(scores) | max | unwrapOr(-1); + * + * Note that the fallback value needn't match the value in the Optional it is + * unwrapping. If mis-matched types are supported, the common type of the two is + * returned by value. If the types match, a reference (T&& > T& > const T&) is + * returned. + */ +template +class UnwrapOr { + public: + explicit UnwrapOr(T&& value) : value_(std::move(value)) {} + explicit UnwrapOr(const T& value) : value_(value) {} + + T& value() { return value_; } + const T& value() const { return value_; } + + private: + T value_; +}; + +template +T&& operator|(Optional&& opt, UnwrapOr&& fallback) { + if (T* p = opt.get_pointer()) { + return std::move(*p); + } + return std::move(fallback.value()); +} + +template +T& operator|(Optional& opt, UnwrapOr& fallback) { + if (T* p = opt.get_pointer()) { + return *p; + } + return fallback.value(); +} + +template +const T& operator|(const Optional& opt, const UnwrapOr& fallback) { + if (const T* p = opt.get_pointer()) { + return *p; + } + return fallback.value(); +} + +// Mixed type unwrapping always returns values, moving where possible +template ::value, + typename std::common_type::type>::type> +R operator|(Optional&& opt, UnwrapOr&& fallback) { + if (T* p = opt.get_pointer()) { + return std::move(*p); + } + return std::move(fallback.value()); +} + +template ::value, + typename std::common_type::type>::type> +R operator|(const Optional& opt, UnwrapOr&& fallback) { + if (const T* p = opt.get_pointer()) { + return *p; + } + return std::move(fallback.value()); +} + +template ::value, + typename std::common_type::type>::type> +R operator|(Optional&& opt, const UnwrapOr& fallback) { + if (T* p = opt.get_pointer()) { + return std::move(*p); + } + return fallback.value(); +} + +template ::value, + typename std::common_type::type>::type> +R operator|(const Optional& opt, const UnwrapOr& fallback) { + if (const T* p = opt.get_pointer()) { + return *p; + } + return fallback.value(); +} + +/** + * Unwrap - For unwrapping folly::Optional values in a folly::gen style. Usually + * used through the 'unwrap' instace like so: + * + * auto best = from(scores) | max | unwrap; // may throw + */ +class Unwrap {}; + +template +T&& operator|(Optional&& opt, const Unwrap&) { + return std::move(opt.value()); +} + +template +T& operator|(Optional& opt, const Unwrap&) { + return opt.value(); +} + +template +const T& operator|(const Optional& opt, const Unwrap&) { + return opt.value(); +} + } //::detail /** @@ -2193,6 +2304,8 @@ constexpr detail::Dereference dereference{}; constexpr detail::Indirect indirect{}; +constexpr detail::Unwrap unwrap{}; + inline detail::Take take(size_t count) { return detail::Take(count); } inline detail::Stride stride(size_t s) { return detail::Stride(s); } diff --git a/folly/gen/Base.h b/folly/gen/Base.h index 08a901a9..6e661f16 100644 --- a/folly/gen/Base.h +++ b/folly/gen/Base.h @@ -81,13 +81,6 @@ namespace folly { namespace gen { -class EmptySequence : public std::exception { -public: - virtual const char* what() const noexcept { - return "This operation cannot be called on an empty sequence"; - } -}; - class Less { public: template class GuardImpl; +template +class UnwrapOr; + +class Unwrap; + } /** @@ -823,6 +821,12 @@ GuardImpl guard(ErrorHandler&& handler) { return GuardImpl(std::forward(handler)); } +template::type>> +UnwrapOr unwrapOr(Fallback&& fallback) { + return UnwrapOr(std::forward(fallback)); +} + }} // folly::gen #include diff --git a/folly/gen/test/BaseTest.cpp b/folly/gen/test/BaseTest.cpp index 06acd00d..7741b053 100644 --- a/folly/gen/test/BaseTest.cpp +++ b/folly/gen/test/BaseTest.cpp @@ -165,16 +165,16 @@ TEST(Gen, Field) { std::vector xs(1); EXPECT_EQ(2, from(xs) | field(&X::a) - | first); + | sum); EXPECT_EQ(3, from(xs) | field(&X::b) - | first); + | sum); EXPECT_EQ(4, from(xs) | field(&X::c) - | first); + | sum); EXPECT_EQ(2, seq(&xs[0], &xs[0]) | field(&X::a) - | first); + | sum); // type-verification empty() | field(&X::a) | assert_type(); empty() | field(&X::a) | assert_type(); @@ -613,13 +613,14 @@ TEST(Gen, MinBy) { | minBy([](int i) -> double { double d = i - 6.8; return d * d; - })); + }) + | unwrap); } TEST(Gen, MaxBy) { auto gen = from({"three", "eleven", "four"}); - EXPECT_EQ("eleven", gen | maxBy(&strlen)); + EXPECT_EQ("eleven", gen | maxBy(&strlen) | unwrap); } TEST(Gen, Min) { @@ -708,17 +709,13 @@ TEST(Gen, Foldl) { TEST(Gen, Reduce) { int expected = 2 + 3 + 4 + 5; auto actual = seq(2, 5) | reduce(add); - EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, actual | unwrap); } TEST(Gen, ReduceBad) { auto gen = seq(1) | take(0); - try { - EXPECT_TRUE(true); - gen | reduce(add); - EXPECT_TRUE(false); - } catch (...) { - } + auto actual = gen | reduce(add); + EXPECT_FALSE(actual); // Empty sequences are okay, they just yeild 'none' } TEST(Gen, Moves) { @@ -731,10 +728,8 @@ TEST(Gen, Moves) { } TEST(Gen, First) { - auto gen = - seq(0) - | filter([](int x) { return x > 3; }); - EXPECT_EQ(4, gen | first); + auto gen = seq(0) | filter([](int x) { return x > 3; }); + EXPECT_EQ(4, gen | first | unwrap); } TEST(Gen, FromCopy) { @@ -1106,7 +1101,7 @@ TEST(Gen, Dereference) { TEST(Gen, Indirect) { vector vs{1}; - EXPECT_EQ(&vs[0], from(vs) | indirect | first); + EXPECT_EQ(&vs[0], from(vs) | indirect | first | unwrap); } TEST(Gen, Guard) { @@ -1171,24 +1166,24 @@ TEST(Gen, Just) { { int x = 3; auto j = just(x); - EXPECT_EQ(&x, j | indirect | first); + EXPECT_EQ(&x, j | indirect | first | unwrap); x = 4; - EXPECT_EQ(4, j | first); + EXPECT_EQ(4, j | sum); } { int x = 3; const int& cx = x; auto j = just(cx); - EXPECT_EQ(&x, j | indirect | first); + EXPECT_EQ(&x, j | indirect | first | unwrap); x = 5; - EXPECT_EQ(5, j | first); + EXPECT_EQ(5, j | sum); } { int x = 3; auto j = just(std::move(x)); - EXPECT_NE(&x, j | indirect | first); + EXPECT_NE(&x, j | indirect | first | unwrap); x = 5; - EXPECT_EQ(3, j | first); + EXPECT_EQ(3, j | sum); } } @@ -1202,16 +1197,115 @@ TEST(Gen, GroupBy) { EXPECT_EQ(3, gb | count); vector mode{"zero", "four", "five", "nine"}; - EXPECT_EQ( - mode, - gb | maxBy([](const Group& g) { return g.size(); }) - | as()); + EXPECT_EQ(mode, + gb | maxBy([](const Group& g) { return g.size(); }) + | unwrap + | as()); vector largest{"three", "seven", "eight"}; - EXPECT_EQ( - largest, - gb | maxBy([](const Group& g) { return g.key(); }) - | as()); + EXPECT_EQ(largest, + gb | maxBy([](const Group& g) { return g.key(); }) + | unwrap + | as()); +} + +TEST(Gen, Unwrap) { + Optional o(4); + Optional e; + EXPECT_EQ(4, o | unwrap); + EXPECT_THROW(e | unwrap, OptionalEmptyException); + + auto oup = folly::make_optional(folly::make_unique(5)); + // optional has a value, and that value is non-null + EXPECT_TRUE(oup | unwrap); + EXPECT_EQ(5, *(oup | unwrap)); + EXPECT_TRUE(oup.hasValue()); // still has a pointer (null or not) + EXPECT_TRUE(oup.value()); // that value isn't null + + auto moved1 = std::move(oup) | unwrapOr(folly::make_unique(6)); + // oup still has a value, but now it's now nullptr since the pointer was moved + // into moved1 + EXPECT_TRUE(oup.hasValue()); + EXPECT_FALSE(oup.value()); + EXPECT_TRUE(moved1); + EXPECT_EQ(5, *moved1); + + auto moved2 = std::move(oup) | unwrapOr(folly::make_unique(7)); + // oup's still-valid nullptr value wins here, the pointer to 7 doesn't apply + EXPECT_FALSE(moved2); + + oup.clear(); + auto moved3 = std::move(oup) | unwrapOr(folly::make_unique(8)); + // oup is empty now, so the unwrapOr comes into play. + EXPECT_TRUE(moved3); + EXPECT_EQ(8, *moved3); + + { + // mixed types, with common type matching optional + Optional full(3.3); + decltype(full) empty; + auto fallback = unwrapOr(4); + EXPECT_EQ(3.3, full | fallback); + EXPECT_EQ(3.3, std::move(full) | fallback); + EXPECT_EQ(3.3, full | std::move(fallback)); + EXPECT_EQ(3.3, std::move(full) | std::move(fallback)); + EXPECT_EQ(4.0, empty | fallback); + EXPECT_EQ(4.0, std::move(empty) | fallback); + EXPECT_EQ(4.0, empty | std::move(fallback)); + EXPECT_EQ(4.0, std::move(empty) | std::move(fallback)); + } + + { + // mixed types, with common type matching fallback + Optional full(3); + decltype(full) empty; + auto fallback = unwrapOr(5.0); // type: double + // if we chose 'int' as the common type, we'd see truncation here + EXPECT_EQ(1.5, (full | fallback) / 2); + EXPECT_EQ(1.5, (std::move(full) | fallback) / 2); + EXPECT_EQ(1.5, (full | std::move(fallback)) / 2); + EXPECT_EQ(1.5, (std::move(full) | std::move(fallback)) / 2); + EXPECT_EQ(2.5, (empty | fallback) / 2); + EXPECT_EQ(2.5, (std::move(empty) | fallback) / 2); + EXPECT_EQ(2.5, (empty | std::move(fallback)) / 2); + EXPECT_EQ(2.5, (std::move(empty) | std::move(fallback)) / 2); + } + + { + auto opt = folly::make_optional(std::make_shared(8)); + auto fallback = unwrapOr(folly::make_unique(9)); + // fallback must be std::move'd to be used + EXPECT_EQ(8, *(opt | std::move(fallback))); + EXPECT_TRUE(opt.value()); // shared_ptr copied out, not moved + EXPECT_TRUE(opt); // value still present + EXPECT_TRUE(fallback.value()); // fallback value not needed + + EXPECT_EQ(8, *(std::move(opt) | std::move(fallback))); + EXPECT_FALSE(opt.value()); // shared_ptr moved out + EXPECT_TRUE(opt); // gutted value still present + EXPECT_TRUE(fallback.value()); // fallback value not needed + + opt.clear(); + + EXPECT_FALSE(opt); // opt is empty now + EXPECT_EQ(9, *(std::move(opt) | std::move(fallback))); + EXPECT_FALSE(fallback.value()); // fallback moved out! + } + + { + // test with nullptr + vector v{1, 2}; + EXPECT_EQ(&v[1], from(v) | indirect | max | unwrap); + v.clear(); + EXPECT_FALSE(from(v) | indirect | max | unwrapOr(nullptr)); + } + + { + // mixed type determined by fallback + Optional empty; + int x = 3; + EXPECT_EQ(&x, empty | unwrapOr(&x)); + } } int main(int argc, char *argv[]) {