// // either_stream.hpp // optional.stream // // Created by Sam Jaffe on 1/28/17. // #pragma once #include #include template using either = std::variant; namespace stream { template class either_stream; } namespace stream::traits { template struct either_traits { static constexpr bool const is_either = false; }; template struct either_traits<::either> { static constexpr bool const is_either = true; using stream_type = either_stream; using value_type = T; using error_type = E; }; template using either_stream_t = typename either_traits::stream_type; template using either_value_t = typename either_traits::value_type; template inline constexpr bool is_either_v = either_traits::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 class either_stream { private: template using result_of = decltype(std::declval()(std::declval())); private: ::either value_; public: either_stream(E const & v) : value_(v) {} either_stream(E && v) : value_(std::forward(v)) {} either_stream(T const & v) : value_(v) {} either_stream(T && v) : value_(std::forward(v)) {} either_stream(::either const & v) : value_(v) {} either_stream(::either && v) : value_(std::forward<::either>(v)) {} // This probably should be for internal use only operator bool() const { return value_.index() == 0; } template auto map(F && fun) const -> either_stream, E> { static_assert(!traits::is_either_v>, "Cannot nest eithers"); if (*this) { return fun(std::get<0>(value_)); } else { return std::get<1>(value_); } } template auto flatmap(F && fun) const -> either_stream>, E> { if (*this) { return fun(std::get<0>(value_)); } else { return std::get<1>(value_); } } template auto match(FT && left, FE && right) -> std::common_type_t, result_of> { 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 either_stream make_stream(T const & opt) { return {{opt}}; } /** * Construct a stream from the given type where the real type is an inferred * property */ template either_stream make_stream(T const & opt) { return {{opt}}; } /** * Construct a stream with the given either-type as a template parameter. */ template auto make_stream(traits::either_value_t const & opt) { return traits::either_stream_t{opt}; } }