diff --git a/src/include/fl/utils/ap_optional.hpp b/src/include/fl/utils/ap_optional.hpp new file mode 100644 index 0000000..b9660e0 --- /dev/null +++ b/src/include/fl/utils/ap_optional.hpp @@ -0,0 +1,69 @@ +// +// MIT License +// +// Copyright (c) 2023-present Vitaly Fanaskov +// +// fl -- Writer "monad" for C++ +// Project home: https://github.com/vt4a2h/fl +// +// See LICENSE file for the further details. +// +#pragma once + +#include +#include +#include + +namespace fl { + +namespace details { + +template +constexpr bool is_optional = false; + +template +constexpr bool is_optional> = true; + +template +concept IsOptional = is_optional>; + +template +constexpr auto pure(V &&v) +{ + if constexpr (IsOptional) { + return std::forward(v); + } + else { + return std::make_optional(std::forward(v)); + } +} + +} // namespace details + +template +constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(std::invoke(std::forward(f), std::forward(args)...))) + requires (std::is_invocable_v) +{ + return std::make_optional(std::invoke(std::forward(f), std::forward(args)...)); +} + +template +constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(ap(std::forward(f), *std::forward(args)...))) + -> decltype(ap(std::forward(f), *std::forward(args)...)) + requires (!std::is_invocable_v) +{ + if ((... && args)) { + return ap(std::forward(f), *std::forward(args)...); + } else { + return {}; + } +} + +template +constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(ap(std::forward(f), *details::pure(args)...))) + requires (!std::is_invocable_v && !(... && details::IsOptional)) +{ + return ap(std::forward(f), *details::pure(args)...); +} + +} // namespace fl diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 163d444..c3cdc1f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources(tests PRIVATE writer_default_types.hpp writer_utility_types.hpp test_writer_move_copy.cpp + test_utils_ap_optional.cpp ) target_compile_features(tests PRIVATE cxx_std_23) diff --git a/test/test_utils_ap_optional.cpp b/test/test_utils_ap_optional.cpp new file mode 100644 index 0000000..1557c8d --- /dev/null +++ b/test/test_utils_ap_optional.cpp @@ -0,0 +1,48 @@ +// +// MIT License +// +// Copyright (c) 2023-present Vitaly Fanaskov +// +// fl -- Writer "monad" for C++ +// Project home: https://github.com/vt4a2h/fl +// +// See LICENSE file for the further details. +// +#include "catch.hpp" + +#include + +TEST_CASE("Applicative for optional") +{ + SECTION("Regular args and function") { + const auto add = [](int a, int b) { return a + b; }; + const int a = 1; + const int b = 2; + + REQUIRE(fl::ap(add, a, b) == 3); + } + + SECTION("Optional non-invocable") { + const auto add = [](int a, const std::string &b) { return a + std::stoi(b); }; + const auto a = std::make_optional(1); + const auto b = std::make_optional("2"); + + REQUIRE(fl::ap(add, a, b) == 3); + } + + SECTION("Optional invocable") { + const auto add = [](std::optional a, const std::optional &b) { return *a + std::stoi(*b); }; + const auto a = std::make_optional(1); + const auto b = std::make_optional("2"); + + REQUIRE(fl::ap(add, a, b) == 3); + } + + SECTION("Optional partial ap") { + const auto add = [](int a, const std::string &b) { return a + std::stoi(b); }; + const auto a = std::make_optional(1); + const auto b = std::string{"2"}; + + REQUIRE(fl::ap(add, a, b) == 3); + } +} \ No newline at end of file