either_stream.hpp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. //
  2. // either_stream.hpp
  3. // optional.stream
  4. //
  5. // Created by Sam Jaffe on 1/28/17.
  6. //
  7. #pragma once
  8. #include <type_traits>
  9. #include <variant>
  10. template <typename T, typename E> using either = std::variant<T, E>;
  11. namespace stream {
  12. template <typename, typename> class either_stream;
  13. }
  14. namespace stream::traits {
  15. template <typename Either> struct either_traits {
  16. static constexpr bool const is_either = false;
  17. };
  18. template <typename T, typename E> struct either_traits<::either<T, E>> {
  19. static constexpr bool const is_either = true;
  20. using stream_type = either_stream<T, E>;
  21. using value_type = T;
  22. using error_type = E;
  23. };
  24. template <typename Either>
  25. using either_stream_t = typename either_traits<Either>::stream_type;
  26. template <typename Either>
  27. using either_value_t = typename either_traits<Either>::value_type;
  28. template <typename Either>
  29. inline constexpr bool is_either_v = either_traits<Either>::is_either;
  30. }
  31. namespace stream {
  32. /**
  33. * An either stream is an object that allows functional compositions on a type
  34. * that is either a concrete value or an error-type. This can be used as an
  35. * alternative to the "status and out-param" pattern or exception-throwing
  36. * code. It combines the benefits of both types - gaining the immediate
  37. * knowledge of error-information that is provided by the first, while also
  38. * allowing you to avoid out-params.
  39. * The weakness of this design is that it often requires remodeling your
  40. * entire codebase to operate around the either_stream. In order to terminate
  41. * the stream, a fail-fast option is provided with operator bool(). However,
  42. * the primary intent is to chain together functions with map and flatmap
  43. * until you either have the desired result, or an error. At that point, you
  44. * invoke match and handle the error with a default return value, an
  45. * error-handler callback, or a thrown exception.
  46. */
  47. template <typename T, typename E> class either_stream {
  48. private:
  49. template <typename F, typename T2 = T>
  50. using result_of = decltype(std::declval<F>()(std::declval<T2>()));
  51. private:
  52. ::either<T, E> value_;
  53. public:
  54. either_stream(E const & v) : value_(v) {}
  55. either_stream(E && v) : value_(std::forward<E>(v)) {}
  56. either_stream(T const & v) : value_(v) {}
  57. either_stream(T && v) : value_(std::forward<T>(v)) {}
  58. either_stream(::either<T, E> const & v) : value_(v) {}
  59. either_stream(::either<T, E> && v)
  60. : value_(std::forward<::either<T, E>>(v)) {}
  61. // This probably should be for internal use only
  62. operator bool() const { return value_.index() == 0; }
  63. template <typename F>
  64. auto map(F && fun) const -> either_stream<result_of<F>, E> {
  65. static_assert(!traits::is_either_v<result_of<F>>, "Cannot nest eithers");
  66. if (*this) {
  67. return fun(std::get<0>(value_));
  68. } else {
  69. return std::get<1>(value_);
  70. }
  71. }
  72. template <typename F>
  73. auto flatmap(F && fun) const
  74. -> either_stream<traits::either_value_t<result_of<F>>, E> {
  75. if (*this) {
  76. return fun(std::get<0>(value_));
  77. } else {
  78. return std::get<1>(value_);
  79. }
  80. }
  81. template <typename FT, typename FE>
  82. auto match(FT && left, FE && right)
  83. -> std::common_type_t<result_of<FT, T>, result_of<FE, E>> {
  84. if (*this) {
  85. return left(std::get<0>(value_));
  86. } else {
  87. return right(std::get<1>(value_));
  88. }
  89. }
  90. };
  91. }
  92. namespace stream::either {
  93. /**
  94. * Construct a stream from the given type while explicitly providing both the
  95. * type and the error class
  96. */
  97. template <typename T, typename E>
  98. either_stream<T, E> make_stream(T const & opt) {
  99. return {{opt}};
  100. }
  101. /**
  102. * Construct a stream from the given type where the real type is an inferred
  103. * property
  104. */
  105. template <typename E, typename T>
  106. either_stream<T, E> make_stream(T const & opt) {
  107. return {{opt}};
  108. }
  109. /**
  110. * Construct a stream with the given either-type as a template parameter.
  111. */
  112. template <typename E>
  113. auto make_stream(traits::either_value_t<E> const & opt) {
  114. return traits::either_stream_t<E>{opt};
  115. }
  116. }