Introducing folly::partial
authorSven Over <over@fb.com>
Tue, 16 Aug 2016 15:42:54 +0000 (08:42 -0700)
committerFacebook Github Bot 4 <facebook-github-bot-4-bot@fb.com>
Tue, 16 Aug 2016 15:53:26 +0000 (08:53 -0700)
Summary:
This diff adds folly::partial, a function to partially apply
a set of zero or more arguments to a callable. It is similar
to Python's `functools.partial`.

`folly::partial` takes a callable object and additional
arguments and returns a callable with those additional
arguments bound to it. When the returned callable is invoked
with additional arguments, those are appended to the set of
arguments that were passed to `folly::partial`.

It is similar to `std::bind`, but more simple as it does not
support reordering of parameters, but also does not
require you to know how many arguments will be
eventually passed to the callable. Also, `std::bind`
does not support move-only types being passed by-value.
`folly::partial` does:

  void someFunc(std::unique_ptr<Foo>, int);
  auto p = folly::partial(&someFunc, std::move(foo_unique_ptr));
  ...
  std::move(p)(42);

Reviewed By: mhx

Differential Revision: D3252539

fbshipit-source-id: ee093771ac732fa70052b9908dcb75e90ba80efe

folly/Makefile.am
folly/Partial.h [new file with mode: 0644]
folly/test/Makefile.am
folly/test/PartialTest.cpp [new file with mode: 0644]

index 116ef90e0c3e85dadc38b305caae7807890ec5d7..16228fb279071b845e136a3471e9efad40ddbb9f 100644 (file)
@@ -253,6 +253,7 @@ nobase_follyinclude_HEADERS = \
        Optional.h \
        PackedSyncPtr.h \
        Padded.h \
        Optional.h \
        PackedSyncPtr.h \
        Padded.h \
+       Partial.h \
        PicoSpinLock.h \
        Portability.h \
        portability/Asm.h \
        PicoSpinLock.h \
        Portability.h \
        portability/Asm.h \
diff --git a/folly/Partial.h b/folly/Partial.h
new file mode 100644 (file)
index 0000000..f19941f
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 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 <folly/ApplyTuple.h>
+
+namespace folly {
+
+template <typename F, typename Tuple>
+class Partial {
+ private:
+  F f_;
+  Tuple stored_args_;
+
+ public:
+  template <typename Callable, typename... Args>
+  Partial(Callable&& callable, Args&&... args)
+      : f_(std::forward<Callable>(callable)),
+        stored_args_(std::forward<Args>(args)...) {}
+
+  template <typename... CArgs>
+  auto operator()(CArgs&&... cargs) & {
+    return applyTuple(
+        static_cast<F&>(f_),
+        static_cast<Tuple&>(stored_args_),
+        std::forward_as_tuple(std::forward<CArgs>(cargs)...));
+  }
+
+  template <typename... CArgs>
+  auto operator()(CArgs&&... cargs) const& {
+    return applyTuple(
+        static_cast<F const&>(f_),
+        static_cast<Tuple const&>(stored_args_),
+        std::forward_as_tuple(std::forward<CArgs>(cargs)...));
+  }
+
+  template <typename... CArgs>
+  auto operator()(CArgs&&... cargs) && {
+    return applyTuple(
+        static_cast<F&&>(f_),
+        static_cast<Tuple&&>(stored_args_),
+        std::forward_as_tuple(std::forward<CArgs>(cargs)...));
+  }
+};
+
+/**
+ * Partially applies arguments to a callable
+ *
+ * `partial` takes a callable and zero or more additional arguments and returns
+ * a callable object, which when called with zero or more arguments, will invoke
+ * the original callable with the additional arguments passed to `partial`,
+ * followed by those passed to the call.
+ *
+ * E.g. `partial(Foo, 1, 2)(3)` is equivalent to `Foo(1, 2, 3)`.
+ *
+ * `partial` can be used to bind a class method to an instance:
+ * `partial(&Foo::method, foo_pointer)` returns a callable object that can be
+ * invoked in the same way as `foo_pointer->method`. In case the first
+ * argument in a call to `partial` is a member pointer, the second argument
+ * can be a reference, pointer or any object that can be dereferenced to
+ * an object of type Foo (like `std::shared_ptr` or `std::unique_ptr`).
+ *
+ * `partial` is similar to `std::bind`, but you don't have to use placeholders
+ * to have arguments passed on. Any number of arguments passed to the object
+ * returned by `partial` when called will be added to those passed to `partial`
+ * and passed to the original callable.
+ */
+template <typename F, typename... Args>
+auto partial(F&& f, Args&&... args) -> Partial<
+    typename std::decay<F>::type,
+    std::tuple<typename std::decay<Args>::type...>> {
+  return {std::forward<F>(f), std::forward<Args>(args)...};
+}
+
+} // namespace folly
index 24e241f48b338b7c8d6cf5bb3c82c2d5a3b2dd56..d8c5acae3b315339f4695e2243854f8a6e99afba 100644 (file)
@@ -301,4 +301,8 @@ apply_tuple_test_SOURCES = ApplyTupleTest.cpp
 apply_tuple_test_LDADD = libfollytestmain.la
 TESTS += apply_tuple_test
 
 apply_tuple_test_LDADD = libfollytestmain.la
 TESTS += apply_tuple_test
 
