From: Tom Jackson Date: Fri, 22 Jun 2012 06:27:23 +0000 (-0700) Subject: folly::gen, or Comprehensions->Folly X-Git-Tag: v0.22.0~1166 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=506b9a329da4d173c6fb608a224f73287147511a;p=folly.git folly::gen, or Comprehensions->Folly Summary: Moving Comprehensions library into Folly and refactoring its interface to be much more modular and composable. There are now two core abstractions: # Generators: Standalone classes supporting ##apply()## and optionally ##foreach()##. These all inherit from ##GenImpl##. # Operators: Standalone classes which, when composed with a generator, produce a new generator. These all inherit from ##OperatorImpl##. These generators may be composed with ##operator|## overloads which only match ##const GenImpl&## on the left like ##gen | op##. Additionally, generator may be consumed inline with ##gen | lambda## like ##gen | [](int x) { cout << x << endl; };##. With this design, new operators may be added very simply without modifying the core library and templates are instantiated only exactly as needed. Example: ```lang=cpp auto sum = seq(1, 10) | filter(isPrime) | sum; seq(1, 10) | [](int i) { cout << i << endl; }; ``` Test Plan: Unit tests Reviewed By: andrei.alexandrescu@fb.com FB internal diff: D542215 --- diff --git a/folly/experimental/Gen-inl.h b/folly/experimental/Gen-inl.h new file mode 100644 index 00000000..c213f138 --- /dev/null +++ b/folly/experimental/Gen-inl.h @@ -0,0 +1,1296 @@ +/* + * Copyright 2012 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. + */ + +namespace folly { namespace gen { + +/** + * IsCompatibleSignature - Trait type for testing whether a given Functor + * matches an expected signature. + * + * Usage: + * IsCompatibleSignature::value + */ +template +class IsCompatibleSignature { + static constexpr bool value = false; +}; + +template +class IsCompatibleSignature { + template()(std::declval()...)), + bool good = std::is_same::value> + static constexpr bool testArgs(int* p) { + return good; + } + + template + static constexpr bool testArgs(...) { + return false; + } +public: + static constexpr bool value = testArgs(nullptr); +}; + +/** + * ArgumentReference - For determining ideal argument type to receive a value. + */ +template +struct ArgumentReference : + public std::conditional::value, + T, // T& -> T&, T&& -> T&&, const T& -> const T& + typename std::conditional< + std::is_const::value, + T&, // const int -> const int& + T&& // int -> int&& + >::type> {}; + +/** + * FBounded - Helper type for the curiously recurring template pattern, used + * heavily here to enable inlining and obviate virtual functions + */ +template +struct FBounded { + const Self& self() const { + return *static_cast(this); + } + + Self& self() { + return *static_cast(this); + } +}; + +/** + * Operator - Core abstraction of an operation which may be applied to a + * generator. All operators implement a method compose(), which takes a + * generator and produces an output generator. + */ +template +class Operator : public FBounded { + public: + /** + * compose() - Must be implemented by child class to compose a new Generator + * out of a given generator. This function left intentionally unimplemented. + */ + template + ResultGen compose(const GenImpl& source) const; + protected: + Operator() = default; + Operator(const Operator&) = default; + Operator(Operator&&) = default; +}; + +/** + * GenImpl - Core abstraction of a generator, an object which produces values by + * passing them to a given handler lambda. All generator implementations must + * implement apply(). foreach() may also be implemented to special case the + * condition where the entire sequence is consumed. + */ +template +class GenImpl : public FBounded { + protected: + // To prevent slicing + GenImpl() = default; + GenImpl(const GenImpl&) = default; + GenImpl(GenImpl&&) = default; + + public: + typedef Value ValueType; + typedef typename std::decay::type StorageType; + + /** + * apply() - Send all values produced by this generator to given + * handler until it returns false. Returns true if the false iff the handler + * returned false. + */ + template + bool apply(Handler&& handler) const; + + /** + * foreach() - Send all values produced by this generator to given lambda. + */ + template + void foreach(Body&& body) const { + this->self().apply([&](Value value) { + body(std::forward(value)); + return true; + }); + } + + template, + class ValueNext> + Chain operator+(const GenImpl& next) const { + static_assert( + std::is_same::value, + "Generators may ony be combined if Values are the exact same type."); + return Chain(*this, next); + } +}; + +/** + * operator|() which enables foreach-like usage: + * gen | [](Value v) -> void {...}; + */ +template +typename std::enable_if< + IsCompatibleSignature::value>::type +operator|(const GenImpl& gen, Handler&& handler) { + gen.self().foreach(std::forward(handler)); +} + +/** + * operator|() which enables foreach-like usage with 'break' support: + * gen | [](Value v) -> bool { return shouldContinue(); }; + */ +template +typename std::enable_if< + IsCompatibleSignature::value>::type +operator|(const GenImpl& gen, Handler&& handler) { + gen.self().apply(std::forward(handler)); +} + +/** + * operator|() for composing generators with operators, similar to boosts' range + * adaptors: + * gen | map(square) | sum + */ +template +auto operator|(const GenImpl& gen, const Operator& op) -> +decltype(op.self().compose(gen)) { + return op.self().compose(gen); +} + +namespace detail { + +/* + * ReferencedSource - Generate values from an STL-like container using + * iterators from .begin() until .end(). Value type defaults to the type of + * *container->begin(). For std::vector, this would be int&. Note that the + * value here is a reference, so the values in the vector will be passed by + * reference to downstream operators. + * + * This type is primarily used through the 'from' helper method, like: + * + * string& longestName = from(names) + * | maxBy([](string& s) { return s.size() }); + */ +template +class ReferencedSource : + public GenImpl> { + Container* const container_; +public: + explicit ReferencedSource(Container* container) + : container_(container) {} + + template + void foreach(Body&& body) const { + for (auto& value : *container_) { + body(std::forward(value)); + } + } + + template + bool apply(Handler&& handler) const { + for (auto& value : *container_) { + if (!handler(std::forward(value))) { + return false; + } + } + return true; + } +}; + +/** + * CopiedSource - For producing values from eagerly from a sequence of values + * whose storage is owned by this class. Useful for preparing a generator for + * use after a source collection will no longer be available, or for when the + * values are specified literally with an initializer list. + * + * This type is primarily used through the 'fromCopy' function, like: + * + * auto sourceCopy = fromCopy(makeAVector()); + * auto sum = sourceCopy | sum; + * auto max = sourceCopy | max; + * + * Though it is also used for the initializer_list specialization of from(). + */ +template +class CopiedSource : + public GenImpl> { + static_assert( + !std::is_reference::value, "StorageType must be decayed"); + public: + // Generator objects are often copied during normal construction as they are + // encapsulated by downstream generators. It would be bad if this caused + // a copy of the entire container each time, and since we're only exposing a + // const reference to the value, it's safe to share it between multiple + // generators. + static_assert( + !std::is_reference::value, + "Can't copy into a reference"); + const std::shared_ptr copy_; +public: + typedef Container ContainerType; + + template + explicit CopiedSource(const SourceContainer& container) + : copy_(new Container(begin(container), end(container))) {} + + explicit CopiedSource(Container&& container) : + copy_(new Container(std::move(container))) {} + + // To enable re-use of cached results. + CopiedSource(const CopiedSource& source) + : copy_(source.copy_) {} + + template + void foreach(Body&& body) const { + for (const auto& value : *copy_) { + body(value); + } + } + + template + bool apply(Handler&& handler) const { + // The collection may be reused by others, we can't allow it to be changed. + for (const auto& value : *copy_) { + if (!handler(value)) { + return false; + } + } + return true; + } +}; + +/** + * Sequence - For generating values from beginning value, incremented along the + * way with the ++ and += operators. Iteration may continue indefinitely by + * setting the 'endless' template parameter to true. If set to false, iteration + * will stop when value reaches 'end', either inclusively or exclusively, + * depending on the template parameter 'endInclusive'. Value type specified + * explicitly. + * + * This type is primarily used through the 'seq' and 'range' function, like: + * + * int total = seq(1, 10) | sum; + * auto indexes = range(0, 10); + */ +template +class Sequence : public GenImpl> { + static_assert(!std::is_reference::value && + !std::is_const::value, "Value mustn't be const or ref."); + Value bounds_[endless ? 1 : 2]; +public: + explicit Sequence(const Value& begin) + : bounds_{begin} { + static_assert(endless, "Must supply 'end'"); + } + + explicit Sequence(const Value& begin, const Value& end) + : bounds_{begin, end} {} + + template + bool apply(Handler&& handler) const { + Value value = bounds_[0]; + for (;endless || value < bounds_[1]; ++value) { + const Value& arg = value; + if (!handler(arg)) { + return false; + } + } + if (endInclusive && value == bounds_[1]) { + const Value& arg = value; + if (!handler(arg)) { + return false; + } + } + return true; + } + + template + void foreach(Body&& body) const { + Value value = bounds_[0]; + for (;endless || value < bounds_[1]; ++value) { + const Value& arg = value; + body(arg); + } + if (endInclusive && value == bounds_[1]) { + const Value& arg = value; + body(arg); + } + } +}; + +/** + * Chain - For concatenating the values produced by two Generators. + * + * This type is primarily used through using '+' to combine generators, like: + * + * auto nums = seq(1, 10) + seq(20, 30); + * int total = nums | sum; + */ +template +class Chain : public GenImpl> { + const First first_; + const Second second_; +public: + explicit Chain(const GenImpl& first, + const GenImpl& second) + : first_(first.self()) + , second_(second.self()) {} + + template + bool apply(Handler&& handler) const { + return first_.apply(std::forward(handler)) + && second_.apply(std::forward(handler)); + } + + template + void foreach(Body&& body) const { + first_.foreach(std::forward(body)); + second_.foreach(std::forward(body)); + } +}; + +/** + * Yield - For producing values from a user-defined generator by way of a + * 'yield' function. + **/ +template +class Yield : public GenImpl> { + const Source source_; + public: + explicit Yield(const Source& source) + : source_(source) { + } + + template + bool apply(Handler&& handler) const { + struct Break {}; + auto body = [&](Value value) { + if (!handler(std::forward(value))) { + throw Break(); + } + }; + try { + source_(body); + return true; + } catch (Break&) { + return false; + } + } + + template + void foreach(Body&& body) const { + source_(std::forward(body)); + } +}; + + +/* + * Operators + */ + +/** + * Map - For producing a sequence of values by passing each value from a source + * collection through a predicate. + * + * This type is usually used through the 'map' or 'mapped' helper function: + * + * auto squares = seq(1, 10) | map(square) | asVector; + */ +template +class Map : public Operator> { + const Predicate predicate_; + public: + explicit Map(const Predicate& predicate = Predicate()) + : predicate_(predicate) + { } + + template::type + >::type> + class Generator : + public GenImpl> { + const Source source_; + const Predicate pred_; + public: + explicit Generator(const Source& source, const Predicate& pred) + : source_(source), pred_(pred) {} + + template + void foreach(Body&& body) const { + source_.foreach([&](Value value) { + body(pred_(std::forward(value))); + }); + } + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Value value) { + return handler(pred_(std::forward(value))); + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), predicate_); + } +}; + + +/** + * Filter - For filtering values from a source sequence by a predicate. + * + * This type is usually used through the 'filter' helper function, like: + * + * auto nonEmpty = from(strings) + * | filter([](const string& str) -> bool { + * return !str.empty(); + * }); + */ +template +class Filter : public Operator> { + const Predicate predicate_; + public: + explicit Filter(const Predicate& predicate) + : predicate_(predicate) + { } + + template + class Generator : public GenImpl> { + const Source source_; + const Predicate pred_; + public: + explicit Generator(const Source& source, const Predicate& pred) + : source_(source), pred_(pred) {} + + template + void foreach(Body&& body) const { + source_.foreach([&](Value value) { + if (pred_(std::forward(value))) { + body(std::forward(value)); + } + }); + } + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Value value) -> bool { + if (pred_(std::forward(value))) { + return handler(std::forward(value)); + } + return true; + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), predicate_); + } +}; + +/** + * Until - For producing values from a source until a predicate is satisfied. + * + * This type is usually used through the 'until' helper function, like: + * + * auto best = from(sortedItems) + * | until([](Item& item) { return item.score > 100; }) + * | asVector; + */ +template +class Until : public Operator> { + const Predicate predicate_; + public: + explicit Until(const Predicate& predicate) + : predicate_(predicate) + { } + + template::type> + class Generator : + public GenImpl> { + const Source source_; + const Predicate pred_; + public: + explicit Generator(const Source& source, const Predicate& pred) + : source_(source), pred_(pred) {} + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Value value) -> bool { + return !pred_(std::forward(value)) + && handler(std::forward(value)); + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), predicate_); + } +}; + +/** + * Take - For producing up to N values from a source. + * + * This type is usually used through the 'take' helper function, like: + * + * auto best = from(docs) + * | orderByDescending(scoreDoc) + * | take(10); + */ +class Take : public Operator { + const size_t count_; +public: + explicit Take(size_t count) + : count_(count) {} + + template + class Generator : + public GenImpl> { + const Source source_; + const size_t count_; + public: + explicit Generator(const Source& source, size_t count) + : source_(source) , count_(count) {} + + template + bool apply(Handler&& handler) const { + if (count_ == 0) { return false; } + size_t n = count_; + return source_.apply([&](Value value) -> bool { + if (!handler(std::forward(value))) { + return false; + } + return --n; + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), count_); + } +}; + +/** + * Skip - For skipping N items from the beginning of a source generator. + * + * This type is usually used through the 'skip' helper function, like: + * + * auto page = from(results) + * | skip(pageSize * startPage) + * | take(10); + */ +class Skip : public Operator { + const size_t count_; +public: + explicit Skip(size_t count) + : count_(count) {} + + template + class Generator : + public GenImpl> { + const Source source_; + const size_t count_; + public: + explicit Generator(const Source& source, size_t count) + : source_(source) , count_(count) {} + + template + void foreach(Body&& body) const { + if (count_ == 0) { + source_.foreach(body); + return; + } + size_t n = 0; + source_.foreach([&](Value value) { + if (n < count_) { + ++n; + } else { + body(std::forward(value)); + } + }); + } + + template + bool apply(Handler&& handler) const { + if (count_ == 0) { + return source_.apply(handler); + } + size_t n = 0; + return source_.apply([&](Value value) -> bool { + if (n < count_) { + ++n; + return true; + } + return handler(std::forward(value)); + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), count_); + } +}; + +/** + * Order - For ordering a sequence of values from a source by key. + * The key is extracted by the given selector functor, and this key is then + * compared using the specified comparator. + * + * This type is usually used through the 'order' helper function, like: + * + * auto closest = from(places) + * | orderBy([](Place& p) { + * return -distance(p.location, here); + * }) + * | take(10); + */ +template +class Order : public Operator> { + const Selector selector_; + const Comparer comparer_; + public: + Order(const Selector& selector = Selector(), + const Comparer& comparer = Comparer()) + : selector_(selector) , comparer_(comparer) {} + + template::type, + class Result = typename std::result_of::type> + class Generator : + public GenImpl> { + const Source source_; + const Selector selector_; + const Comparer comparer_; + + typedef std::vector VectorType; + + VectorType asVector() const { + auto comparer = [&](const StorageType& a, const StorageType& b) { + return comparer_(selector_(a), selector_(b)); + }; + auto vals = source_ | as(); + std::sort(vals.begin(), vals.end(), comparer); + return std::move(vals); + } + public: + Generator(const Source& source, + const Selector& selector, + const Comparer& comparer) + : source_(source) , selector_(selector) , comparer_(comparer) {} + + VectorType operator|(const Collect&) const { + return asVector(); + } + + VectorType operator|(const CollectTemplate&) const { + return asVector(); + } + + template + void foreach(Body&& body) const { + for (auto& value : asVector()) { + body(std::move(value)); + } + } + + template + bool apply(Handler&& handler) const { + auto comparer = [&](const StorageType& a, const StorageType& b) { + // swapped for minHeap + return comparer_(selector_(b), selector_(a)); + }; + auto heap = source_ | as(); + std::make_heap(heap.begin(), heap.end(), comparer); + while (!heap.empty()) { + std::pop_heap(heap.begin(), heap.end(), comparer); + if (!handler(std::move(heap.back()))) { + return false; + } + heap.pop_back(); + } + return true; + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self(), selector_, comparer_); + } +}; + +/* + * Sinks + */ + +/** + * FoldLeft - Left-associative functional fold. For producing an aggregate value + * from a seed and a folder function. Useful for custom aggregators on a + * sequence. + * + * This type is primarily used through the 'foldl' helper method, like: + * + * double movingAverage = from(values) + * | foldl(0.0, [](double avg, double sample) { + * return sample * 0.1 + avg * 0.9; + * }); + */ +template +class FoldLeft : public Operator> { + const Seed seed_; + const Fold fold_; + public: + FoldLeft(const Seed& seed, const Fold& fold) + : seed_(seed) + , fold_(fold) + {} + + template + Seed compose(const GenImpl& source) const { + Seed accum = seed_; + source | [&](Value v) { + accum = fold_(std::move(accum), std::forward(v)); + }; + return accum; + } +}; + +/** + * First - For finding the first value in a sequence. + * + * This type is primarily used through the 'first' static value, like: + * + * int firstThreeDigitPrime = seq(100) | filter(isPrime) | first; + */ +class First : public Operator { + public: + template::type> + StorageType compose(const GenImpl& source) const { + static_assert(std::is_same::value, "wtf"); + Optional accum; + source | [&](Value v) -> bool { + accum = std::forward(v); + return false; + }; + if (!accum.hasValue()) { + throw EmptySequence(); + } + return std::move(accum.value()); + } +}; + + +/** + * Any - For determining whether any values are contained in a sequence. + * + * This type is primarily used through the 'any' static value, like: + * + * bool any20xPrimes = seq(200, 210) | filter(isPrime) | any; + */ +class Any : public Operator { + public: + template + bool compose(const GenImpl& source) const { + bool any = false; + source | [&](Value v) -> bool { + any = true; + return false; + }; + return any; + } +}; + +/** + * Reduce - Functional reduce, for recursively combining values from a source + * using a reducer function until there is only one item left. Useful for + * combining values when an empty sequence doesn't make sense. + * + * This type is primarily used through the 'reduce' helper method, like: + * + * sring longest = from(names) + * | reduce([](string&& best, string& current) { + * return best.size() >= current.size() ? best : current; + * }); + */ +template +class Reduce : public Operator> { + const Reducer reducer_; + public: + Reduce(const Reducer& reducer) + : reducer_(reducer) + {} + + template::type> + StorageType compose(const GenImpl& source) const { + Optional accum; + source | [&](Value v) { + if (accum.hasValue()) { + accum = reducer_(std::move(accum.value()), std::forward(v)); + } else { + accum = std::forward(v); + } + }; + if (!accum.hasValue()) { + throw EmptySequence(); + } + return accum.value(); + } +}; + +/** + * Count - for simply counting the items in a collection. + * + * This type is usually used through its singleton, 'count': + * + * auto shortPrimes = seq(1, 100) | filter(isPrime) | count; + */ +class Count : public Operator { + public: + template + size_t compose(const GenImpl& source) const { + return foldl(size_t(0), + [](size_t accum, Value v) { + return accum + 1; + }).compose(source); + } +}; + +/** + * Sum - For simply summing up all the values from a source. + * + * This type is usually used through its singleton, 'sum': + * + * auto gaussSum = seq(1, 100) | sum; + */ +class Sum : public Operator { + public: + template::type> + StorageType compose(const GenImpl& source) const { + return foldl(StorageType(0), + [](StorageType&& accum, Value v) { + return std::move(accum) + std::forward(v); + }).compose(source); + } +}; + +/** + * Min - For a value which minimizes a key, where the key is determined by a + * given selector, and compared by given comparer. + * + * This type is usually used through the singletone 'min' or through the helper + * functions 'minBy' and 'maxBy'. + * + * auto oldest = from(people) + * | minBy([](Person& p) { + * return p.dateOfBirth; + * }); + */ +template +class Min : public Operator> { + Selector selector_; + Comparer comparer_; + public: + Min(const Selector& selector = Selector(), + const Comparer& comparer = Comparer()) + : selector_(selector) + , comparer_(comparer) + {} + + template::type, + class Key = typename std::decay< + typename std::result_of::type + >::type> + StorageType compose(const GenImpl& source) const { + Optional min; + Optional minKey; + source | [&](Value v) { + Key key = selector_(std::forward(v)); + if (!minKey.hasValue() || comparer_(key, minKey.value())) { + minKey = key; + min = std::forward(v); + } + }; + if (!min.hasValue()) { + throw EmptySequence(); + } + return min.value(); + } +}; + +/** + * Append - For collecting values from a source into a given output container + * by appending. + * + * This type is usually used through the helper function 'appendTo', like: + * + * vector ids; + * from(results) | map([](Person& p) { return p.id }) + * | appendTo(ids); + */ +template +class Append : public Operator> { + Collection* const collection_; + public: + explicit Append(Collection* collection) + : collection_(collection) + {} + + template + Collection& compose(const GenImpl& source) const { + source | [&](Value v) { + collection_->insert(collection_->end(), std::forward(v)); + }; + return *collection_; + } +}; + +/** + * Collect - For collecting values from a source in a collection of the desired + * type. + * + * This type is usually used through the helper function 'as', like: + * + * std::string upper = from(stringPiece) + * | map(&toupper) + * | as(); + */ +template +class Collect : public Operator> { + public: + template::type> + Collection compose(const GenImpl& source) const { + Collection collection; + source | [&](Value v) { + collection.insert(collection.end(), std::forward(v)); + }; + return collection; + } +}; + + +/** + * CollectTemplate - For collecting values from a source in a collection + * constructed using the specified template type. Given the type of values + * produced by the given generator, the collection type will be: + * Container> + * + * 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. + * + * Example: + * + * set uniqueNames = from(names) | as(); + */ +template class Container, + template class Allocator> +class CollectTemplate : public Operator> { + public: + template::type, + class Collection = Container>> + Collection compose(const GenImpl& source) const { + Collection collection; + source | [&](Value v) { + collection.insert(collection.end(), std::forward(v)); + }; + return collection; + } +}; +/** + * Concat - For flattening generators of generators. + * + * This type is usually used through the 'concat' static value, like: + * + * auto edges = + * from(nodes) + * | map([](Node& x) { + * return from(x.neighbors) + * | map([&](Node& y) { + * return Edge(x, y); + * }); + * }) + * | concat + * | as(); + */ +class Concat : public Operator { +public: + template::type::ValueType> + class Generator : + public GenImpl> { + const Source source_; + public: + explicit Generator(const Source& source) + : source_(source) {} + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Inner inner) -> bool { + return inner.apply(std::forward(handler)); + }); + } + + template + void foreach(Body&& body) const { + source_.foreach([&](Inner inner) { + inner.foreach(std::forward(body)); + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self()); + } +}; + +/** + * RangeConcat - For flattening generators of generators. + * + * This type is usually used through the 'rconcat' static value, like: + * + * map> adjacency; + * auto sinks = + * from(adjacency) + * | get<1>() + * | rconcat() + * | as(); + */ +class RangeConcat : public Operator { +public: + template::RefType> + class Generator + : public GenImpl> { + const Source source_; + public: + Generator(const Source& source) + : source_(source) {} + + template + void foreach(Body&& body) const { + source_.foreach([&](Range range) { + for (auto& value : range) { + body(value); + } + }); + } + + template + bool apply(Handler&& handler) const { + return source_.apply([&](Range range) { + for (auto& value : range) { + if (!handler(value)) { + return false; + } + } + return true; + }); + } + }; + + template> + Gen compose(const GenImpl& source) const { + return Gen(source.self()); + } +}; + +} //::detail + +/** + * Gen - For wrapping template types in simple polymorphic wrapper. + * + * This type is usually used through the 'rconcat' static value, like: + * + * map> adjacency; + * auto sinks = + * from(adjacency) + * | get<1>() + * | rconcat() + * | as(); + **/ +template +class VirtualGen : public GenImpl> { + class WrapperBase { + public: + virtual ~WrapperBase() {} + virtual bool apply(const std::function& handler) const = 0; + virtual void foreach(const std::function& body) const = 0; + virtual std::unique_ptr clone() const = 0; + }; + + template + class WrapperImpl : public WrapperBase { + const Wrapped wrapped_; + public: + WrapperImpl(const Wrapped& wrapped) + : wrapped_(wrapped) { + } + + virtual bool apply(const std::function& handler) const { + return wrapped_.apply(handler); + } + + virtual void foreach(const std::function& body) const { + wrapped_.foreach(body); + } + + virtual std::unique_ptr clone() const { + return std::unique_ptr(new WrapperImpl(wrapped_)); + } + }; + + std::unique_ptr wrapper_; + + public: + template + /* implicit */ VirtualGen(const GenImpl& source) + : wrapper_(new WrapperImpl(source.self())) + { } + + VirtualGen(VirtualGen&& source) + : wrapper_(std::move(source.wrapper_)) + { } + + VirtualGen(const VirtualGen& source) + : wrapper_(source.wrapper_->clone()) + { } + + VirtualGen& operator=(const VirtualGen& source) { + wrapper_.reset(source.wrapper_->clone()); + return *this; + } + + VirtualGen& operator=(VirtualGen&& source) { + wrapper_= std::move(source.wrapper_); + return *this; + } + + bool apply(const std::function& handler) const { + return wrapper_->apply(handler); + } + + void foreach(const std::function& body) const { + wrapper_->foreach(body); + } +}; + +/** + * non-template operators, statically defined to avoid the need for anything but + * the header. + */ +static const detail::Sum sum; + +static const detail::Count count; + +static const detail::First first; + +static const detail::Any any; + +static const detail::Min min; + +static const detail::Min max; + +static const detail::Order order; + +static const detail::Map move; + +static const detail::Concat concat; + +static const detail::RangeConcat rconcat; + +inline detail::Take take(size_t count) { + return detail::Take(count); +} + +inline detail::Skip skip(size_t count) { + return detail::Skip(count); +} + +}} //folly::gen::detail diff --git a/folly/experimental/Gen.h b/folly/experimental/Gen.h new file mode 100644 index 00000000..0797637d --- /dev/null +++ b/folly/experimental/Gen.h @@ -0,0 +1,393 @@ +/* + * Copyright 2012 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "folly/Range.h" +#include "folly/Optional.h" + +/** + * Generator-based Sequence Comprehensions in C++, akin to C#'s LINQ + * @author Tom Jackson + * + * This library makes it possible to write declarative comprehensions for + * processing sequences of values efficiently in C++. The operators should be + * familiar to those with experience in functional programming, and the + * performance will be virtually identical to the equivalent, boilerplate C++ + * implementations. + * + * Generator objects may be created from either an stl-like container (anything + * supporting begin() and end()), from sequences of values, or from another + * generator (see below). To create a generator that pulls values from a vector, + * for example, one could write: + * + * vector names { "Jack", "Jill", "Sara", "Tom" }; + * auto gen = from(names); + * + * Generators are composed by building new generators out of old ones through + * the use of operators. These are reminicent of shell pipelines, and afford + * similar composition. Lambda functions are used liberally to describe how to + * handle individual values: + * + * auto lengths = gen + * | mapped([](const fbstring& name) { return name.size(); }); + * + * Generators are lazy; they don't actually perform any work until they need to. + * As an example, the 'lengths' generator (above) won't actually invoke the + * provided lambda until values are needed: + * + * auto lengthVector = lengths | asVector(); + * auto totalLength = lengths | sum; + * + * 'auto' is useful in here because the actual types of the generators objects + * are usually complicated and implementation-sensitive. + * + * If a simpler type is desired (for returning, as an example), VirtualGen + * may be used to wrap the generator in a polymorphic wrapper: + * + * VirtualGen powersOfE() { + * return seq(1) | mapped(&expf); + * } + * + * To learn more about this library, including the use of infinite generators, + * see the examples in the comments, or the docs (coming soon). +*/ + +namespace folly { namespace gen { + +template +class GenImpl; + +template +class Operator; + +class EmptySequence : std::exception { +public: + virtual const char* what() const noexcept { + return "This operation cannot be called on an empty sequence"; + } +}; + +class Less { +public: + template + auto operator()(const First& first, const Second& second) const -> + decltype(first < second) { + return first < second; + } +}; + +class Greater { +public: + template + auto operator()(const First& first, const Second& second) const -> + decltype(first > second) { + return first > second; + } +}; + +template +class Get { +public: + template + auto operator()(Value&& value) const -> + decltype(std::get(std::forward(value))) { + return std::get(std::forward(value)); + } +}; + +class Move { +public: + template + auto operator()(Value&& value) const -> + decltype(std::move(std::forward(value))) { + return std::move(std::forward(value)); + } +}; + +class Identity { +public: + template + auto operator()(Value&& value) const -> + decltype(std::forward(value)) { + return std::forward(value); + } +}; + +namespace detail { + +template +struct FBounded; + +/* + * Type Traits + */ +template +struct ValueTypeOfRange { + private: + static Container container_; + public: + typedef decltype(*std::begin(container_)) + RefType; + typedef typename std::decay::type + StorageType; +}; + + +/* + * Sources + */ +template::RefType> +class ReferencedSource; + +template::type>> +class CopiedSource; + +template +class Sequence; + +template +class Chain; + +template +class Yield; + +/* + * Operators + */ +template +class Map; + +template +class Filter; + +template +class Until; + +class Take; + +class Skip; + +template +class Order; + + +/* + * Sinks + */ +template +class FoldLeft; + +template +class Reduce; + +class Sum; + +template +class Min; + +template +class Collect; + +template class Collection = std::vector, + template class Allocator = std::allocator> +class CollectTemplate; + +template +class Append; + +} + +/** + * Polymorphic wrapper + **/ +template +class VirtualGen; + +/* + * Source Factories + */ +template> +From fromConst(const Container& source) { + return From(&source); +} + +template> +From from(Container& source) { + return From(&source); +} + +template::StorageType, + class CopyOf = detail::CopiedSource> +CopyOf fromCopy(Container&& source) { + return CopyOf(std::forward(source)); +} + +template> +From from(std::initializer_list source) { + return From(source); +} + +template> +From from(Container&& source) { + return From(std::move(source)); +} + +template> +Gen range(Value begin, Value end) { + return Gen(begin, end); +} + +template> +Gen seq(Value first, Value last) { + return Gen(first, last); +} + +template> +Gen seq(Value begin) { + return Gen(begin); +} + +template> +Yield generator(Source&& source) { + return Yield(std::forward(source)); +} + +#define GENERATOR(type, body) \ + ::folly::gen::generator( \ + [=](const std::function& yield) \ + { body }) + +/* + * Operator Factories + */ +template> +Map mapped(const Predicate& pred = Predicate()) { + return Map(pred); +} + +template> +Map map(const Predicate& pred = Predicate()) { + return Map(pred); +} + +template> +Filter filter(const Predicate& pred = Predicate()) { + return Filter(pred); +} + +template> +Until until(const Predicate& pred = Predicate()) { + return Until(pred); +} + +template> +Order orderBy(const Selector& selector, + const Comparer& comparer = Comparer()) { + return Order(selector, comparer); +} + +template> +Order orderByDescending(const Selector& selector) { + return Order(selector); +} + +template>> +Get get() { + return Get(); +} + +/* + * Sink Factories + */ +template> +FoldLeft foldl(const Seed& seed, const Fold& fold) { + return FoldLeft(seed, fold); +} + +template> +Reduce reduce(const Reducer& reducer) { + return Reduce(reducer); +} + +template> +Min minBy(const Selector& selector = Selector()) { + return Min(selector); +} + +template> +MaxBy maxBy(const Selector& selector = Selector()) { + return MaxBy(selector); +} + +template> +Collect as() { + return Collect(); +} + +template class Container = std::vector, + template class Allocator = std::allocator, + class Collect = detail::CollectTemplate> +Collect as() { + return Collect(); +} + +template> +Append appendTo(Collection& collection) { + return Append(&collection); +} + +}} // folly::gen + +#include "folly/experimental/Gen-inl.h" diff --git a/folly/experimental/test/GenBenchmark.cpp b/folly/experimental/test/GenBenchmark.cpp new file mode 100644 index 00000000..ad9bf7f1 --- /dev/null +++ b/folly/experimental/test/GenBenchmark.cpp @@ -0,0 +1,273 @@ +/* + * Copyright 2012 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 "folly/experimental/Gen.h" + +#include +#include + +#include "folly/Benchmark.h" + +using namespace folly; +using namespace folly::gen; +using std::ostream; +using std::pair; +using std::set; +using std::vector; +using std::tuple; + +static std::atomic testSize(1000); +static vector testVector = + seq(1, testSize.load()) + | mapped([](int) { return rand(); }) + | as(); +static vector> testVectorVector = + seq(1, 100) + | map([](int i) { + return seq(1, i) | as(); + }) + | as(); + +auto square = [](int x) { return x * x; }; +auto add = [](int a, int b) { return a + b; }; +auto multiply = [](int a, int b) { return a * b; }; + +BENCHMARK(Sum_Basic_NoGen, iters) { + int limit = testSize.load(); + int s = 0; + while (iters--) { + for (int i = 0; i < limit; ++i) { + s += i; + } + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(Sum_Basic_Gen, iters) { + int limit = testSize.load(); + int s = 0; + while (iters--) { + s += range(0, limit) | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_DRAW_LINE() + +BENCHMARK(Sum_Vector_NoGen, iters) { + int s = 0; + while (iters--) { + for (auto& i : testVector) { + s += i; + } + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(Sum_Vector_Gen, iters) { + int s = 0; + while (iters--) { + s += from(testVector) | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_DRAW_LINE() + +BENCHMARK(Count_Vector_NoGen, iters) { + int s = 0; + while (iters--) { + for (auto& i : testVector) { + if (i * 2 < rand()) { + ++s; + } + } + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(Count_Vector_Gen, iters) { + int s = 0; + while (iters--) { + s += from(testVector) + | filter([](int i) { + return i * 2 < rand(); + }) + | count; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_DRAW_LINE() + +BENCHMARK(Fib_Sum_NoGen, iters) { + int s = 0; + while (iters--) { + auto fib = [](int limit) -> vector { + vector ret; + int a = 0; + int b = 1; + for (int i = 0; i * 2 < limit; ++i) { + ret.push_back(a += b); + ret.push_back(b += a); + } + return ret; + }; + for (auto& v : fib(testSize.load())) { + s += v; + v = s; + } + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(Fib_Sum_Gen, iters) { + int s = 0; + while (iters--) { + auto fib = GENERATOR(int, { + int a = 0; + int b = 1; + for (;;) { + yield(a += b); + yield(b += a); + } + }); + s += fib | take(testSize.load()) | sum; + } + folly::doNotOptimizeAway(s); +} + +struct FibYielder { + template + void operator()(Yield&& yield) const { + int a = 0; + int b = 1; + for (;;) { + yield(a += b); + yield(b += a); + } + } +}; + +BENCHMARK_RELATIVE(Fib_Sum_Gen_Static, iters) { + int s = 0; + while (iters--) { + auto fib = generator(FibYielder()); + s += fib | take(30) | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_DRAW_LINE() + +BENCHMARK(VirtualGen_0Virtual, iters) { + int s = 0; + while (iters--) { + auto numbers = seq(1, 10000); + auto squares = numbers | map(square); + auto quads = squares | map(square); + s += quads | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(VirtualGen_1Virtual, iters) { + int s = 0; + while (iters--) { + VirtualGen numbers = seq(1, 10000); + auto squares = numbers | map(square); + auto quads = squares | map(square); + s += quads | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(VirtualGen_2Virtual, iters) { + int s = 0; + while (iters--) { + VirtualGen numbers = seq(1, 10000); + VirtualGen squares = numbers | map(square); + auto quads = squares | map(square); + s += quads | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(VirtualGen_3Virtual, iters) { + int s = 0; + while (iters--) { + VirtualGen numbers = seq(1, 10000); + VirtualGen squares = numbers | map(square); + VirtualGen quads = squares | map(square); + s += quads | sum; + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_DRAW_LINE() + +BENCHMARK(Concat_NoGen, iters) { + int s = 0; + while (iters--) { + for (auto& v : testVectorVector) { + for (auto& i : v) { + s += i; + } + } + } + folly::doNotOptimizeAway(s); +} + +BENCHMARK_RELATIVE(Concat_Gen, iters) { + int s = 0; + while (iters--) { + s += from(testVectorVector) | rconcat | sum; + } + folly::doNotOptimizeAway(s); +} + +// Results from a dual core Xeon L5520 @ 2.27GHz: +// +// ============================================================================ +// folly/experimental/test/GenBenchmark.cpp relative time/iter iters/s +// ============================================================================ +// Sum_Basic_NoGen 301.60ns 3.32M +// Sum_Basic_Gen 103.20% 292.24ns 3.42M +// ---------------------------------------------------------------------------- +// Sum_Vector_NoGen 200.33ns 4.99M +// Sum_Vector_Gen 99.44% 201.45ns 4.96M +// ---------------------------------------------------------------------------- +// Count_Vector_NoGen 19.07fs 52.43T +// Count_Vector_Gen 166.67% 11.44fs 87.38T +// ---------------------------------------------------------------------------- +// Fib_Sum_NoGen 4.15us 241.21K +// Fib_Sum_Gen 48.75% 8.50us 117.58K +// Fib_Sum_Gen_Static 113.24% 3.66us 273.16K +// ---------------------------------------------------------------------------- +// VirtualGen_0Virtual 10.05us 99.48K +// VirtualGen_1Virtual 29.63% 33.93us 29.47K +// VirtualGen_2Virtual 20.47% 49.09us 20.37K +// VirtualGen_3Virtual 15.30% 65.68us 15.23K +// ---------------------------------------------------------------------------- +// Concat_NoGen 2.34us 427.15K +// Concat_Gen 90.04% 2.60us 384.59K +// ============================================================================ + +int main(int argc, char *argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true); + runBenchmarks(); + return 0; +} diff --git a/folly/experimental/test/GenTest.cpp b/folly/experimental/test/GenTest.cpp new file mode 100644 index 00000000..bb85abab --- /dev/null +++ b/folly/experimental/test/GenTest.cpp @@ -0,0 +1,556 @@ +/* + * Copyright 2012 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 +#include +#include +#include "folly/experimental/Gen.h" +#include "folly/FBVector.h" +#include "folly/dynamic.h" + +using namespace folly::gen; +using namespace folly; +using std::ostream; +using std::pair; +using std::set; +using std::unique_ptr; +using std::vector; +using std::tuple; +using std::make_tuple; +//using std::unordered_map; + +#define EXPECT_SAME(A, B) \ + static_assert(std::is_same::value, "Mismatched: " #A ", " #B) +EXPECT_SAME(int&&, typename ArgumentReference::type); +EXPECT_SAME(int&, typename ArgumentReference::type); +EXPECT_SAME(const int&, typename ArgumentReference::type); +EXPECT_SAME(const int&, typename ArgumentReference::type); + +template +ostream& operator<<(ostream& os, const set& values) { + return os << from(values); +} + +template +ostream& operator<<(ostream& os, const vector& values) { + os << "["; + for (auto& value : values) { + if (&value != &values.front()) { + os << " "; + } + os << value; + } + return os << "]"; +} + +auto square = [](int x) { return x * x; }; +auto add = [](int a, int b) { return a + b; }; +auto multiply = [](int a, int b) { return a * b; }; + +auto product = foldl(1, multiply); + +template +ostream& operator<<(ostream& os, const pair& pair) { + return os << "(" << pair.first << ", " << pair.second << ")"; +} + +TEST(Gen, Count) { + auto gen = seq(1, 10); + EXPECT_EQ(10, gen | count); + EXPECT_EQ(5, gen | take(5) | count); +} + +TEST(Gen, Sum) { + auto gen = seq(1, 10); + EXPECT_EQ((1 + 10) * 10 / 2, gen | sum); + EXPECT_EQ((1 + 5) * 5 / 2, gen | take(5) | sum); +} + +TEST(Gen, Foreach) { + auto gen = seq(1, 4); + int accum = 0; + gen | [&](int x) { accum += x; }; + EXPECT_EQ(10, accum); + int accum2 = 0; + gen | take(3) | [&](int x) { accum2 += x; }; + EXPECT_EQ(6, accum2); +} + +TEST(Gen, Map) { + auto expected = vector{4, 9, 16}; + auto gen = from({2, 3, 4}) | map(square); + EXPECT_EQ((vector{4, 9, 16}), gen | as()); + EXPECT_EQ((vector{4, 9}), gen | take(2) | as()); +} + +TEST(Gen, Seq) { + // cover the fenceposts of the loop unrolling + for (int n = 1; n < 100; ++n) { + EXPECT_EQ(n, seq(1, n) | count); + EXPECT_EQ(n + 1, seq(1) | take(n + 1) | count); + } +} + +TEST(Gen, Range) { + // cover the fenceposts of the loop unrolling + for (int n = 1; n < 100; ++n) { + EXPECT_EQ(range(0, n) | count, n); + } +} + +TEST(Gen, FromIterators) { + vector source {2, 3, 5, 7, 11}; + auto gen = from(makeRange(source.begin() + 1, source.end() - 1)); + EXPECT_EQ(3 * 5 * 7, gen | product); +} + +TEST(Gen, FromMap) { + auto source = seq(0, 10) + | map([](int i) { return std::make_pair(i, i * i); }) + | as>(); + auto gen = fromConst(source) + | map([&](const std::pair& p) { + return p.second - p.first; + }); + EXPECT_EQ(330, gen | sum); +} + +TEST(Gen, Filter) { + const auto expected = vector{1, 2, 4, 5, 7, 8}; + auto actual = + seq(1, 9) + | filter([](int x) { return x % 3; }) + | as>(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, Take) { + auto expected = vector{1, 4, 9, 16}; + auto actual = + seq(1, 1000) + | mapped([](int x) { return x * x; }) + | take(4) + | as>(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, Skip) { + auto gen = + seq(1, 1000) + | mapped([](int x) { return x * x; }) + | skip(4) + | take(4); + EXPECT_EQ((vector{25, 36, 49, 64}), gen | as()); +} + +TEST(Gen, Until) { + auto gen = + seq(1) //infinite + | mapped([](int x) { return x * x; }) + | until([](int x) { return x >= 1000; }); + EXPECT_EQ(31, gen | count); +} + + +TEST(Gen, Chain) { + std::vector nums {2, 3, 5, 7}; + std::map mappings { { 3, 9}, {5, 25} }; + auto gen = from(nums) + (from(mappings) | get<1>()); + EXPECT_EQ(51, gen | sum); + EXPECT_EQ(5, gen | take(2) | sum); + EXPECT_EQ(26, gen | take(5) | sum); +} + +TEST(Gen, Concat) { + std::vector> nums {{2, 3}, {5, 7}}; + auto gen = from(nums) | rconcat; + EXPECT_EQ(17, gen | sum); + EXPECT_EQ(10, gen | take(3) | sum); +} + +TEST(Gen, ConcatGen) { + auto gen = seq(1, 10) + | map([](int i) { return seq(1, i); }) + | concat; + EXPECT_EQ(220, gen | sum); + EXPECT_EQ(10, gen | take(6) | sum); +} + +TEST(Gen, ConcatAlt) { + std::vector> nums {{2, 3}, {5, 7}}; + auto actual = from(nums) + | map([](std::vector& v) { return from(v); }) + | concat + | sum; + auto expected = 17; + EXPECT_EQ(expected, actual); +} + +TEST(Gen, Order) { + auto expected = vector{0, 3, 5, 6, 7, 8, 9}; + auto actual = + from({8, 6, 7, 5, 3, 0, 9}) + | order + | as(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, OrderMoved) { + auto expected = vector{0, 9, 25, 36, 49, 64, 81}; + auto actual = + from({8, 6, 7, 5, 3, 0, 9}) + | move + | order + | map(square) + | as(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, OrderTake) { + auto expected = vector{9, 8, 7}; + auto actual = + from({8, 6, 7, 5, 3, 0, 9}) + | orderByDescending(square) + | take(3) + | as(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, MinBy) { + EXPECT_EQ(7, seq(1, 10) + | minBy([](int i) { + auto d = i - 6.8; + return d * d; + })); +} + +TEST(Gen, MaxBy) { + auto gen = from({"three", "eleven", "four"}); + + EXPECT_EQ("eleven", gen | maxBy(&strlen)); +} + +TEST(Gen, Append) { + fbstring expected = "facebook"; + fbstring actual = "face"; + from(StringPiece("book")) | appendTo(actual); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, FromRValue) { + { + // AFAICT The C++ Standard does not specify what happens to the rvalue + // 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. + folly::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; + EXPECT_EQ(expected, q1 | product); + + auto q2 = from(std::move(v)); + EXPECT_EQ(v.size(), 0); // ensure that rvalue version was called + EXPECT_EQ(expected, q2 | product); + } + { + auto expected = 7; + auto q = from([] {return vector({3,7,5}); }()); + EXPECT_EQ(expected, q | max); + } + { + 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 + // (disgustingly!) just store a *reference* to the temporary object, + // which is bad. Try to catch this by allocating two temporary vectors + // of the same size, so that they'll probably use the same underlying + // buffer if q1's vector is destructed before q2's vector is constructed. + EXPECT_EQ(size * 2 + size * 3, (q1 | sum) + (q2 | sum)); + } + } + { + auto q = from(set{1,2,3,2,1}); + EXPECT_EQ(q | sum, 6); + } +} + +TEST(Gen, OrderBy) { + auto expected = vector{5, 6, 4, 7, 3, 8, 2, 9, 1, 10}; + auto actual = + seq(1, 10) + | orderBy([](int x) { return (5.1 - x) * (5.1 - x); }) + | as(); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, Foldl) { + int expected = 2 * 3 * 4 * 5; + auto actual = + seq(2, 5) + | foldl(1, multiply); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, Reduce) { + int expected = 2 + 3 + 4 + 5; + auto actual = seq(2, 5) | reduce(add); + EXPECT_EQ(expected, actual); +} + +TEST(Gen, ReduceBad) { + auto gen = seq(1) | take(0); + try { + EXPECT_TRUE(true); + gen | reduce(add); + EXPECT_TRUE(false); + } catch (...) { + } +} + +TEST(Gen, Moves) { + std::vector> ptrs; + ptrs.emplace_back(new int(1)); + EXPECT_NE(ptrs.front().get(), nullptr); + auto ptrs2 = from(ptrs) | move | as(); + EXPECT_EQ(ptrs.front().get(), nullptr); + EXPECT_EQ(**ptrs2.data(), 1); +} + +TEST(Gen, First) { + auto gen = + seq(0) + | filter([](int x) { return x > 3; }); + EXPECT_EQ(4, gen | first); +} + +TEST(Gen, FromCopy) { + vector v {3, 5}; + auto src = from(v); + auto copy = fromCopy(v); + EXPECT_EQ(8, src | sum); + EXPECT_EQ(8, copy | sum); + v[1] = 7; + EXPECT_EQ(10, src | sum); + EXPECT_EQ(8, copy | sum); +} + +TEST(Gen, Get) { + std::map pairs { + {1, 1}, + {2, 4}, + {3, 9}, + {4, 16}, + }; + auto pairSrc = from(pairs); + auto keys = pairSrc | get<0>(); + auto values = pairSrc | get<1>(); + EXPECT_EQ(10, keys | sum); + EXPECT_EQ(30, values | sum); + EXPECT_EQ(30, keys | map(square) | sum); + pairs[5] = 25; + EXPECT_EQ(15, keys | sum); + EXPECT_EQ(55, values | sum); + + vector> tuples { + make_tuple(1, 1, 1), + make_tuple(2, 4, 8), + make_tuple(3, 9, 27), + }; + EXPECT_EQ(36, from(tuples) | get<2>() | sum); +} + +TEST(Gen, Any) { + EXPECT_TRUE(seq(0) | any); + EXPECT_TRUE(seq(0, 1) | any); + EXPECT_TRUE(from({1}) | any); + EXPECT_FALSE(range(0, 0) | any); + EXPECT_FALSE(from({1}) | take(0) | any); +} + +TEST(Gen, Yielders) { + auto gen = GENERATOR(int, { + for (int i = 1; i <= 5; ++i) { + yield(i); + } + yield(7); + for (int i = 3; ; ++i) { + yield(i * i); + } + }); + vector expected { + 1, 2, 3, 4, 5, 7, 9, 16, 25 + }; + EXPECT_EQ(expected, gen | take(9) | as()); +} + +TEST(Gen, NestedYield) { + auto nums = GENERATOR(int, { + for (int i = 1; ; ++i) { + yield(i); + } + }); + auto gen = GENERATOR(int, { + nums | take(10) | yield; + seq(1, 5) | [&](int i) { + yield(i); + }; + }); + EXPECT_EQ(70, gen | sum); +} + +TEST(Gen, MapYielders) { + auto gen = seq(1, 5) + | map([](int n) { + return GENERATOR(int, { + int i; + for (i = 1; i < n; ++i) + yield(i); + for (; i >= 1; --i) + yield(i); + }); + }) + | concat; + vector expected { + 1, + 1, 2, 1, + 1, 2, 3, 2, 1, + 1, 2, 3, 4, 3, 2, 1, + 1, 2, 3, 4, 5, 4, 3, 2, 1, + }; + EXPECT_EQ(expected, gen | as()); +} + +TEST(Gen, VirtualGen) { + VirtualGen v(seq(1, 10)); + EXPECT_EQ(55, v | sum); + v = v | map(square); + EXPECT_EQ(385, v | sum); + v = v | take(5); + EXPECT_EQ(55, v | sum); + EXPECT_EQ(30, v | take(4) | sum); +} + + +TEST(Gen, CustomType) { + struct Foo{ + int y; + }; + auto gen = from({Foo{2}, Foo{3}}) + | map([](const Foo& f) { return f.y; }); + EXPECT_EQ(5, gen | sum); +} + +TEST(Gen, NoNeedlessCopies) { + auto gen = seq(1, 5) + | map([](int x) { return unique_ptr(new int(x)); }) + | map([](unique_ptr p) { return p; }) + | map([](unique_ptr&& p) { return std::move(p); }) + | map([](const unique_ptr& p) { return *p; }); + EXPECT_EQ(15, gen | sum); + EXPECT_EQ(6, gen | take(3) | sum); +} + +TEST(Gen, FromArray) { + int source[] = {2, 3, 5, 7}; + auto gen = from(source); + EXPECT_EQ(2 * 3 * 5 * 7, gen | product); +} + +TEST(Gen, FromStdArray) { + std::array source {{2, 3, 5, 7}}; + auto gen = from(source); + EXPECT_EQ(2 * 3 * 5 * 7, gen | product); +} + +TEST(Gen, StringConcat) { + auto gen = seq(1, 10) + | map([](int n) { return folly::to(n); }) + | rconcat; + EXPECT_EQ("12345678910", gen | as()); +} + +struct CopyCounter { + static int alive; + int copies; + int moves; + + CopyCounter() : copies(0), moves(0) { + ++alive; + } + + CopyCounter(CopyCounter&& source) { + *this = std::move(source); + ++alive; + } + + CopyCounter(const CopyCounter& source) { + *this = source; + ++alive; + } + + ~CopyCounter() { + --alive; + } + + CopyCounter& operator=(const CopyCounter& source) { + this->copies = source.copies + 1; + this->moves = source.moves; + return *this; + } + + CopyCounter& operator=(CopyCounter&& source) { + this->copies = source.copies; + this->moves = source.moves + 1; + return *this; + } +}; + +int CopyCounter::alive = 0; + +TEST(Gen, CopyCount) { + vector originals; + originals.emplace_back(); + EXPECT_EQ(1, originals.size()); + EXPECT_EQ(0, originals.back().copies); + + vector copies = from(originals) | as(); + EXPECT_EQ(1, copies.back().copies); + EXPECT_EQ(0, copies.back().moves); + + vector moves = from(originals) | move | as(); + EXPECT_EQ(0, moves.back().copies); + EXPECT_EQ(1, moves.back().moves); +} + +// test dynamics with various layers of nested arrays. +TEST(Gen, Dynamic) { + dynamic array1 = {1, 2}; + EXPECT_EQ(dynamic(3), from(array1) | sum); + dynamic array2 = {{1}, {1, 2}}; + EXPECT_EQ(dynamic(4), from(array2) | rconcat | sum); + dynamic array3 = {{{1}}, {{1}, {1, 2}}}; + EXPECT_EQ(dynamic(5), from(array3) | rconcat | rconcat | sum); +} + +int main(int argc, char *argv[]) { + testing::InitGoogleTest(&argc, argv); + google::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +}