Add folly::lazy
authorJordan DeLong <jdelong@fb.com>
Sun, 19 May 2013 21:47:39 +0000 (14:47 -0700)
committerSara Golemon <sgolemon@fb.com>
Thu, 23 May 2013 21:33:23 +0000 (14:33 -0700)
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

folly/Lazy.h [new file with mode: 0644]
folly/test/LazyTest.cpp [new file with mode: 0644]

diff --git a/folly/Lazy.h b/folly/Lazy.h
new file mode 100644 (file)
index 0000000..2b18ea7
--- /dev/null
@@ -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 <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
diff --git a/folly/test/LazyTest.cpp b/folly/test/LazyTest.cpp
new file mode 100644 (file)
index 0000000..62fd705
--- /dev/null
@@ -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 <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);
+}
+
+}
+