Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/fplus/result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,25 @@ auto compose_result(Callables&&... callables)
return internal::compose_binary_lift(bind_result,
std::forward<Callables>(callables)...);
}

// API search type: compose_first_error_and_result : ((a -> Result b c), (a -> Result d e)) -> (a -> (Maybe c, Result d e))
// Run two result-returning functions on the same input independently.
// The first contributes only its potential error (as a Maybe), its Ok
// value is discarded. The second's result is forwarded unchanged.
// The second function is always called, regardless of whether the first
// one failed. Useful for ancillary checks (logging, metrics, validation,
// audit, cache lookup, ...) that may fail but should not block the main
// computation.
template <typename First, typename Second>
auto compose_first_error_and_result(First first, Second second)
{
return [first = std::move(first), second = std::move(second)](const auto& x) {
auto first_result = internal::invoke(first, x);
using FirstErr = typename std::decay_t<decltype(first_result)>::error_t;
auto first_err = is_error(first_result)
? just<FirstErr>(unsafe_get_error(first_result))
: nothing<FirstErr>();
return std::make_pair(std::move(first_err), internal::invoke(second, x));
};
}
} // namespace fplus
21 changes: 21 additions & 0 deletions include_all_in_one/include/fplus/fplus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5719,6 +5719,27 @@ auto compose_result(Callables&&... callables)
return internal::compose_binary_lift(bind_result,
std::forward<Callables>(callables)...);
}

// API search type: compose_first_error_and_result : ((a -> Result b c), (a -> Result d e)) -> (a -> (Maybe c, Result d e))
// Run two result-returning functions on the same input independently.
// The first contributes only its potential error (as a Maybe), its Ok
// value is discarded. The second's result is forwarded unchanged.
// The second function is always called, regardless of whether the first
// one failed. Useful for ancillary checks (logging, metrics, validation,
// audit, cache lookup, ...) that may fail but should not block the main
// computation.
template <typename First, typename Second>
auto compose_first_error_and_result(First first, Second second)
{
return [first = std::move(first), second = std::move(second)](const auto& x) {
auto first_result = internal::invoke(first, x);
using FirstErr = typename std::decay_t<decltype(first_result)>::error_t;
auto first_err = is_error(first_result)
? just<FirstErr>(unsafe_get_error(first_result))
: nothing<FirstErr>();
return std::make_pair(std::move(first_err), internal::invoke(second, x));
};
}
} // namespace fplus

#include <algorithm>
Expand Down
29 changes: 29 additions & 0 deletions test/result_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <doctest/doctest.h>
#include <fplus/fplus.hpp>
#include <variant>
#include <vector>

namespace {
Expand Down Expand Up @@ -116,6 +117,34 @@
REQUIRE_EQ(squareSumResult(5, 5), (ok<int, std::string>(100)));
}

TEST_CASE("result_test - compose_first_error_and_result")
Comment thread
Dobiasd marked this conversation as resolved.
Dismissed
{
using namespace fplus;

const auto check_ok = [](int) {
return ok<std::monostate, std::string>({});
};
const auto check_fail = [](int) {
return error<std::monostate, std::string>("check failed");
};
const auto run = [](int x) {
return x < 0
? error<int, std::string>("negative input")
: ok<int, std::string>(x * x);
};

using Pair = std::pair<maybe<std::string>, result<int, std::string>>;

REQUIRE_EQ(compose_first_error_and_result(check_ok, run)(3),
Pair(nothing<std::string>(), ok<int, std::string>(9)));
REQUIRE_EQ(compose_first_error_and_result(check_ok, run)(-1),
Pair(nothing<std::string>(), error<int, std::string>("negative input")));
REQUIRE_EQ(compose_first_error_and_result(check_fail, run)(3),
Pair(just<std::string>("check failed"), ok<int, std::string>(9)));
REQUIRE_EQ(compose_first_error_and_result(check_fail, run)(-1),
Pair(just<std::string>("check failed"), error<int, std::string>("negative input")));
}

TEST_CASE("result_test - lift")
{
using namespace fplus;
Expand Down
Loading