Changing behavior of 'any' and 'all' sinks, adding in 'isEmpty' and 'notEmpty' sinks
authorJoe Richey <jrichey@fb.com>
Tue, 14 Jul 2015 20:50:24 +0000 (13:50 -0700)
committerSara Golemon <sgolemon@fb.com>
Wed, 15 Jul 2015 20:25:11 +0000 (13:25 -0700)
Summary: When adding in the 'filter()' default behavior, I considered adding in similar
behavior for 'any' and 'all'. However, we had 'any' with no funciton call
basically check if anything was present, not testing a predicate. This can
create a confusing senario, so I removed this behavior from 'any' and added in
the 'isEmpty' and 'notEmpty' sinks. Now the calls 'any()' and 'all()' (called
with parens, so old uses won't compile) check for truthy values simlar to
'filter()'.

I also added some unit tests and changed 'static const' objects to 'constexpr'.

Reviewed By: @ddrcoder

Differential Revision: D2234637

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

index 3827fdf4216998187523dd8baaa64eae9b538fcd..7a26fedeb512be83c4c094936e8b438149492304 100644 (file)
@@ -1340,78 +1340,32 @@ class First : public Operator<First> {
   }
 };
 
-
 /**
- * Any - For determining whether any values in a sequence satisfy a predicate.
- *
- * This type is primarily used through the 'any' static value, like:
- *
- *   bool any20xPrimes = seq(200, 210) | filter(isPrime) | any;
- *
- * Note that it may also be used like so:
- *
- *   bool any20xPrimes = seq(200, 210) | any(isPrime);
+ * IsEmpty - a helper class for isEmpty and notEmpty
  *
+ * Essentially returns 'result' if the source is empty. Note that this cannot be
+ * called on an infinite source, because then there is only one possible return
+ * value.
  */