+partial_test_SOURCES = PartialTest.cpp
+partial_test_LDADD = libfollytestmain.la
+TESTS += partial_test
+
 check_PROGRAMS += $(TESTS)
 check_PROGRAMS += $(TESTS)
diff --git a/folly/test/PartialTest.cpp b/folly/test/PartialTest.cpp
new file mode 100644 (file)
index 0000000..025cd25
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 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 <memory>
+
+#include <folly/Partial.h>
+#include <gtest/gtest.h>
+
+using folly::partial;
+
+int add3(int x, int y, int z) {
+  return 100 * x + 10 * y + z;
+}
+
+TEST(Partial, Simple) {
+  auto p0 = partial(&add3);
+  EXPECT_EQ(123, p0(1, 2, 3));
+
+  auto p1 = partial(&add3, 2);
+  EXPECT_EQ(234, p1(3, 4));
+
+  auto p2 = partial(&add3, 3, 4);
+  EXPECT_EQ(345, p2(5));
+
+  auto p3 = partial(&add3, 4, 5, 6);
+  EXPECT_EQ(456, p3());
+}
+
+struct Foo {
+  int method(int& x, int& y, int& z) {
+    return 1000 + 100 * x + 10 * y + z;
+  }
+  int constMethod(int const& x, int const& y, int const& z) const {
+    return 2000 + 100 * x + 10 * y + z;
+  }
+  int tempMethod(int&& x, int&& y, int&& z) {
+    return 3000 + 100 * x + 10 * y + z;
+  }
+};
+
+TEST(Partial, ReferenceArguments) {
+  auto p0 = partial(&Foo::method, Foo{}, 2, 3);
+  int four = 4;
+  EXPECT_EQ(1234, p0(four));
+
+  auto const p1 = partial(&Foo::constMethod, Foo{}, 3, 4);
+  EXPECT_EQ(2345, p1(5));
+
+  auto p2 = partial(&Foo::tempMethod, Foo{}, 4, 5);
+  EXPECT_EQ(3456, std::move(p2)(6));
+}
+
+struct RefQualifiers {
+  int operator()(int x, int y, int z) & {
+    return 1000 + 100 * x + 10 * y + z;
+  }
+  int operator()(int x, int y, int z) const& {
+    return 2000 + 100 * x + 10 * y + z;
+  }
+  int operator()(int x, int y, int z) && {
+    return 3000 + 100 * x + 10 * y + z;
+  }
+};
+
+TEST(Partial, RefQualifiers) {
+  auto p = partial(RefQualifiers{});
+  auto const& pconst = p;
+
+  EXPECT_EQ(1234, p(2, 3, 4));
+  EXPECT_EQ(2345, pconst(3, 4, 5));
+  EXPECT_EQ(3456, std::move(p)(4, 5, 6));
+}
+
+struct RefQualifiers2 {
+  int operator()(int& x, int const& y, int z) & {
+    return 1000 + 100 * x + 10 * y + z;
+  }
+  int operator()(int const& x, int y, int z) const& {
+    return 2000 + 100 * x + 10 * y + z;
+  }
+  int operator()(int&& x, int const& y, int z) && {
+    return 3000 + 100 * x + 10 * y + z;
+  }
+};
+
+TEST(Partial, RefQualifiers2) {
+  auto p = partial(RefQualifiers2{}, 9, 8);
+  auto const& pconst = p;
+
+  EXPECT_EQ(1984, p(4));
+  EXPECT_EQ(2985, pconst(5));
+  EXPECT_EQ(3986, std::move(p)(6));
+}
+
+std::unique_ptr<int> calc_uptr(std::unique_ptr<int> x, std::unique_ptr<int> y) {
+  *x = 100 * *x + 10 * *y;
+  return x;
+}
+
+TEST(Partial, MoveOnly) {
+  auto five = std::make_unique<int>(5);
+  auto six = std::make_unique<int>(6);
+
+  // create a partial object which holds a pointer to the `calc_uptr` function
+  // and a `unique_ptr<int>` for the first argument
+  auto p = partial(&calc_uptr, std::move(five));
+
+  // `five` should be moved out of
+  EXPECT_FALSE(five);
+
+  // call to the partial object as rvalue, which allows the call to consume
+  // captured data (here: the `unique_ptr<int>` storing 5), and pass it
+  // the other `unique_ptr`
+  auto result = std::move(p)(std::move(six));
+
+  // ...which now should be moved out of
+  EXPECT_FALSE(six);
+
+  EXPECT_EQ(560, *result);
+}