-
Notifications
You must be signed in to change notification settings - Fork 20
/
monad.hpp
207 lines (190 loc) · 5.43 KB
/
monad.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#pragma once
#include <tl_expected/expected.hpp>
#include <optional>
namespace rsl {
/** @file */
/**
* @brief Monad optional bind
*
* @param opt Input optional
* @param fn Function, must return a optional
*
* @tparam T Input type
* @tparam Fn Function
*
* @return Return type of fn
*/
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};
}
/**
* @brief Monad tl::expected<T,E>
*
* @param exp tl::expected<T,E> input
* @param fn Function to apply
*
* @tparam T Type for the input expected
* @tparam E Error type
* @tparam Fn Function
*
* @return Return type of the function
*/
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());
}
/**
* @brief Monadic try, used to lift a function that throws an exception into one that returns an
* tl::expected<T, std::exception_ptr>
*
* @param fn Function to call
*
* @tparam Fn Function type
*
* @return Return value of the function
*/
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());
}
/**
* @brief Monadic compose two monad functions
*
* @param fn First function
* @param g Second function
*
* @tparam Fn Type of the first function
* @tparam G Type of the second function
*
* @return A functional composition of two monad functions
*/
template <typename Fn, typename G>
[[nodiscard]] constexpr auto mcompose(Fn fn, G g) {
return [=](auto value) { return mbind(fn(value), g); };
}
/**
* @brief Variadic mcompose
*
* @param t First function
* @param g Second function
* @param vars Rest of the functions
*
* @tparam T Type of the first function
* @tparam G Type of the second function
* @tparam Ts Types of the rest of the functions
*
* @return A functional composition of variadic monad functions
*/
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...);
}
/**
* @brief Test if expected type is Error
*
* @param exp Input tl::expected<T,E> value
*
* @return True if expected paraemter is Error
*/
template <typename T, typename E>
[[nodiscard]] constexpr auto has_error(tl::expected<T, E> const& exp) {
return !exp.has_value();
}
/**
* @brief Test if expected type is Value
*
* @param exp Input tl::expected<T,E> value
*
* @return True if expected paraemter is Value
*/
template <typename T, typename E>
[[nodiscard]] constexpr auto has_value(tl::expected<T, E> const& exp) {
return exp.has_value();
}
/**
* @brief Tests if any of the expected args passed in has an error.
*
* @param args tl::expected<T, E> parameter pack
*
* @tparam E The error type
* @tparam Args The value types for the tl::expected<T, E> args
*
* @return The first error found or nothing
*/
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
/**
* @brief Overload of the | operator as bind
*
* @param opt Input optional
* @param fn Function
*
* @tparam T Input type
* @tparam Fn Function
*
* @return Return type of f
*/
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));
}
/**
* @brief Overload of the | operator as bind
*
* @param exp Input tl::expected<T,E> value
* @param fn Function to apply
*
* @tparam T Type for the input expected
* @tparam E Error type
* @tparam Fn Function
*
* @return Return type of 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);
}
/**
* @brief Overload of the | operator for unary functions
*
* @param val Input value
* @param fn Function to apply on val
*
* @tparam T Type for the input
* @tparam Fn Function
*
* @return Return the result of invoking the function on val
*/
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));
}