Commit 12c8603d authored by Jordan DeLong's avatar Jordan DeLong Committed by Sara Golemon

Add folly::lazy

Summary:
A thin wrapper around Optional for terse creation of
lazily-initialized values.

Test Plan: New tests, and a use case in hphp.

Reviewed By: tjackson@fb.com

FB internal diff: D817906
parent dc64a0e3
/*
* Copyright 2013 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.
*/
#ifndef FOLLY_LAZY_H_
#define FOLLY_LAZY_H_
#include <utility>
#include <type_traits>
#include "folly/Optional.h"
namespace folly {
//////////////////////////////////////////////////////////////////////
/*
* Lazy -- for delayed initialization of a value. The value's
* initialization will be computed on demand at its first use, but
* will not be recomputed if its value is requested again. The value
* may still be mutated after its initialization if the lazy is not
* declared const.
*
* The value is created using folly::lazy, usually with a lambda, and
* its value is requested using operator().
*
* Note that the value is not safe for current accesses by multiple
* threads, even if you declare it const.
*
*
* Example Usage:
*
* void foo() {
* auto const val = folly::lazy([&]{
* return something_expensive(blah());
* });
*
* if (condition1) {
* use(val());
* }
* if (condition2) {
* useMaybeAgain(val());
* } else {
* // Unneeded in this branch.
* }
* }
*
*
* Rationale:
*
* - operator() is used to request the value instead of an implicit
* conversion because the slight syntactic overhead in common
* seems worth the increased clarity.
*
* - Lazy values do not model CopyConstructible because it is
* unclear what semantics would be desirable. Either copies
* should share the cached value (adding overhead to cases that
* don't need to support copies), or they could recompute the
* value unnecessarily. Sharing with mutable lazies would also
* leave them with non-value semantics despite looking
* value-like.
*/
//////////////////////////////////////////////////////////////////////
namespace detail {
template<class Func>
struct Lazy {
typedef typename std::result_of<Func()>::type result_type;
explicit Lazy(Func&& f) : func_(std::move(f)) {}
explicit Lazy(Func& f) : func_(f) {}
Lazy(Lazy&& o)
: value_(std::move(o.value_))
, func_(std::move(o.func_))
{}
Lazy(const Lazy&) = delete;
Lazy& operator=(const Lazy&) = delete;
Lazy& operator=(Lazy&&) = delete;
const result_type& operator()() const {
return const_cast<Lazy&>(*this)();
}
result_type& operator()() {
if (!value_) value_ = func_();
return *value_;
}
private:
Optional<result_type> value_;
Func func_;
};
}
//////////////////////////////////////////////////////////////////////
template<class Func>
detail::Lazy<typename std::remove_reference<Func>::type>
lazy(Func&& fun) {
return detail::Lazy<typename std::remove_reference<Func>::type>(
std::forward<Func>(fun)
);
}
//////////////////////////////////////////////////////////////////////
}
#endif
/*
* Copyright 2013 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/Lazy.h"
#include <map>
#include <functional>
#include <iostream>
#include <gtest/gtest.h>
namespace folly {
TEST(Lazy, Simple) {
int computeCount = 0;
auto const val = folly::lazy([&]() -> int {
EXPECT_EQ(++computeCount, 1);
return 12;
});
EXPECT_EQ(computeCount, 0);
for (int i = 0; i < 100; ++i) {
if (i > 50) {
EXPECT_EQ(val(), 12);
EXPECT_EQ(computeCount, 1);
} else {
EXPECT_EQ(computeCount, 0);
}
}
EXPECT_EQ(val(), 12);
EXPECT_EQ(computeCount, 1);
}
auto globalCount = folly::lazy([]{ return 0; });
auto const foo = folly::lazy([]() -> std::string {
EXPECT_EQ(++globalCount(), 1);
return std::string("YEP");
});
TEST(Lazy, Global) {
EXPECT_EQ(globalCount(), 0);
EXPECT_EQ(foo(), "YEP");
EXPECT_EQ(globalCount(), 1);
}
TEST(Lazy, Map) {
auto lazyMap = folly::lazy([]() -> std::map<std::string,std::string> {
return {
{ "foo", "bar" },
{ "baz", "quux" }
};
});
EXPECT_EQ(lazyMap().size(), 2);
lazyMap()["blah"] = "asd";
EXPECT_EQ(lazyMap().size(), 3);
}
struct CopyCount {
CopyCount() {}
CopyCount(const CopyCount&) { ++count; }
CopyCount(CopyCount&&) {}
static int count;
bool operator()() const { return true ; }
};
int CopyCount::count = 0;
TEST(Lazy, NonLambda) {
auto const rval = folly::lazy(CopyCount());
EXPECT_EQ(CopyCount::count, 0);
EXPECT_EQ(rval(), true);
EXPECT_EQ(CopyCount::count, 0);
CopyCount cpy;
auto const lval = folly::lazy(cpy);
EXPECT_EQ(CopyCount::count, 1);
EXPECT_EQ(lval(), true);
EXPECT_EQ(CopyCount::count, 1);
std::function<bool()> f = [&]{ return 12; };
auto const lazyF = folly::lazy(f);
EXPECT_EQ(lazyF(), true);
}
TEST(Lazy, Consty) {
std::function<int ()> const f = [&] { return 12; };
auto lz = folly::lazy(f);
EXPECT_EQ(lz(), 12);
}
}
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