From: Tom Jackson Date: Thu, 20 Jul 2017 00:22:27 +0000 (-0700) Subject: Visit, for monitoring or mutating pipelines X-Git-Tag: v2017.07.24.00~18 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e1fb97e7c0bf8176ff9b72493ecf14d7781559b0;p=folly.git Visit, for monitoring or mutating pipelines Summary: The pattern `mapped([](auto&& i) { ...; return std::move(i); }` is quite common. Evidently people need some good mechanism to just observe or possibly mutate items in pipelines. Note that this is still lazy, it is only called when the pipeline is run. Reviewed By: yfeldblum Differential Revision: D5457196 fbshipit-source-id: 3a892b8895e02dd8fcd6a7fd4d3b27063d1b071f --- diff --git a/folly/gen/Base-inl.h b/folly/gen/Base-inl.h index c654cbcd..9f7c5d19 100644 --- a/folly/gen/Base-inl.h +++ b/folly/gen/Base-inl.h @@ -470,7 +470,7 @@ class SingleCopy : public GenImpl> { * * This type is usually used through the 'map' or 'mapped' helper function: * - * auto squares = seq(1, 10) | map(square) | asVector; + * auto squares = seq(1, 10) | map(square) | as(); */ template class Map : public Operator> { @@ -597,7 +597,7 @@ class Filter : public Operator> { * * auto best = from(sortedItems) * | until([](Item& item) { return item.score > 100; }) - * | asVector; + * | as(); */ template class Until : public Operator> { @@ -703,6 +703,65 @@ class Take : public Operator { } }; +/** + * Visit - For calling a function on each item before passing it down the + * pipeline. + * + * This type is usually used through the 'visit' helper function: + * + * auto printedValues = seq(1) | visit(debugPrint); + * // nothing printed yet + * auto results = take(10) | as(); + * // results now populated, 10 values printed + */ +template +class Visit : public Operator> { + Visitor visitor_; + + public: + Visit() = default; + + explicit Visit(Visitor visitor) : visitor_(std::move(visitor)) {} + + template + class Generator : public GenImpl> { + Source source_; + Visitor visitor_; + + public: + explicit Generator(Source source, const Visitor& visitor) + : source_(std::move(source)), visitor_(visitor) {} + + template + void foreach(Body&& body) const { + source_.foreach([&](Value value) { + visitor_(value); // not forwarding to avoid accidental moves + body(std::forward(value)); + }); + } + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Value value) { + visitor_(value); // not forwarding to avoid accidental moves + return handler(std::forward(value)); + }); + } + + static constexpr bool infinite = Source::infinite; + }; + + template > + Gen compose(GenImpl&& source) const { + return Gen(std::move(source.self()), visitor_); + } + + template > + Gen compose(const GenImpl& source) const { + return Gen(source.self(), visitor_); + } +}; + /** * Stride - For producing every Nth value from a source. * diff --git a/folly/gen/Base.h b/folly/gen/Base.h index 625cd060..279ec0d2 100644 --- a/folly/gen/Base.h +++ b/folly/gen/Base.h @@ -330,6 +330,9 @@ class Sample; class Skip; +template +class Visit; + template class Order; @@ -634,6 +637,11 @@ Filter filter(Predicate pred = Predicate()) { return Filter(std::move(pred)); } +template > +Visit visit(Visitor visitor = Visitor()) { + return Visit(std::move(visitor)); +} + template > Until until(Predicate pred = Predicate()) { return Until(std::move(pred)); @@ -742,7 +750,6 @@ Composed any(Predicate pred = Predicate()) { * * from(source) | all(pred) == from(source) | filter(negate(pred)) | isEmpty */ - template < class Predicate = Identity, class Filter = detail::Filter>, @@ -814,6 +821,7 @@ template < UnwrapOr unwrapOr(Fallback&& fallback) { return UnwrapOr(std::forward(fallback)); } + } // gen } // folly diff --git a/folly/gen/test/BaseTest.cpp b/folly/gen/test/BaseTest.cpp index e7082366..8e0358c2 100644 --- a/folly/gen/test/BaseTest.cpp +++ b/folly/gen/test/BaseTest.cpp @@ -475,7 +475,35 @@ TEST(Gen, Until) { | as>(); EXPECT_EQ(expected, actual); } - */ + */ +} + +TEST(Gen, Visit) { + auto increment = [](int& i) { ++i; }; + auto clone = map([](int i) { return i; }); + { // apply() + auto expected = 10; + auto actual = seq(0) | clone | visit(increment) | take(4) | sum; + EXPECT_EQ(expected, actual); + } + { // foreach() + auto expected = 10; + auto actual = seq(0, 3) | clone | visit(increment) | sum; + EXPECT_EQ(expected, actual); + } + { // tee-like + std::vector x2, x4; + std::vector expected2{0, 1, 4, 9}; + std::vector expected4{0, 1, 16, 81}; + + auto tee = [](std::vector& container) { + return visit([&](int value) { container.push_back(value); }); + }; + EXPECT_EQ( + 98, seq(0, 3) | map(square) | tee(x2) | map(square) | tee(x4) | sum); + EXPECT_EQ(expected2, x2); + EXPECT_EQ(expected4, x4); + } } TEST(Gen, Composed) { @@ -664,7 +692,7 @@ TEST(Gen, FromRValue) { // reference of a std::vector when it is used as the 'other' for an rvalue // constructor. Use fbvector because we're sure its size will be zero in // this case. - fbvector v({1,2,3,4}); + fbvector v({1, 2, 3, 4}); auto q1 = from(v); EXPECT_EQ(v.size(), 4); // ensure that the lvalue version was called! auto expected = 1 * 2 * 3 * 4; @@ -676,11 +704,11 @@ TEST(Gen, FromRValue) { } { auto expected = 7; - auto q = from([] {return vector({3,7,5}); }()); + auto q = from([] { return vector({3, 7, 5}); }()); EXPECT_EQ(expected, q | max); } { - for (auto size: {5, 1024, 16384, 1<<20}) { + for (auto size : {5, 1024, 16384, 1 << 20}) { auto q1 = from(vector(size, 2)); auto q2 = from(vector(size, 3)); // If the rvalue specialization is broken/gone, then the compiler will