Commit 506b9a32 authored by Tom Jackson's avatar Tom Jackson Committed by Jordan DeLong

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<T, Self>##.
# Operators: Standalone classes which, when composed with a generator, produce a new generator. These all inherit from ##OperatorImpl<Self>##.

These generators may be composed with ##operator|## overloads which only match ##const GenImpl<T, Self>&## 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
parent 99d9bfdd
/*
* 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<FunctorType, bool(int, float)>::value
*/
template<class Candidate, class Expected>
class IsCompatibleSignature {
static constexpr bool value = false;
};
template<class Candidate,
class ExpectedReturn,
class... ArgTypes>
class IsCompatibleSignature<Candidate, ExpectedReturn(ArgTypes...)> {
template<class F,
class ActualReturn =
decltype(std::declval<F>()(std::declval<ArgTypes>()...)),
bool good = std::is_same<ExpectedReturn, ActualReturn>::value>
static constexpr bool testArgs(int* p) {
return good;
}
template<class F>
static constexpr bool testArgs(...) {
return false;
}
public:
static constexpr bool value = testArgs<Candidate>(nullptr);
};
/**
* ArgumentReference - For determining ideal argument type to receive a value.
*/
template<class T>
struct ArgumentReference :
public std::conditional<std::is_reference<T>::value,
T, // T& -> T&, T&& -> T&&, const T& -> const T&
typename std::conditional<
std::is_const<T>::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<class Self>
struct FBounded {
const Self& self() const {
return *static_cast<const Self*>(this);
}
Self& self() {
return *static_cast<Self*>(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 Self>
class Operator : public FBounded<Self> {
public:
/**
* compose() - Must be implemented by child class to compose a new Generator
* out of a given generator. This function left intentionally unimplemented.
*/
template<class Source,
class Value,
class ResultGen = void>
ResultGen compose(const GenImpl<Value, Source>& 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 Value,
class Self>
class GenImpl : public FBounded<Self> {
protected:
// To prevent slicing
GenImpl() = default;
GenImpl(const GenImpl&) = default;
GenImpl(GenImpl&&) = default;
public:
typedef Value ValueType;
typedef typename std::decay<Value>::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<class Handler>
bool apply(Handler&& handler) const;
/**
* foreach() - Send all values produced by this generator to given lambda.
*/
template<class Body>
void foreach(Body&& body) const {
this->self().apply([&](Value value) {
body(std::forward<Value>(value));
return true;
});
}
template<class Next,
class Chain = detail::Chain<Value, Self, Next>,
class ValueNext>
Chain operator+(const GenImpl<ValueNext, Next>& next) const {
static_assert(
std::is_same<Value, ValueNext>::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<class Value,
class Gen,
class Handler>
typename std::enable_if<
IsCompatibleSignature<Handler, void(Value)>::value>::type
operator|(const GenImpl<Value, Gen>& gen, Handler&& handler) {
gen.self().foreach(std::forward<Handler>(handler));
}
/**
* operator|() which enables foreach-like usage with 'break' support:
* gen | [](Value v) -> bool { return shouldContinue(); };
*/
template<class Value,
class Gen,
class Handler>
typename std::enable_if<
IsCompatibleSignature<Handler, bool(Value)>::value>::type
operator|(const GenImpl<Value, Gen>& gen, Handler&& handler) {
gen.self().apply(std::forward<Handler>(handler));
}
/**
* operator|() for composing generators with operators, similar to boosts' range
* adaptors:
* gen | map(square) | sum
*/
template<class Value,
class Gen,
class Op>
auto operator|(const GenImpl<Value, Gen>& gen, const Operator<Op>& 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<int>, 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 Container,
class Value>
class ReferencedSource :
public GenImpl<Value, ReferencedSource<Container, Value>> {
Container* const container_;
public:
explicit ReferencedSource(Container* container)
: container_(container) {}
template<class Body>
void foreach(Body&& body) const {
for (auto& value : *container_) {
body(std::forward<Value>(value));
}
}
template<class Handler>
bool apply(Handler&& handler) const {
for (auto& value : *container_) {
if (!handler(std::forward<Value>(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 StorageType,
class Container>
class CopiedSource :
public GenImpl<const StorageType&,
CopiedSource<StorageType, Container>> {
static_assert(
!std::is_reference<StorageType>::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<Container>::value,
"Can't copy into a reference");
const std::shared_ptr<const Container> copy_;
public:
typedef Container ContainerType;
template<class SourceContainer>
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<StorageType, Container>& source)
: copy_(source.copy_) {}
template<class Body>
void foreach(Body&& body) const {
for (const auto& value : *copy_) {
body(value);
}
}
template<class Handler>
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 Value,
bool endless,
bool endInclusive>
class Sequence : public GenImpl<const Value&,
Sequence<Value, endless, endInclusive>> {
static_assert(!std::is_reference<Value>::value &&
!std::is_const<Value>::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<class Handler>
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<class Body>
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 Value, class First, class Second>
class Chain : public GenImpl<Value,
Chain<Value, First, Second>> {
const First first_;
const Second second_;
public:
explicit Chain(const GenImpl<Value, First>& first,
const GenImpl<Value, Second>& second)
: first_(first.self())
, second_(second.self()) {}
template<class Handler>
bool apply(Handler&& handler) const {
return first_.apply(std::forward<Handler>(handler))
&& second_.apply(std::forward<Handler>(handler));
}
template<class Body>
void foreach(Body&& body) const {
first_.foreach(std::forward<Body>(body));
second_.foreach(std::forward<Body>(body));
}
};
/**
* Yield - For producing values from a user-defined generator by way of a
* 'yield' function.
**/
template<class Value, class Source>
class Yield : public GenImpl<Value, Yield<Value, Source>> {
const Source source_;
public:
explicit Yield(const Source& source)
: source_(source) {
}
template<class Handler>
bool apply(Handler&& handler) const {
struct Break {};
auto body = [&](Value value) {
if (!handler(std::forward<Value>(value))) {
throw Break();
}
};
try {
source_(body);
return true;
} catch (Break&) {
return false;
}
}
template<class Body>
void foreach(Body&& body) const {
source_(std::forward<Body>(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 Predicate>
class Map : public Operator<Map<Predicate>> {
const Predicate predicate_;
public:
explicit Map(const Predicate& predicate = Predicate())
: predicate_(predicate)
{ }
template<class Value,
class Source,
class Result = typename ArgumentReference<
typename std::result_of<Predicate(Value)>::type
>::type>
class Generator :
public GenImpl<Result, Generator<Value, Source, Result>> {
const Source source_;
const Predicate pred_;
public:
explicit Generator(const Source& source, const Predicate& pred)
: source_(source), pred_(pred) {}
template<class Body>
void foreach(Body&& body) const {
source_.foreach([&](Value value) {
body(pred_(std::forward<Value>(value)));
});
}
template<class Handler>
bool apply(Handler&& handler) const {
return source_.apply([&](Value value) {
return handler(pred_(std::forward<Value>(value)));
});
}
};
template<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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 Predicate>
class Filter : public Operator<Filter<Predicate>> {
const Predicate predicate_;
public:
explicit Filter(const Predicate& predicate)
: predicate_(predicate)
{ }
template<class Value,
class Source>
class Generator : public GenImpl<Value, Generator<Value, Source>> {
const Source source_;
const Predicate pred_;
public:
explicit Generator(const Source& source, const Predicate& pred)
: source_(source), pred_(pred) {}
template<class Body>
void foreach(Body&& body) const {
source_.foreach([&](Value value) {
if (pred_(std::forward<Value>(value))) {
body(std::forward<Value>(value));
}
});
}
template<class Handler>
bool apply(Handler&& handler) const {
return source_.apply([&](Value value) -> bool {
if (pred_(std::forward<Value>(value))) {
return handler(std::forward<Value>(value));
}
return true;
});
}
};
template<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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 Predicate>
class Until : public Operator<Until<Predicate>> {
const Predicate predicate_;
public:
explicit Until(const Predicate& predicate)
: predicate_(predicate)
{ }
template<class Value,
class Source,
class Result = typename std::result_of<Predicate(Value)>::type>
class Generator :
public GenImpl<Result, Generator<Value, Source, Result>> {
const Source source_;
const Predicate pred_;
public:
explicit Generator(const Source& source, const Predicate& pred)
: source_(source), pred_(pred) {}
template<class Handler>
bool apply(Handler&& handler) const {
return source_.apply([&](Value value) -> bool {
return !pred_(std::forward<Value>(value))
&& handler(std::forward<Value>(value));
});
}
};
template<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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<Take> {
const size_t count_;
public:
explicit Take(size_t count)
: count_(count) {}
template<class Value,
class Source>
class Generator :
public GenImpl<Value, Generator<Value, Source>> {
const Source source_;
const size_t count_;
public:
explicit Generator(const Source& source, size_t count)
: source_(source) , count_(count) {}
template<class Handler>
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>(value))) {
return false;
}
return --n;
});
}
};
template<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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<Skip> {
const size_t count_;
public:
explicit Skip(size_t count)
: count_(count) {}
template<class Value,
class Source>
class Generator :
public GenImpl<Value, Generator<Value, Source>> {
const Source source_;
const size_t count_;
public:
explicit Generator(const Source& source, size_t count)
: source_(source) , count_(count) {}
template<class Body>
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>(value));
}
});
}
template<class Handler>
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>(value));
});
}
};
template<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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 Selector, class Comparer>
class Order : public Operator<Order<Selector, Comparer>> {
const Selector selector_;
const Comparer comparer_;
public:
Order(const Selector& selector = Selector(),
const Comparer& comparer = Comparer())
: selector_(selector) , comparer_(comparer) {}
template<class Value,
class Source,
class StorageType = typename std::decay<Value>::type,
class Result = typename std::result_of<Selector(Value)>::type>
class Generator :
public GenImpl<StorageType&&,
Generator<Value, Source, StorageType, Result>> {
const Source source_;
const Selector selector_;
const Comparer comparer_;
typedef std::vector<StorageType> VectorType;
VectorType asVector() const {
auto comparer = [&](const StorageType& a, const StorageType& b) {
return comparer_(selector_(a), selector_(b));
};
auto vals = source_ | as<VectorType>();
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<VectorType>&) const {
return asVector();
}
VectorType operator|(const CollectTemplate<std::vector>&) const {
return asVector();
}
template<class Body>
void foreach(Body&& body) const {
for (auto& value : asVector()) {
body(std::move(value));
}
}
template<class Handler>
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<VectorType>();
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<class Source,
class Value,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& 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 Seed,
class Fold>
class FoldLeft : public Operator<FoldLeft<Seed, Fold>> {
const Seed seed_;
const Fold fold_;
public:
FoldLeft(const Seed& seed, const Fold& fold)
: seed_(seed)
, fold_(fold)
{}
template<class Source,
class Value>
Seed compose(const GenImpl<Value, Source>& source) const {
Seed accum = seed_;
source | [&](Value v) {
accum = fold_(std::move(accum), std::forward<Value>(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<First> {
public:
template<class Source,
class Value,
class StorageType = typename std::decay<Value>::type>
StorageType compose(const GenImpl<Value, Source>& source) const {
static_assert(std::is_same<StorageType, int>::value, "wtf");
Optional<StorageType> accum;
source | [&](Value v) -> bool {
accum = std::forward<Value>(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<Any> {
public:
template<class Source,
class Value>
bool compose(const GenImpl<Value, Source>& 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 Reducer>
class Reduce : public Operator<Reduce<Reducer>> {
const Reducer reducer_;
public:
Reduce(const Reducer& reducer)
: reducer_(reducer)
{}
template<class Source,
class Value,
class StorageType = typename std::decay<Value>::type>
StorageType compose(const GenImpl<Value, Source>& source) const {
Optional<StorageType> accum;
source | [&](Value v) {
if (accum.hasValue()) {
accum = reducer_(std::move(accum.value()), std::forward<Value>(v));
} else {
accum = std::forward<Value>(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<Count> {
public:
template<class Source,
class Value>
size_t compose(const GenImpl<Value, Source>& 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<Sum> {
public:
template<class Source,
class Value,
class StorageType = typename std::decay<Value>::type>
StorageType compose(const GenImpl<Value, Source>& source) const {
return foldl(StorageType(0),
[](StorageType&& accum, Value v) {
return std::move(accum) + std::forward<Value>(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 Selector,
class Comparer>
class Min : public Operator<Min<Selector, Comparer>> {
Selector selector_;
Comparer comparer_;
public:
Min(const Selector& selector = Selector(),
const Comparer& comparer = Comparer())
: selector_(selector)
, comparer_(comparer)
{}
template<class Value,
class Source,
class StorageType = typename std::decay<Value>::type,
class Key = typename std::decay<
typename std::result_of<Selector(Value)>::type
>::type>
StorageType compose(const GenImpl<Value, Source>& source) const {
Optional<StorageType> min;
Optional<Key> minKey;
source | [&](Value v) {
Key key = selector_(std::forward<Value>(v));
if (!minKey.hasValue() || comparer_(key, minKey.value())) {
minKey = key;
min = std::forward<Value>(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<int64_t> ids;
* from(results) | map([](Person& p) { return p.id })
* | appendTo(ids);
*/
template<class Collection>
class Append : public Operator<Append<Collection>> {
Collection* const collection_;
public:
explicit Append(Collection* collection)
: collection_(collection)
{}
template<class Value,
class Source>
Collection& compose(const GenImpl<Value, Source>& source) const {
source | [&](Value v) {
collection_->insert(collection_->end(), std::forward<Value>(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<std::string>();
*/
template<class Collection>
class Collect : public Operator<Collect<Collection>> {
public:
template<class Value,
class Source,
class StorageType = typename std::decay<Value>::type>
Collection compose(const GenImpl<Value, Source>& source) const {
Collection collection;
source | [&](Value v) {
collection.insert(collection.end(), std::forward<Value>(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<Value, Allocator<Value>>
*
* The allocator defaults to std::allocator, so this may be used for the STL
* containers by simply using operators like 'as<set>', 'as<deque>',
* 'as<vector>'. 'as', here is the helper method which is the usual means of
* consturcting this operator.
*
* Example:
*
* set<string> uniqueNames = from(names) | as<set>();
*/
template<template<class, class> class Container,
template<class> class Allocator>
class CollectTemplate : public Operator<CollectTemplate<Container, Allocator>> {
public:
template<class Value,
class Source,
class StorageType = typename std::decay<Value>::type,
class Collection = Container<StorageType, Allocator<StorageType>>>
Collection compose(const GenImpl<Value, Source>& source) const {
Collection collection;
source | [&](Value v) {
collection.insert(collection.end(), std::forward<Value>(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<std::set>();
*/
class Concat : public Operator<Concat> {
public:
template<class Inner,
class Source,
class InnerValue = typename std::decay<Inner>::type::ValueType>
class Generator :
public GenImpl<InnerValue, Generator<Inner, Source, InnerValue>> {
const Source source_;
public:
explicit Generator(const Source& source)
: source_(source) {}
template<class Handler>
bool apply(Handler&& handler) const {
return source_.apply([&](Inner inner) -> bool {
return inner.apply(std::forward<Handler>(handler));
});
}
template<class Body>
void foreach(Body&& body) const {
source_.foreach([&](Inner inner) {
inner.foreach(std::forward<Body>(body));
});
}
};
template<class Value,
class Source,
class Gen = Generator<Value, Source>>
Gen compose(const GenImpl<Value, Source>& source) const {
return Gen(source.self());
}
};
/**
* RangeConcat - For flattening generators of generators.
*
* This type is usually used through the 'rconcat' static value, like:
*
* map<int, vector<int>> adjacency;
* auto sinks =
* from(adjacency)
* | get<1>()
* | rconcat()
* | as<std::set>();
*/
class RangeConcat : public Operator<RangeConcat> {
public:
template<class Source,
class Range,
class InnerValue = typename ValueTypeOfRange<Range>::RefType>
class Generator
: public GenImpl<InnerValue, Generator<Source, Range, InnerValue>> {
const Source source_;
public:
Generator(const Source& source)
: source_(source) {}
template<class Body>
void foreach(Body&& body) const {
source_.foreach([&](Range range) {
for (auto& value : range) {
body(value);
}
});
}
template<class Handler>
bool apply(Handler&& handler) const {
return source_.apply([&](Range range) {
for (auto& value : range) {
if (!handler(value)) {
return false;
}
}
return true;
});
}
};
template<class Value,
class Source,
class Gen = Generator<Source, Value>>
Gen compose(const GenImpl<Value, Source>& source) const {
return Gen(source.self());
}
};
} //::detail
/**
* Gen<T> - For wrapping template types in simple polymorphic wrapper.
*
* This type is usually used through the 'rconcat' static value, like:
*
* map<int, vector<int>> adjacency;
* auto sinks =
* from(adjacency)
* | get<1>()
* | rconcat()
* | as<std::set>();
**/
template<class Value>
class VirtualGen : public GenImpl<Value, VirtualGen<Value>> {
class WrapperBase {
public:
virtual ~WrapperBase() {}
virtual bool apply(const std::function<bool(Value)>& handler) const = 0;
virtual void foreach(const std::function<void(Value)>& body) const = 0;
virtual std::unique_ptr<const WrapperBase> clone() const = 0;
};
template<class Wrapped>
class WrapperImpl : public WrapperBase {
const Wrapped wrapped_;
public:
WrapperImpl(const Wrapped& wrapped)
: wrapped_(wrapped) {
}
virtual bool apply(const std::function<bool(Value)>& handler) const {
return wrapped_.apply(handler);
}
virtual void foreach(const std::function<void(Value)>& body) const {
wrapped_.foreach(body);
}
virtual std::unique_ptr<const WrapperBase> clone() const {
return std::unique_ptr<const WrapperBase>(new WrapperImpl(wrapped_));
}
};
std::unique_ptr<const WrapperBase> wrapper_;
public:
template<class SourceValue,
class Self>
/* implicit */ VirtualGen(const GenImpl<SourceValue, Self>& source)
: wrapper_(new WrapperImpl<Self>(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<bool(Value)>& handler) const {
return wrapper_->apply(handler);
}
void foreach(const std::function<void(Value)>& 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<Identity, Less> min;
static const detail::Min<Identity, Greater> max;
static const detail::Order<Identity> order;
static const detail::Map<Move> 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
/*
* 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 <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include <algorithm>
#include "folly/Range.h"
#include "folly/Optional.h"
/**
* Generator-based Sequence Comprehensions in C++, akin to C#'s LINQ
* @author Tom Jackson <tjackson@fb.com>
*
* 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<string> 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<T>
* may be used to wrap the generator in a polymorphic wrapper:
*
* VirtualGen<float> 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 Value, class Self>
class GenImpl;
template<class Self>
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<class First,
class Second>
auto operator()(const First& first, const Second& second) const ->
decltype(first < second) {
return first < second;
}
};
class Greater {
public:
template<class First,
class Second>
auto operator()(const First& first, const Second& second) const ->
decltype(first > second) {
return first > second;
}
};
template<int n>
class Get {
public:
template<class Value>
auto operator()(Value&& value) const ->
decltype(std::get<n>(std::forward<Value>(value))) {
return std::get<n>(std::forward<Value>(value));
}
};
class Move {
public:
template<class Value>
auto operator()(Value&& value) const ->
decltype(std::move(std::forward<Value>(value))) {
return std::move(std::forward<Value>(value));
}
};
class Identity {
public:
template<class Value>
auto operator()(Value&& value) const ->
decltype(std::forward<Value>(value)) {
return std::forward<Value>(value);
}
};
namespace detail {
template<class Self>
struct FBounded;
/*
* Type Traits
*/
template<class Container>
struct ValueTypeOfRange {
private:
static Container container_;
public:
typedef decltype(*std::begin(container_))
RefType;
typedef typename std::decay<decltype(*std::begin(container_))>::type
StorageType;
};
/*
* Sources
*/
template<class Container,
class Value = typename ValueTypeOfRange<Container>::RefType>
class ReferencedSource;
template<class Value,
class Container = std::vector<typename std::decay<Value>::type>>
class CopiedSource;
template<class Value, bool endless = false, bool endInclusive = false>
class Sequence;
template<class Value, class First, class Second>
class Chain;
template<class Value, class Source>
class Yield;
/*
* Operators
*/
template<class Predicate>
class Map;
template<class Predicate>
class Filter;
template<class Predicate>
class Until;
class Take;
class Skip;
template<class Selector, class Comparer = Less>
class Order;
/*
* Sinks
*/
template<class Seed,
class Fold>
class FoldLeft;
template<class Reducer>
class Reduce;
class Sum;
template<class Selector,
class Comparer>
class Min;
template<class Container>
class Collect;
template<template<class, class> class Collection = std::vector,
template<class> class Allocator = std::allocator>
class CollectTemplate;
template<class Collection>
class Append;
}
/**
* Polymorphic wrapper
**/
template<class Value>
class VirtualGen;
/*
* Source Factories
*/
template<class Container,
class From = detail::ReferencedSource<const Container>>
From fromConst(const Container& source) {
return From(&source);
}
template<class Container,
class From = detail::ReferencedSource<Container>>
From from(Container& source) {
return From(&source);
}
template<class Container,
class Value =
typename detail::ValueTypeOfRange<Container>::StorageType,
class CopyOf = detail::CopiedSource<Value>>
CopyOf fromCopy(Container&& source) {
return CopyOf(std::forward<Container>(source));
}
template<class Value,
class From = detail::CopiedSource<Value>>
From from(std::initializer_list<Value> source) {
return From(source);
}
template<class Container,
class From = detail::CopiedSource<typename Container::value_type,
Container>>
From from(Container&& source) {
return From(std::move(source));
}
template<class Value, class Gen = detail::Sequence<Value, false, false>>
Gen range(Value begin, Value end) {
return Gen(begin, end);
}
template<class Value,
class Gen = detail::Sequence<Value, false, true>>
Gen seq(Value first, Value last) {
return Gen(first, last);
}
template<class Value,
class Gen = detail::Sequence<Value, true>>
Gen seq(Value begin) {
return Gen(begin);
}
template<class Value,
class Source,
class Yield = detail::Yield<Value, Source>>
Yield generator(Source&& source) {
return Yield(std::forward<Source>(source));
}
#define GENERATOR(type, body) \
::folly::gen::generator<type>( \
[=](const std::function<void(type)>& yield) \
{ body })
/*
* Operator Factories
*/
template<class Predicate,
class Map = detail::Map<Predicate>>
Map mapped(const Predicate& pred = Predicate()) {
return Map(pred);
}
template<class Predicate,
class Map = detail::Map<Predicate>>
Map map(const Predicate& pred = Predicate()) {
return Map(pred);
}
template<class Predicate,
class Filter = detail::Filter<Predicate>>
Filter filter(const Predicate& pred = Predicate()) {
return Filter(pred);
}
template<class Predicate,
class Until = detail::Until<Predicate>>
Until until(const Predicate& pred = Predicate()) {
return Until(pred);
}
template<class Selector,
class Comparer = Less,
class Order = detail::Order<Selector, Comparer>>
Order orderBy(const Selector& selector,
const Comparer& comparer = Comparer()) {
return Order(selector, comparer);
}
template<class Selector,
class Order = detail::Order<Selector, Greater>>
Order orderByDescending(const Selector& selector) {
return Order(selector);
}
template<int n,
class Get = detail::Map<Get<n>>>
Get get() {
return Get();
}
/*
* Sink Factories
*/
template<class Seed,
class Fold,
class FoldLeft = detail::FoldLeft<Seed, Fold>>
FoldLeft foldl(const Seed& seed, const Fold& fold) {
return FoldLeft(seed, fold);
}
template<class Reducer,
class Reduce = detail::Reduce<Reducer>>
Reduce reduce(const Reducer& reducer) {
return Reduce(reducer);
}
template<class Selector,
class Min = detail::Min<Selector, Less>>
Min minBy(const Selector& selector = Selector()) {
return Min(selector);
}
template<class Selector,
class MaxBy = detail::Min<Selector, Greater>>
MaxBy maxBy(const Selector& selector = Selector()) {
return MaxBy(selector);
}
template<class Collection,
class Collect = detail::Collect<Collection>>
Collect as() {
return Collect();
}
template<template<class, class> class Container = std::vector,
template<class> class Allocator = std::allocator,
class Collect = detail::CollectTemplate<Container, Allocator>>
Collect as() {
return Collect();
}
template<class Collection,
class Append = detail::Append<Collection>>
Append appendTo(Collection& collection) {
return Append(&collection);
}
}} // folly::gen
#include "folly/experimental/Gen-inl.h"
/*
* 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 <glog/logging.h>
#include <atomic>
#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<int> testSize(1000);
static vector<int> testVector =
seq(1, testSize.load())
| mapped([](int) { return rand(); })
| as<vector>();
static vector<vector<int>> testVectorVector =
seq(1, 100)
| map([](int i) {
return seq(1, i) | as<vector>();
})
| as<vector>();
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<int> {
vector<int> 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<class Yield>
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<int>(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<int> 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<int> numbers = seq(1, 10000);
VirtualGen<int> 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<int> numbers = seq(1, 10000);
VirtualGen<int> squares = numbers | map(square);
VirtualGen<int> 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;
}
/*
* 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 <glog/logging.h>
#include <gtest/gtest.h>
#include <iostream>
#include <set>
#include <vector>
#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<A, B>::value, "Mismatched: " #A ", " #B)
EXPECT_SAME(int&&, typename ArgumentReference<int>::type);
EXPECT_SAME(int&, typename ArgumentReference<int&>::type);
EXPECT_SAME(const int&, typename ArgumentReference<const int&>::type);
EXPECT_SAME(const int&, typename ArgumentReference<const int>::type);
template<typename T>
ostream& operator<<(ostream& os, const set<T>& values) {
return os << from(values);
}
template<typename T>
ostream& operator<<(ostream& os, const vector<T>& 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<typename A, typename B>
ostream& operator<<(ostream& os, const pair<A, B>& 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<int>{4, 9, 16};
auto gen = from({2, 3, 4}) | map(square);
EXPECT_EQ((vector<int>{4, 9, 16}), gen | as<vector>());
EXPECT_EQ((vector<int>{4, 9}), gen | take(2) | as<vector>());
}
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<int> 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<std::map<int, int>>();
auto gen = fromConst(source)
| map([&](const std::pair<const int, int>& p) {
return p.second - p.first;
});
EXPECT_EQ(330, gen | sum);
}
TEST(Gen, Filter) {
const auto expected = vector<int>{1, 2, 4, 5, 7, 8};
auto actual =
seq(1, 9)
| filter([](int x) { return x % 3; })
| as<vector<int>>();
EXPECT_EQ(expected, actual);
}
TEST(Gen, Take) {
auto expected = vector<int>{1, 4, 9, 16};
auto actual =
seq(1, 1000)
| mapped([](int x) { return x * x; })
| take(4)
| as<vector<int>>();
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<int>{25, 36, 49, 64}), gen | as<vector>());
}
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<int> nums {2, 3, 5, 7};
std::map<int, int> 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<std::vector<int>> 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<std::vector<int>> nums {{2, 3}, {5, 7}};
auto actual = from(nums)
| map([](std::vector<int>& v) { return from(v); })
| concat
| sum;
auto expected = 17;
EXPECT_EQ(expected, actual);
}
TEST(Gen, Order) {
auto expected = vector<int>{0, 3, 5, 6, 7, 8, 9};
auto actual =
from({8, 6, 7, 5, 3, 0, 9})
| order
| as<vector>();
EXPECT_EQ(expected, actual);
}
TEST(Gen, OrderMoved) {
auto expected = vector<int>{0, 9, 25, 36, 49, 64, 81};
auto actual =
from({8, 6, 7, 5, 3, 0, 9})
| move
| order
| map(square)
| as<vector>();
EXPECT_EQ(expected, actual);
}
TEST(Gen, OrderTake) {
auto expected = vector<int>{9, 8, 7};
auto actual =
from({8, 6, 7, 5, 3, 0, 9})
| orderByDescending(square)
| take(3)
| as<vector>();
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<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;
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<int>({3,7,5}); }());
EXPECT_EQ(expected, q | max);
}
{
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
// (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<int>{1,2,3,2,1});
EXPECT_EQ(q | sum, 6);
}
}
TEST(Gen, OrderBy) {
auto expected = vector<int>{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<vector>();
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<unique_ptr<int>> ptrs;
ptrs.emplace_back(new int(1));
EXPECT_NE(ptrs.front().get(), nullptr);
auto ptrs2 = from(ptrs) | move | as<vector>();
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<int> 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<int, int> 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<tuple<int, int, int>> 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<int> expected {
1, 2, 3, 4, 5, 7, 9, 16, 25
};
EXPECT_EQ(expected, gen | take(9) | as<vector>());
}
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<int> 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<vector>());
}
TEST(Gen, VirtualGen) {
VirtualGen<int> 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<int>(new int(x)); })
| map([](unique_ptr<int> p) { return p; })
| map([](unique_ptr<int>&& p) { return std::move(p); })
| map([](const unique_ptr<int>& 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<int,4> 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<fbstring>(n); })
| rconcat;
EXPECT_EQ("12345678910", gen | as<fbstring>());
}
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<CopyCounter> originals;
originals.emplace_back();
EXPECT_EQ(1, originals.size());
EXPECT_EQ(0, originals.back().copies);
vector<CopyCounter> copies = from(originals) | as<vector>();
EXPECT_EQ(1, copies.back().copies);
EXPECT_EQ(0, copies.back().moves);
vector<CopyCounter> moves = from(originals) | move | as<vector>();
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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment