From 0c2c50675c48b142e63a4e358c1948a45ee56ac0 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 26 Aug 2023 21:18:42 +0100 Subject: [PATCH 1/6] Work in progress curry helper --- .../morpheus/core/functional/CMakeLists.txt | 1 + .../src/morpheus/core/functional/curry.hpp | 24 +++++++++++++++++ .../core/tests/functional/CMakeLists.txt | 1 + .../core/tests/functional/curry.tests.cpp | 26 +++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 libraries/core/src/morpheus/core/functional/curry.hpp create mode 100644 libraries/core/tests/functional/curry.tests.cpp diff --git a/libraries/core/src/morpheus/core/functional/CMakeLists.txt b/libraries/core/src/morpheus/core/functional/CMakeLists.txt index 60a3a78e0..4d91e8635 100644 --- a/libraries/core/src/morpheus/core/functional/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/functional/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(MorpheusCore PUBLIC + curry.hpp function_ref.hpp overload.hpp ) diff --git a/libraries/core/src/morpheus/core/functional/curry.hpp b/libraries/core/src/morpheus/core/functional/curry.hpp new file mode 100644 index 000000000..c7dfea5e1 --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/curry.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace morpheus::functional +{ + +/// \fun curry +/// +/// +template + requires std::invokable(F, Args...) +constexpr auto curry(F&& f, Args&&... args) -> decltype(auto) +{ + if constexpr () + else + { + return [f = std::forward(f), args = std::forward(args)](this Self&& self, As&&... as) -> decltype(auto) + { return curry(std::forward_like(f), std::forward_like < Self(args)..., std::forward(as)...); } + } +} + +} // namespace morpheus::functional \ No newline at end of file diff --git a/libraries/core/tests/functional/CMakeLists.txt b/libraries/core/tests/functional/CMakeLists.txt index 9ffdd0e57..a40a69ee6 100644 --- a/libraries/core/tests/functional/CMakeLists.txt +++ b/libraries/core/tests/functional/CMakeLists.txt @@ -1,4 +1,5 @@ target_sources(MorpheusCoreTests PRIVATE + curry.tests.cpp function_ref.tests.cpp ) diff --git a/libraries/core/tests/functional/curry.tests.cpp b/libraries/core/tests/functional/curry.tests.cpp new file mode 100644 index 000000000..a4b1ffdb6 --- /dev/null +++ b/libraries/core/tests/functional/curry.tests.cpp @@ -0,0 +1,26 @@ +#include "morpheus/core/functional/curry.hpp" + +#include + +namespace morpheus::functional +{ + +TEST_CASE("Verify construction of function_ref", "[morpheus.functional.function_ref]") +{ + GIVEN("A free standing function") + { + WHEN("Constructing a function reference to the function") + { + THEN("Expect the function to be invocable by the function ref") {} + } + } + GIVEN("A class member function and class instance") + { + WHEN("Constructing a function reference to the function") + { + THEN("Expect the function to be invocable by the function ref") {} + } + } +} + +} // namespace morpheus::functional \ No newline at end of file From b8af41680fc01ffbd5f8ecac2a04f310563be1b5 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Tue, 2 Apr 2024 20:42:41 +0100 Subject: [PATCH 2/6] Complete curry and add simple tests --- .../src/morpheus/core/functional/curry.hpp | 21 ++++---- .../core/tests/functional/curry.tests.cpp | 49 +++++++++++++++---- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/libraries/core/src/morpheus/core/functional/curry.hpp b/libraries/core/src/morpheus/core/functional/curry.hpp index c7dfea5e1..80252cd2f 100644 --- a/libraries/core/src/morpheus/core/functional/curry.hpp +++ b/libraries/core/src/morpheus/core/functional/curry.hpp @@ -6,19 +6,22 @@ namespace morpheus::functional { -/// \fun curry -/// +/// \fn curry /// +/// \note +/// [Applicative: The Forgotten Functional Pattern in C++ - Ben Deane - CppNow 2023](https://youtu.be/At-b4PHNxMg?si=hDI3zgmfPwrrIxoe&t=1313) template - requires std::invokable(F, Args...) constexpr auto curry(F&& f, Args&&... args) -> decltype(auto) { - if constexpr () - else + if constexpr (std::invocable) + return std::invoke(std::forward(f), std::forward(args)...); + else + { + return [f = std::forward(f), ...args = std::forward(args)](this Self&& self, As&&... as) -> decltype(auto) { - return [f = std::forward(f), args = std::forward(args)](this Self&& self, As&&... as) -> decltype(auto) - { return curry(std::forward_like(f), std::forward_like < Self(args)..., std::forward(as)...); } - } + return curry(std::forward_like(f), std::forward_like(args)..., std::forward(as)...); + }; + } } -} // namespace morpheus::functional \ No newline at end of file +} // namespace morpheus::functional diff --git a/libraries/core/tests/functional/curry.tests.cpp b/libraries/core/tests/functional/curry.tests.cpp index a4b1ffdb6..8c4c298aa 100644 --- a/libraries/core/tests/functional/curry.tests.cpp +++ b/libraries/core/tests/functional/curry.tests.cpp @@ -5,22 +5,51 @@ namespace morpheus::functional { -TEST_CASE("Verify construction of function_ref", "[morpheus.functional.function_ref]") +TEST_CASE("Verify partial application via currying", "[morpheus.functional.curry]") { - GIVEN("A free standing function") + SECTION("Zero parameters") { - WHEN("Constructing a function reference to the function") + auto constexpr shouldCall = []() { - THEN("Expect the function to be invocable by the function ref") {} - } + SUCCEED(); + }; + curry(shouldCall); } - GIVEN("A class member function and class instance") + SECTION("One parameters") { - WHEN("Constructing a function reference to the function") + auto constexpr expectedParam1 = 1; + auto constexpr identity = [](auto a) { return a; }; + curry(identity, expectedParam1); + REQUIRE(expectedParam1 == identity(expectedParam1)); + } + SECTION("Two parameters") + { + auto constexpr expectedParam1 = 1; + auto constexpr expectedParam2 = 2; + auto constexpr add = [](auto a, auto b) + { + REQUIRE(a == expectedParam1); + REQUIRE(b == expectedParam2); + return a + b; + }; + auto constexpr add1 = curry(add, expectedParam1); + REQUIRE(add1(expectedParam2) == (expectedParam1 + expectedParam2)); + } + SECTION("Three parameters") + { + auto constexpr expectedParam1 = 1; + auto constexpr expectedParam2 = 2; + auto constexpr expectedParam3 = 3; + auto constexpr add = [](auto a, auto b, auto c) { - THEN("Expect the function to be invocable by the function ref") {} - } + REQUIRE(a == expectedParam1); + REQUIRE(b == expectedParam2); + REQUIRE(c == expectedParam3); + return a + b + c; + }; + auto constexpr add1 = curry(add, expectedParam1); + REQUIRE(add1(expectedParam2, expectedParam3) == (expectedParam1 + expectedParam2, expectedParam3)); } } -} // namespace morpheus::functional \ No newline at end of file +} // namespace morpheus::functional From b309db37f43f22909c6564a7080b3709a032c0f2 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 7 Apr 2024 09:07:02 +0100 Subject: [PATCH 3/6] Fix curry test --- libraries/core/tests/functional/curry.tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/core/tests/functional/curry.tests.cpp b/libraries/core/tests/functional/curry.tests.cpp index 8c4c298aa..bff21e1a9 100644 --- a/libraries/core/tests/functional/curry.tests.cpp +++ b/libraries/core/tests/functional/curry.tests.cpp @@ -48,7 +48,7 @@ TEST_CASE("Verify partial application via currying", "[morpheus.functional.curry return a + b + c; }; auto constexpr add1 = curry(add, expectedParam1); - REQUIRE(add1(expectedParam2, expectedParam3) == (expectedParam1 + expectedParam2, expectedParam3)); + REQUIRE(add1(expectedParam2, expectedParam3) == (expectedParam1 + expectedParam2 + expectedParam3)); } } From f016884feb8bf7c12d033571ad274e8a630d87d0 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 7 Apr 2024 09:15:58 +0100 Subject: [PATCH 4/6] Add missing include --- libraries/core/src/morpheus/core/functional/curry.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/core/src/morpheus/core/functional/curry.hpp b/libraries/core/src/morpheus/core/functional/curry.hpp index 80252cd2f..586e7cdb7 100644 --- a/libraries/core/src/morpheus/core/functional/curry.hpp +++ b/libraries/core/src/morpheus/core/functional/curry.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace morpheus::functional From 2202a2e9252172f7bb4d3ce5f1dd5d5c9dc57dfe Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 7 Apr 2024 09:25:42 +0100 Subject: [PATCH 5/6] Require deducing this support --- libraries/core/src/morpheus/core/functional/curry.hpp | 7 ++++++- libraries/core/tests/functional/curry.tests.cpp | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/core/src/morpheus/core/functional/curry.hpp b/libraries/core/src/morpheus/core/functional/curry.hpp index 586e7cdb7..ad5f32d1a 100644 --- a/libraries/core/src/morpheus/core/functional/curry.hpp +++ b/libraries/core/src/morpheus/core/functional/curry.hpp @@ -7,8 +7,11 @@ namespace morpheus::functional { +#if (__cpp_explicit_this_parameter >= 202110L) + /// \fn curry -/// +/// Currying is a technique in which we consider a function takign multuiple arguments and turn it into a function which takes a single argument and +/// returns a funtion to handle the remaining arguments. /// \note /// [Applicative: The Forgotten Functional Pattern in C++ - Ben Deane - CppNow 2023](https://youtu.be/At-b4PHNxMg?si=hDI3zgmfPwrrIxoe&t=1313) template @@ -25,4 +28,6 @@ constexpr auto curry(F&& f, Args&&... args) -> decltype(auto) } } +#endif // (__cpp_explicit_this_parameter >= 202110L) + } // namespace morpheus::functional diff --git a/libraries/core/tests/functional/curry.tests.cpp b/libraries/core/tests/functional/curry.tests.cpp index bff21e1a9..31c30448f 100644 --- a/libraries/core/tests/functional/curry.tests.cpp +++ b/libraries/core/tests/functional/curry.tests.cpp @@ -5,6 +5,8 @@ namespace morpheus::functional { +#if (__cpp_explicit_this_parameter >= 202110L) + TEST_CASE("Verify partial application via currying", "[morpheus.functional.curry]") { SECTION("Zero parameters") @@ -52,4 +54,6 @@ TEST_CASE("Verify partial application via currying", "[morpheus.functional.curry } } +#endif // (__cpp_explicit_this_parameter >= 202110L) + } // namespace morpheus::functional From 0afa84e67f3cc334d5a6e8cda4618017a14b331b Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Mon, 1 Sep 2025 12:32:20 +0100 Subject: [PATCH 6/6] Run pre-commit --- .../lib/morpheus/core/functional/curry.hpp | 6 ++--- .../core/tests/functional/curry.tests.cpp | 27 +++++++++---------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/libraries/core/lib/morpheus/core/functional/curry.hpp b/libraries/core/lib/morpheus/core/functional/curry.hpp index ad5f32d1a..a3be90c31 100644 --- a/libraries/core/lib/morpheus/core/functional/curry.hpp +++ b/libraries/core/lib/morpheus/core/functional/curry.hpp @@ -21,10 +21,8 @@ constexpr auto curry(F&& f, Args&&... args) -> decltype(auto) return std::invoke(std::forward(f), std::forward(args)...); else { - return [f = std::forward(f), ...args = std::forward(args)](this Self&& self, As&&... as) -> decltype(auto) - { - return curry(std::forward_like(f), std::forward_like(args)..., std::forward(as)...); - }; + return [f = std::forward(f), ... args = std::forward(args)](this Self&& self, As&&... as) -> decltype(auto) + { return curry(std::forward_like(f), std::forward_like(args)..., std::forward(as)...); }; } } diff --git a/libraries/core/tests/functional/curry.tests.cpp b/libraries/core/tests/functional/curry.tests.cpp index 31c30448f..9b9215ad9 100644 --- a/libraries/core/tests/functional/curry.tests.cpp +++ b/libraries/core/tests/functional/curry.tests.cpp @@ -11,45 +11,42 @@ TEST_CASE("Verify partial application via currying", "[morpheus.functional.curry { SECTION("Zero parameters") { - auto constexpr shouldCall = []() - { - SUCCEED(); - }; + constexpr auto shouldCall = []() { SUCCEED(); }; curry(shouldCall); } SECTION("One parameters") { - auto constexpr expectedParam1 = 1; - auto constexpr identity = [](auto a) { return a; }; + constexpr auto expectedParam1 = 1; + constexpr auto identity = [](auto a) { return a; }; curry(identity, expectedParam1); REQUIRE(expectedParam1 == identity(expectedParam1)); } SECTION("Two parameters") { - auto constexpr expectedParam1 = 1; - auto constexpr expectedParam2 = 2; - auto constexpr add = [](auto a, auto b) + constexpr auto expectedParam1 = 1; + constexpr auto expectedParam2 = 2; + constexpr auto add = [](auto a, auto b) { REQUIRE(a == expectedParam1); REQUIRE(b == expectedParam2); return a + b; }; - auto constexpr add1 = curry(add, expectedParam1); + constexpr auto add1 = curry(add, expectedParam1); REQUIRE(add1(expectedParam2) == (expectedParam1 + expectedParam2)); } SECTION("Three parameters") { - auto constexpr expectedParam1 = 1; - auto constexpr expectedParam2 = 2; - auto constexpr expectedParam3 = 3; - auto constexpr add = [](auto a, auto b, auto c) + constexpr auto expectedParam1 = 1; + constexpr auto expectedParam2 = 2; + constexpr auto expectedParam3 = 3; + constexpr auto add = [](auto a, auto b, auto c) { REQUIRE(a == expectedParam1); REQUIRE(b == expectedParam2); REQUIRE(c == expectedParam3); return a + b + c; }; - auto constexpr add1 = curry(add, expectedParam1); + constexpr auto add1 = curry(add, expectedParam1); REQUIRE(add1(expectedParam2, expectedParam3) == (expectedParam1 + expectedParam2 + expectedParam3)); } }