-class Any : public Operator<Any> {
+template <bool emptyResult>
+class IsEmpty : public Operator<IsEmpty<emptyResult>> {
  public:
-  Any() = default;
+  IsEmpty() = default;
 
   template<class Source,
            class Value>
   bool compose(const GenImpl<Value, Source>& source) const {
-    bool any = false;
+    static_assert(!Source::infinite,
+                  "Cannot call 'all', 'any', 'isEmpty', or 'notEmpty' on "
+                  "infinite source. 'all' and 'isEmpty' will either return "
+                  "false or hang. 'any' or 'notEmpty' will either return true "
+                  "or hang.");
+    bool ans = emptyResult;
     source | [&](Value v) -> bool {
-      any = true;
+      ans = !emptyResult;
       return false;
     };
-    return any;
-  }
-
-  /**
-   * Convenience function for use like:
-   *
-   *  bool found = gen | any([](int i) { return i * i > 100; });
-   */
-  template<class Predicate,
-           class Filter = Filter<Predicate>,
-           class Composed = Composed<Filter, Any>>
-  Composed operator()(Predicate pred) const {
-    return Composed(Filter(std::move(pred)), Any());
-  }
-};
-
-/**
- * All - For determining whether all values in a sequence satisfy a predicate.
- *
- * This type is primarily used through the 'any' static value, like:
- *
- *   bool valid = from(input) | all(validate);
- *
- * Note: Passing an empty sequence through 'all()' will always return true.
- */
-template<class Predicate>
-class All : public Operator<All<Predicate>> {
-  Predicate pred_;
- public:
-  All() = default;
-  explicit All(Predicate pred)
-    : pred_(std::move(pred))
-  { }
-
-  template<class Source,
-           class Value>
-  bool compose(const GenImpl<Value, Source>& source) const {
-    static_assert(!Source::infinite, "Cannot call 'all' on infinite source");
-    bool all = true;
-    source | [&](Value v) -> bool {
-      if (!pred_(std::forward<Value>(v))) {
-        all = false;
-        return false;
-      }
-      return true;
-    };
-    return all;
+    return ans;
   }
 };
 
@@ -1486,7 +1440,7 @@ class Count : public Operator<Count> {
  */
 class Sum : public Operator<Sum> {
  public:
-  Sum() : Operator<Sum>() {}
+  Sum() = default;
 
   template<class Source,
            class Value,
@@ -1878,21 +1832,29 @@ class GuardImpl : public Operator<GuardImpl<Exception, ErrorHandler>> {
  *     = from(samples)
  *     | cycle
  *     | take(100);
+ *
+ * or in the finite case:
+ *
+ *   auto thrice = g | cycle(3);
  */
-class Cycle : public Operator<Cycle> {
-  off_t limit_; // -1 for infinite
+template <bool forever>
+class Cycle : public Operator<Cycle<forever>> {
+  off_t limit_; // not used if forever == true
  public:
-  Cycle()
-    : limit_(-1) { }
+  Cycle() = default;
 
   explicit Cycle(off_t limit)
-    : limit_(limit) { }
+    : limit_(limit) {
+      static_assert(
+          !forever,
+          "Cycle limit consturctor should not be used when forever == true.");
+    }
 
   template<class Value,
            class Source>
   class Generator : public GenImpl<Value, Generator<Value, Source>> {
     Source source_;
-    off_t limit_; // -1 for infinite
+    off_t limit_;
   public:
     explicit Generator(Source source, off_t limit)
       : source_(std::move(source))
@@ -1905,7 +1867,8 @@ class Cycle : public Operator<Cycle> {
         cont = handler(std::forward<Value>(value));
         return cont;
       };
-      for (off_t count = 0; count != limit_; ++count) {
+      // Becomes an infinte loop if forever == true
+      for (off_t count = 0; (forever || count != limit_); ++count) {
         cont = false;
         source_.apply(handler2);
         if (!cont) {
@@ -1934,12 +1897,12 @@ class Cycle : public Operator<Cycle> {
   }
 
   /**
-   * Convenience function for use like:
+   * Convenience function for finite cycles used like:
    *
    *  auto tripled = gen | cycle(3);
    */
-  Cycle operator()(off_t limit) const {
-    return Cycle(limit);
+  Cycle<false> operator()(off_t limit) const {
+    return Cycle<false>(limit);
   }
 };
 
@@ -2126,46 +2089,41 @@ class VirtualGen : public GenImpl<Value, VirtualGen<Value>> {
  * non-template operators, statically defined to avoid the need for anything but
  * the header.
  */
-static const detail::Sum sum{};
+constexpr detail::Sum sum{};
 
-static const detail::Count count{};
+constexpr detail::Count count{};
 
-static const detail::First first{};
+constexpr detail::First first{};
 
 /**
- * Use directly for detecting any values, or as a function to detect values
- * which pass a predicate:
+ * Use 'isEmpty' and 'notEmpty' for detecting if there are any values or not.
  *
- *  auto nonempty = g | any;
- *  auto evens = g | any(even);
+ *  bool hasPrimes = g | filter(prime) | notEmpty;
+ *  bool lacksEvens = g | filter(even) | isEmpty;
  */
-static const detail::Any any{};
+constexpr detail::IsEmpty<true> isEmpty{};
 
-static const detail::Min<Identity, Less> min{};
+constexpr detail::IsEmpty<false> notEmpty{};
 
-static const detail::Min<Identity, Greater> max{};
+constexpr detail::Min<Identity, Less> min{};
 
-static const detail::Order<Identity> order{};
+constexpr detail::Min<Identity, Greater> max{};
 
-static const detail::Distinct<Identity> distinct{};
+constexpr detail::Order<Identity> order{};
 
-static const detail::Map<Move> move{};
+constexpr detail::Distinct<Identity> distinct{};
 
-static const detail::Concat concat{};
+constexpr detail::Map<Move> move{};
 
-static const detail::RangeConcat rconcat{};
+constexpr detail::Concat concat{};
 
-/**
- * Use directly for infinite sequences, or as a function to limit cycle count.
- *
- *  auto forever = g | cycle;
- *  auto thrice = g | cycle(3);
- */
-static const detail::Cycle cycle{};
+constexpr detail::RangeConcat rconcat{};
+
+constexpr detail::Cycle<true> cycle{};
 
-static const detail::Dereference dereference{};
+constexpr detail::Dereference dereference{};
 
-static const detail::Indirect indirect{};
+constexpr detail::Indirect indirect{};
 
 inline detail::Take take(size_t count) {
   return detail::Take(count);
index 322b895e5f853860c421b939c6861b3fced4dfe5..08a901a99a21aa9224170c2b8a7fb1508eb0461e 100644 (file)
@@ -215,6 +215,30 @@ public:
   }
 };
 
+/**
+ * Class and helper function for negating a boolean Predicate
+ */
+template <class Predicate>
+class Negate {
+  Predicate pred_;
+
+ public:
+  Negate() = default;
+
+  explicit Negate(Predicate pred)
+    : pred_(std::move(pred))
+  {}
+
+  template <class Arg>
+  bool operator()(Arg&& arg) const {
+    return !pred_(std::forward<Arg>(arg));
+  }
+};
+template <class Predicate>
+Negate<Predicate> negate(Predicate pred) {
+  return Negate<Predicate>(std::move(pred));
+}
+
 template <class Dest>
 class Cast {
  public:
@@ -346,6 +370,7 @@ class Concat;
 
 class RangeConcat;
 
+template <bool forever>
 class Cycle;
 
 class Batch;
@@ -363,10 +388,8 @@ class FoldLeft;
 
 class First;
 
-class Any;
-
-template<class Predicate>
-class All;
+template <bool result>
+class IsEmpty;
 
 template<class Reducer>
 class Reduce;
@@ -620,12 +643,6 @@ Filter filter(Predicate pred = Predicate()) {
   return Filter(std::move(pred));
 }
 
-template<class Predicate,
-         class All = detail::All<Predicate>>
-All all(Predicate pred = Predicate()) {
-  return All(std::move(pred));
-}
-
 template<class Predicate,
          class Until = detail::Until<Predicate>>
 Until until(Predicate pred = Predicate()) {
@@ -647,9 +664,9 @@ Order orderByDescending(Selector selector = Selector()) {
   return Order(std::move(selector));
 }
 
-template<class Selector,
-         class GroupBy = detail::GroupBy<Selector>>
-GroupBy groupBy(Selector selector = Identity()) {
+template <class Selector = Identity,
+          class GroupBy = detail::GroupBy<Selector>>
+GroupBy groupBy(Selector selector = Selector()) {
   return GroupBy(std::move(selector));
 }
 
@@ -687,6 +704,63 @@ detail::TypeAssertion<Value> assert_type() {
 /*
  * Sink Factories
  */
+
+/**
+ * any() - For determining if any value in a sequence satisfies a predicate.
+ *
+ * The following is an example for checking if any computer is broken:
+ *
+ *   bool schrepIsMad = from(computers) | any(isBroken);
+ *
+ * (because everyone knows Schrep hates broken computers).
+ *
+ * Note that if no predicate is provided, 'any()' checks if any of the values
+ * are true when cased to bool. To check if any of the scores are nonZero:
+ *
+ *   bool somebodyScored = from(scores) | any();
+ *
+ * Note: Passing an empty sequence through 'any()' will always return false. In
+ * fact, 'any()' is equivilent to the composition of 'filter()' and 'notEmpty'.
+ *
+ *   from(source) | any(pred) == from(source) | filter(pred) | notEmpty
+ */
+
+template <class Predicate = Identity,
+          class Filter = detail::Filter<Predicate>,
+          class NotEmpty = detail::IsEmpty<false>,
+          class Composed = detail::Composed<Filter, NotEmpty>>
+Composed any(Predicate pred = Predicate()) {
+  return Composed(Filter(std::move(pred)), NotEmpty());
+}
+
+/**
+ * all() - For determining whether all values in a sequence satisfy a predicate.
+ *
+ * The following is an example for checking if all members of a team are cool:
+ *
+ *   bool isAwesomeTeam = from(team) | all(isCool);
+ *
+ * Note that if no predicate is provided, 'all()'' checks if all of the values
+ * are true when cased to bool.
+ * The following makes sure none of 'pointers' are nullptr:
+ *
+ *   bool allNonNull = from(pointers) | all();
+ *
+ * Note: Passing an empty sequence through 'all()' will always return true. In
+ * fact, 'all()' is equivilent to the composition of 'filter()' with the
+ * reversed predicate and 'isEmpty'.
+ *
+ *   from(source) | all(pred) == from(source) | filter(negate(pred)) | isEmpty
+ */
+
+template <class Predicate = Identity,
+          class Filter = detail::Filter<Negate<Predicate>>,
+          class IsEmpty = detail::IsEmpty<true>,
+          class Composed = detail::Composed<Filter, IsEmpty>>
+Composed all(Predicate pred = Predicate()) {
+  return Composed(Filter(std::move(negate(pred))), IsEmpty());
+}
+
 template<class Seed,
          class Fold,
          class FoldLeft = detail::FoldLeft<Seed, Fold>>
index c25f40c7e2049b6d9d127e30a80f4c0fc37b4476..6973def211a12d9156723e6bb2e3c04c0bc1f933 100644 (file)
@@ -591,6 +591,18 @@ TEST(Gen, MaxBy) {
   EXPECT_EQ("eleven", gen | maxBy(&strlen));
 }
 
+TEST(Gen, Min) {
+  auto odds = seq(2,10) | filter([](int i){ return i % 2; });
+
+  EXPECT_EQ(3, odds | min);
+}
+
+TEST(Gen, Max) {
+  auto odds = seq(2,10) | filter([](int i){ return i % 2; });
+
+  EXPECT_EQ(9, odds | max);
+}
+
 TEST(Gen, Append) {
   string expected = "facebook";
   string actual = "face";
@@ -730,15 +742,27 @@ TEST(Gen, Get) {
   EXPECT_EQ(36, from(tuples) | get<2>() | sum);
 }
 
+TEST(Gen, notEmpty) {
+  EXPECT_TRUE(seq(0) | notEmpty);
+  EXPECT_TRUE(seq(0, 1) | notEmpty);
+  EXPECT_TRUE(just(1) | notEmpty);
+  EXPECT_FALSE(gen::range(0, 0) | notEmpty);
+  EXPECT_FALSE(from({1}) | take(0) | notEmpty);
+  EXPECT_TRUE(seq(1, 3) | cycle | notEmpty);
+}
+
+TEST(Gen, isEmpty) {
+  EXPECT_FALSE(seq(0) | isEmpty);
+  EXPECT_FALSE(seq(0, 1) | isEmpty);
+  EXPECT_FALSE(just(1) | isEmpty);
+  EXPECT_TRUE(gen::range(0, 0) | isEmpty);
+  EXPECT_TRUE(from({1}) | take(0) | isEmpty);
+  EXPECT_FALSE(seq(1, 3) | cycle | isEmpty);
+}
+
 TEST(Gen, Any) {
-  EXPECT_TRUE(seq(0) | any);
-  EXPECT_TRUE(seq(0, 1) | any);
   EXPECT_TRUE(seq(0, 10) | any([](int i) { return i == 7; }));
   EXPECT_FALSE(seq(0, 10) | any([](int i) { return i == 11; }));
-
-  EXPECT_TRUE(from({1}) | any);
-  EXPECT_FALSE(gen::range(0, 0) | any);
-  EXPECT_FALSE(from({1}) | take(0) | any);
 }
 
 TEST(Gen, All) {
index d6dcc25cf31ba44199d5c868c67d8730c61399f8..61f3c5637bb4d43f7642b526dfb2ad393d240fd7 100644 (file)
@@ -56,10 +56,10 @@ static auto primes =
     seq(1, 1 << 20) | filter(isPrimeSlow) | as<vector>();
 
 static auto isPrime = [](int n) {
-  return !(from(primes)
+  return from(primes)
          | until([&](int d) { return d * d > n; })
          | filter([&](int d) { return 0 == n % d; })
-         | any);
+         | isEmpty;
 };
 
 static auto factors = [](int n) {