| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- //
- // either_stream.hpp
- // optional.stream
- //
- // Created by Sam Jaffe on 1/28/17.
- //
- #pragma once
- #include <type_traits>
- #include <variant>
- template <typename T, typename E> using either = std::variant<T, E>;
- namespace stream {
- template <typename, typename> class either_stream;
- }
- namespace stream::traits {
- template <typename Either> struct either_traits {
- static constexpr bool const is_either = false;
- };
- template <typename T, typename E> struct either_traits<::either<T, E>> {
- static constexpr bool const is_either = true;
- using stream_type = either_stream<T, E>;
- using value_type = T;
- using error_type = E;
- };
- template <typename Either>
- using either_stream_t = typename either_traits<Either>::stream_type;
- template <typename Either>
- using either_value_t = typename either_traits<Either>::value_type;
- template <typename Either>
- inline constexpr bool is_either_v = either_traits<Either>::is_either;
- }
- namespace stream {
- /**
- * An either stream is an object that allows functional compositions on a type
- * that is either a concrete value or an error-type. This can be used as an
- * alternative to the "status and out-param" pattern or exception-throwing
- * code. It combines the benefits of both types - gaining the immediate
- * knowledge of error-information that is provided by the first, while also
- * allowing you to avoid out-params.
- * The weakness of this design is that it often requires remodeling your
- * entire codebase to operate around the either_stream. In order to terminate
- * the stream, a fail-fast option is provided with operator bool(). However,
- * the primary intent is to chain together functions with map and flatmap
- * until you either have the desired result, or an error. At that point, you
- * invoke match and handle the error with a default return value, an
- * error-handler callback, or a thrown exception.
- */
- template <typename T, typename E> class either_stream {
- private:
- template <typename F, typename T2 = T>
- using result_of = decltype(std::declval<F>()(std::declval<T2>()));
- private:
- ::either<T, E> value_;
- public:
- either_stream(E const & v) : value_(v) {}
- either_stream(E && v) : value_(std::forward<E>(v)) {}
- either_stream(T const & v) : value_(v) {}
- either_stream(T && v) : value_(std::forward<T>(v)) {}
- either_stream(::either<T, E> const & v) : value_(v) {}
- either_stream(::either<T, E> && v)
- : value_(std::forward<::either<T, E>>(v)) {}
- // This probably should be for internal use only
- operator bool() const { return value_.index() == 0; }
- template <typename F>
- auto map(F && fun) const -> either_stream<result_of<F>, E> {
- static_assert(!traits::is_either_v<result_of<F>>, "Cannot nest eithers");
- if (*this) {
- return fun(std::get<0>(value_));
- } else {
- return std::get<1>(value_);
- }
- }
- template <typename F>
- auto flatmap(F && fun) const
- -> either_stream<traits::either_value_t<result_of<F>>, E> {
- if (*this) {
- return fun(std::get<0>(value_));
- } else {
- return std::get<1>(value_);
- }
- }
- template <typename FT, typename FE>
- auto match(FT && left, FE && right)
- -> std::common_type_t<result_of<FT, T>, result_of<FE, E>> {
- if (*this) {
- return left(std::get<0>(value_));
- } else {
- return right(std::get<1>(value_));
- }
- }
- };
- }
- namespace stream::either {
- /**
- * Construct a stream from the given type while explicitly providing both the
- * type and the error class
- */
- template <typename T, typename E>
- either_stream<T, E> make_stream(T const & opt) {
- return {{opt}};
- }
- /**
- * Construct a stream from the given type where the real type is an inferred
- * property
- */
- template <typename E, typename T>
- either_stream<T, E> make_stream(T const & opt) {
- return {{opt}};
- }
- /**
- * Construct a stream with the given either-type as a template parameter.
- */
- template <typename E>
- auto make_stream(traits::either_value_t<E> const & opt) {
- return traits::either_stream_t<E>{opt};
- }
- }
|