From: Jordan DeLong Date: Sun, 19 May 2013 21:47:39 +0000 (-0700) Subject: Add folly::lazy X-Git-Tag: v0.22.0~966 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=12c8603da89f7ea8f2fbd0a414125aa8117ac97b;p=folly.git 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 --- diff --git a/folly/Lazy.h b/folly/Lazy.h new file mode 100644 index 00000000..2b18ea7b --- /dev/null +++ b/folly/Lazy.h @@ -0,0 +1,125 @@ +/* + * 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 +#include + +#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 +struct Lazy { + typedef typename std::result_of::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(*this)(); + } + + result_type& operator()() { + if (!value_) value_ = func_(); + return *value_; + } + +private: + Optional value_; + Func func_; +}; + +} + +////////////////////////////////////////////////////////////////////// + +template +detail::Lazy::type> +lazy(Func&& fun) { + return detail::Lazy::type>( + std::forward(fun) + ); +} + +////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/folly/test/LazyTest.cpp b/folly/test/LazyTest.cpp new file mode 100644 index 00000000..62fd7050 --- /dev/null +++ b/folly/test/LazyTest.cpp @@ -0,0 +1,108 @@ +/* + * 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 +#include +#include + +#include + +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 { + 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 f = [&]{ return 12; }; + auto const lazyF = folly::lazy(f); + EXPECT_EQ(lazyF(), true); +} + +TEST(Lazy, Consty) { + std::function const f = [&] { return 12; }; + auto lz = folly::lazy(f); + EXPECT_EQ(lz(), 12); +} + +} +