Visit, for monitoring or mutating pipelines
authorTom Jackson <tjackson@fb.com>
Thu, 20 Jul 2017 00:22:27 +0000 (17:22 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 20 Jul 2017 00:29:19 +0000 (17:29 -0700)
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

folly/gen/Base-inl.h
folly/gen/Base.h
folly/gen/test/BaseTest.cpp

index c654cbcd1de518a9224293a4a3ad398ce8a540f9..9f7c5d1940eb8eb0451e40fa1ddda9f643dbd5a3 100644 (file)
@@ -470,7 +470,7 @@ class SingleCopy : public GenImpl<const Value&, SingleCopy<Value>> {
  *
  * 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<std::vector>();
  */
 template <class Predicate>
 class Map : public Operator<Map<Predicate>> {
@@ -597,7 +597,7 @@ class Filter : public Operator<Filter<Predicate>> {
  *
  *   auto best = from(sortedItems)
  *             | until([](Item& item) { return item.score > 100; })
- *             | asVector;
+ *             | as<std::vector>();
  */
 template <class Predicate>
 class Until : public Operator<Until<Predicate>> {
@@ -703,6 +703,65 @@ class Take : public Operator<Take> {
   }
 };
 
+/**
+ * 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<std::vector>();
+ *   // results now populated, 10 values printed
+ */
+template <class Visitor>
+class Visit : public Operator<Visit<Visitor>> {
+  Visitor visitor_;
+
+ public:
+  Visit() = default;
+
+  explicit Visit(Visitor visitor) : visitor_(std::move(visitor)) {}
+
+  template <class Value, class Source>
+  class Generator : public GenImpl<Value, Generator<Value, Source>> {
+    Source source_;
+    Visitor visitor_;
+
+   public:
+    explicit Generator(Source source, const Visitor& visitor)
+        : source_(std::move(source)), visitor_(visitor) {}
+
+    template <class Body>
+    void foreach(Body&& body) const {
+      source_.foreach([&](Value value) {
+        visitor_(value); // not forwarding to avoid accidental moves
+        body(std::forward<Value>(value));
+      });
+    }
+
+    template <class Handler>
+    bool apply(Handler&& handler) const {
+      return source_.apply([&](Value value) {
+        visitor_(value); // not forwarding to avoid accidental moves
+        return handler(std::forward<Value>(value));
+      });
+    }
+
+    static constexpr bool infinite = Source::infinite;
+  };
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(GenImpl<Value, Source>&& source) const {
+    return Gen(std::move(source.self()), visitor_);
+  }
+
+  template <class Source, class Value, class Gen = Generator<Value, Source>>
+  Gen compose(const GenImpl<Value, Source>& source) const {
+    return Gen(source.self(), visitor_);
+  }
+};
+
 /**
  * Stride - For producing every Nth value from a source.
  *
index 625cd0609d2a58d49895bac3c4c5f51ff531d6e6..279ec0d21510fbfddb8e0c6dd8fb01f2af4a585a 100644 (file)
@@ -330,6 +330,9 @@ class Sample;
 
 class Skip;
 
+template <class Visitor>
+class Visit;
+
 template <class Selector, class Comparer = Less>
 class Order;
 
@@ -634,6 +637,11 @@ Filter filter(Predicate pred = Predicate()) {
   return Filter(std::move(pred));
 }
 
+template <class Visitor = Ignore, class Visit = detail::Visit<Visitor>>
+Visit visit(Visitor visitor = Visitor()) {
+  return Visit(std::move(visitor));
+}
+
 template <class Predicate, class Until = detail::Until<Predicate>>
 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<Negate<Predicate>>,
@@ -814,6 +821,7 @@ template <
 UnwrapOr unwrapOr(Fallback&& fallback) {
   return UnwrapOr(std::forward<Fallback>(fallback));
 }
+
 } // gen
 } // folly
 
index e7082366152707e7253280c19564d93abe4d4a0b..8e0358c2d4375d44f266291fcded775c051e1037 100644 (file)
@@ -475,7 +475,35 @@ TEST(Gen, Until) {
       | as<vector<int>>();
     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<int> x2, x4;
+    std::vector<int> expected2{0, 1, 4, 9};
+    std::vector<int> expected4{0, 1, 16, 81};
+
+    auto tee = [](std::vector<int>& 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<int> v({1,2,3,4});
+    fbvector<int> 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<int>({3,7,5}); }());
+    auto q = from([] { return vector<int>({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<int>(size, 2));
       auto q2 = from(vector<int>(size, 3));
       // If the rvalue specialization is broken/gone, then the compiler will