Skip to content

Commit

Permalink
Some experiments with applicative for std::optional
Browse files Browse the repository at this point in the history
  • Loading branch information
vt4a2h committed Dec 30, 2023
1 parent ef91d9e commit d4b2eac
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
69 changes: 69 additions & 0 deletions src/include/fl/utils/ap_optional.hpp
Original file line number Diff line number Diff line change
@@ -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 <optional>
#include <type_traits>
#include <concepts>

namespace fl {

namespace details {

template<typename>
constexpr bool is_optional = false;

template<typename T>
constexpr bool is_optional<std::optional<T>> = true;

template<class T>
concept IsOptional = is_optional<std::remove_cvref_t<T>>;

template<class V>
constexpr auto pure(V &&v)
{
if constexpr (IsOptional<V>) {
return std::forward<V>(v);
}
else {
return std::make_optional(std::forward<V>(v));
}
}

} // namespace details

template <class F, class ...Args>
constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(std::invoke(std::forward<F>(f), std::forward<Args>(args)...)))
requires (std::is_invocable_v<F, Args...>)
{
return std::make_optional(std::invoke(std::forward<F>(f), std::forward<Args>(args)...));
}

template <class F, details::IsOptional ...Args>
constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(ap(std::forward<F>(f), *std::forward<Args>(args)...)))
-> decltype(ap(std::forward<F>(f), *std::forward<Args>(args)...))
requires (!std::is_invocable_v<F, Args...>)
{
if ((... && args)) {
return ap(std::forward<F>(f), *std::forward<Args>(args)...);
} else {
return {};
}
}

template <class F, class ...Args>
constexpr auto ap(F &&f, Args &&...args) noexcept(noexcept(ap(std::forward<F>(f), *details::pure(args)...)))
requires (!std::is_invocable_v<F, Args...> && !(... && details::IsOptional<Args>))
{
return ap(std::forward<F>(f), *details::pure(args)...);
}

} // namespace fl
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions test/test_utils_ap_optional.cpp
Original file line number Diff line number Diff line change
@@ -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 <fl/utils/ap_optional.hpp>

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<int>(1);
const auto b = std::make_optional<std::string>("2");

REQUIRE(fl::ap(add, a, b) == 3);
}

SECTION("Optional invocable") {
const auto add = [](std::optional<int> a, const std::optional<std::string> &b) { return *a + std::stoi(*b); };
const auto a = std::make_optional<int>(1);
const auto b = std::make_optional<std::string>("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<int>(1);
const auto b = std::string{"2"};

REQUIRE(fl::ap(add, a, b) == 3);
}
}

0 comments on commit d4b2eac

Please sign in to comment.