Program Listing for File monad.hpp

Return to documentation for file (include/rsl/monad.hpp)

#pragma once

#include <tl_expected/expected.hpp>

#include <optional>

namespace rsl {

template <typename T, typename Fn>
[[nodiscard]] constexpr auto mbind(std::optional<T> const& opt,
                                   Fn fn) -> std::invoke_result_t<Fn, T> {
    static_assert(std::is_convertible_v<std::nullopt_t, std::invoke_result_t<Fn, T>>,
                  "Fn must return a std::optional");
    if (opt) return fn(opt.value());
    return std::invoke_result_t<Fn, T>{std::nullopt};
}

template <typename T, typename E, typename Fn>
[[nodiscard]] constexpr auto mbind(tl::expected<T, E> const& exp,
                                   Fn fn) -> std::invoke_result_t<Fn, T> {
    if (exp) return fn(exp.value());
    return tl::unexpected(exp.error());
}

template <typename Fn>
[[nodiscard]] auto mtry(Fn fn) -> tl::expected<std::invoke_result_t<Fn>, std::exception_ptr> try {
    return fn();
} catch (...) {
    return tl::unexpected(std::current_exception());
}

template <typename Fn, typename G>
[[nodiscard]] constexpr auto mcompose(Fn fn, G g) {
    return [=](auto value) { return mbind(fn(value), g); };
}

template <typename T, typename G, typename... Ts>
[[nodiscard]] constexpr auto mcompose(T t, G g, Ts... vars) {
    auto exp = mcompose(t, g);
    return mcompose(exp, vars...);
}

template <typename T, typename E>
[[nodiscard]] constexpr auto has_error(tl::expected<T, E> const& exp) {
    return !exp.has_value();
}

template <typename T, typename E>
[[nodiscard]] constexpr auto has_value(tl::expected<T, E> const& exp) {
    return exp.has_value();
}

template <typename E, typename... Args>
[[nodiscard]] constexpr auto maybe_error(tl::expected<Args, E>... args) {
    auto maybe = std::optional<E>();
    (
        [&](auto& exp) {
            if (maybe.has_value()) return;
            if (has_error(exp)) maybe = exp.error();
        }(args),
        ...);
    return maybe;
}

template <typename>
constexpr inline bool is_optional_impl = false;
template <typename T>
constexpr inline bool is_optional_impl<std::optional<T>> = true;
template <typename T>
constexpr inline bool is_optional = is_optional_impl<std::remove_cv_t<std::remove_reference_t<T>>>;

}  // namespace rsl

template <typename T, typename Fn, typename = std::enable_if_t<rsl::is_optional<T>>,
          typename = std::enable_if_t<std::is_invocable_v<
              Fn, typename std::remove_cv_t<std::remove_reference_t<T>>::value_type>>>
[[nodiscard]] constexpr auto operator|(T&& opt, Fn&& fn) {
    return rsl::mbind(std::forward<T>(opt), std::forward<Fn>(fn));
}

template <typename T, typename E, typename Fn>
[[nodiscard]] constexpr auto operator|(tl::expected<T, E> const& exp, Fn fn) {
    return rsl::mbind(exp, fn);
}

template <typename T, typename Fn, typename = std::enable_if_t<!rsl::is_optional<T>>>
[[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) ->
    typename std::enable_if_t<std::is_invocable_v<Fn, T>, std::invoke_result_t<Fn, T>> {
    return std::invoke(std::forward<Fn>(fn), std::forward<T>(val));
}