Browse Source

Either Stream - represents a series of computations where we might return a user-defined error type instead on failure.
By convention, the left-hand side of the either<T, E> is considered the 'good' value, while the right-hand side is the 'exception' value.
Three functions are provided, 'map :: T -> R', 'flatmap :: T -> either<R, E>', and 'match :: ((T -> V), (E -> V)) -> V' (where V may be void).
Similar to optional_stream, if map and flatmap are called when the value is an 'E' exception, it will not perform any computation, and will simply forward the exception. Flatmap is able to return a new error, in the case it is handed a valid 'T' that failed in processing for some reason.
Match is the only method to then recover the value from the stream, which allows you to perform a final handler computation on either type, unifying them to return.

Samuel Jaffe 8 years ago
commit
d97bd4775a
3 changed files with 464 additions and 0 deletions
  1. 279 0
      either.stream.xcodeproj/project.pbxproj
  2. 88 0
      either_stream.hpp
  3. 97 0
      either_stream.t.h

+ 279 - 0
either.stream.xcodeproj/project.pbxproj

@@ -0,0 +1,279 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		CD407BE31E42D45A00BBA0D5 /* either_stream_tc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD407BE11E42D45A00BBA0D5 /* either_stream_tc.cpp */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		CD407BD01E42D42B00BBA0D5 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		CD407BD21E42D42B00BBA0D5 /* either_stream_tc */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = either_stream_tc; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD407BDD1E42D45400BBA0D5 /* either_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = either_stream.hpp; sourceTree = "<group>"; };
+		CD407BE01E42D45A00BBA0D5 /* either_stream.t.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = either_stream.t.h; sourceTree = "<group>"; };
+		CD407BE11E42D45A00BBA0D5 /* either_stream_tc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = either_stream_tc.cpp; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		CD407BCF1E42D42B00BBA0D5 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		CD407BC91E42D42B00BBA0D5 = {
+			isa = PBXGroup;
+			children = (
+				CD407BDE1E42D45400BBA0D5 /* src */,
+				CD407BE21E42D45A00BBA0D5 /* test */,
+				CD407BD31E42D42B00BBA0D5 /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		CD407BD31E42D42B00BBA0D5 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD407BD21E42D42B00BBA0D5 /* either_stream_tc */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD407BDE1E42D45400BBA0D5 /* src */ = {
+			isa = PBXGroup;
+			children = (
+				CD407BDD1E42D45400BBA0D5 /* either_stream.hpp */,
+			);
+			name = src;
+			sourceTree = "<group>";
+		};
+		CD407BE21E42D45A00BBA0D5 /* test */ = {
+			isa = PBXGroup;
+			children = (
+				CD407BE01E42D45A00BBA0D5 /* either_stream.t.h */,
+				CD407BE11E42D45A00BBA0D5 /* either_stream_tc.cpp */,
+			);
+			name = test;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		CD407BD11E42D42B00BBA0D5 /* either_stream_tc */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = CD407BD91E42D42B00BBA0D5 /* Build configuration list for PBXNativeTarget "either_stream_tc" */;
+			buildPhases = (
+				CD407BE41E42D4AE00BBA0D5 /* ShellScript */,
+				CD407BCE1E42D42B00BBA0D5 /* Sources */,
+				CD407BCF1E42D42B00BBA0D5 /* Frameworks */,
+				CD407BD01E42D42B00BBA0D5 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = either_stream_tc;
+			productName = either.stream;
+			productReference = CD407BD21E42D42B00BBA0D5 /* either_stream_tc */;
+			productType = "com.apple.product-type.tool";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		CD407BCA1E42D42B00BBA0D5 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0720;
+				ORGANIZATIONNAME = "Sam Jaffe";
+				TargetAttributes = {
+					CD407BD11E42D42B00BBA0D5 = {
+						CreatedOnToolsVersion = 7.2.1;
+					};
+				};
+			};
+			buildConfigurationList = CD407BCD1E42D42B00BBA0D5 /* Build configuration list for PBXProject "either.stream" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = CD407BC91E42D42B00BBA0D5;
+			productRefGroup = CD407BD31E42D42B00BBA0D5 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				CD407BD11E42D42B00BBA0D5 /* either_stream_tc */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		CD407BE41E42D4AE00BBA0D5 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"$(SRCROOT)/either_stream.t.h",
+			);
+			outputPaths = (
+				"$(SRCROOT)/either_stream_tc.cpp",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "cxxtestgen --error-printer -o either_stream_tc.cpp either_stream.t.h";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		CD407BCE1E42D42B00BBA0D5 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				CD407BE31E42D45A00BBA0D5 /* either_stream_tc.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		CD407BD71E42D42B00BBA0D5 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		CD407BD81E42D42B00BBA0D5 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		CD407BDA1E42D42B00BBA0D5 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				HEADER_SEARCH_PATHS = /usr/local/include/;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ../../../types/;
+			};
+			name = Debug;
+		};
+		CD407BDB1E42D42B00BBA0D5 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				HEADER_SEARCH_PATHS = /usr/local/include/;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				USER_HEADER_SEARCH_PATHS = ../../../types/;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		CD407BCD1E42D42B00BBA0D5 /* Build configuration list for PBXProject "either.stream" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD407BD71E42D42B00BBA0D5 /* Debug */,
+				CD407BD81E42D42B00BBA0D5 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		CD407BD91E42D42B00BBA0D5 /* Build configuration list for PBXNativeTarget "either_stream_tc" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				CD407BDA1E42D42B00BBA0D5 /* Debug */,
+				CD407BDB1E42D42B00BBA0D5 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = CD407BCA1E42D42B00BBA0D5 /* Project object */;
+}

+ 88 - 0
either_stream.hpp

@@ -0,0 +1,88 @@
+//
+//  either_stream.hpp
+//  optional.stream
+//
+//  Created by Sam Jaffe on 1/28/17.
+//
+
+#pragma once
+
+#include <type_traits>
+
+#if __cplusplus > 201402L
+#include <variant>
+template <typename T, typename E>
+using either = std::variant<T, E>;
+#else
+#include "variant/variant.hpp"
+template <typename T, typename E>
+using either = variant<T, E>;
+#endif
+
+namespace stream { namespace either {
+  namespace detail {
+    template <typename T> class either_stream;
+    
+    template <typename T> struct either_left;
+    template <typename T, typename E>
+    struct either_left<::either<T, E>> { using type = T; };
+    
+    template <typename T, typename E>
+    class either_stream<::either<T, E>> {
+    private:
+      template <typename F, typename T2>
+      using map_f = decltype(std::declval<F>()(std::declval<T2>()));
+      
+      template <typename F, typename T2>
+      using flatmap_f = typename either_left<decltype(std::declval<F>()(std::declval<T2>()))>::type;
+
+    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)) {}
+      
+      template <typename F>
+      either_stream<::either<map_f<F, T>, E>> map(F && fun) const {
+        using next_t = either_stream<::either<map_f<F, T>, E>>;
+        return value.index() == 0 ? next_t{fun(std::get<0>(value))} : next_t{std::get<1>(value)};
+      }
+      
+      template <typename F>
+      either_stream<variant<flatmap_f<F, T>, E>> flatmap(F && fun) const {
+        using next_t = either_stream<::either<flatmap_f<F, T>, E>>;
+        return value.index() == 0 ? next_t{fun(std::get<0>(value))} : next_t{std::get<1>(value)};
+      }
+      
+      template <typename FT, typename FE, typename = typename std::enable_if<!(std::is_void<map_f<FT, T>>::value && std::is_void<map_f<FE, E>>::value)>::type>
+      typename std::common_type<map_f<FT, T>, map_f<FE, E>>::type match(FT && left, FE && right) {
+        return value.index() == 0 ? left(std::get<0>(value)) : right(std::get<1>(value));
+      }
+
+      template <typename FT, typename FE, typename = typename std::enable_if<std::is_void<map_f<FT, T>>::value && std::is_void<map_f<FE, E>>::value>::type>
+      void match(FT && left, FE && right) {
+        value.index() == 0 ? left(std::get<0>(value)) : right(std::get<1>(value));
+      }
+    private:
+      ::either<T, E> value;
+    };
+  }
+  
+  template <typename T, typename E>
+  auto make_stream(T const & opt) -> detail::either_stream<::either<T, E>> {
+    return {{opt}};
+  }
+
+  template <typename E, typename T>
+  auto make_stream(T const & opt) -> detail::either_stream<::either<T, E>> {
+    return {{opt}};
+  }
+
+  template <typename E>
+  auto make_stream(typename detail::either_left<E>::type const & opt) -> detail::either_stream<E> {
+    return {{opt}};
+  }
+} }

+ 97 - 0
either_stream.t.h

@@ -0,0 +1,97 @@
+//
+//  either_stream.t.h
+//  optional.stream
+//
+//  Created by Sam Jaffe on 1/30/17.
+//
+
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+
+#include "either_stream.hpp"
+
+enum class MathError {
+  OutOfBounds,
+  DivideByZero,
+  DomainError
+};
+
+using MathObject = either<double, MathError>;
+
+MathObject divide(double num, double den) {
+  if (std::abs(den) < 0.000000001) {
+    return MathError::DivideByZero;
+  } else {
+    return num / den;
+  }
+}
+
+double multiply(double lhs, double rhs) { return lhs * rhs; }
+
+MathObject log(double value, double base) {
+  if (value <= 0) {
+    return MathError::DomainError;
+  } else {
+    return divide(std::log(value), std::log(base));
+  }
+}
+
+class either_stream_TestSuite : public CxxTest::TestSuite {
+public:
+  void test_either_becomes_error() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    strm.flatmap([](double d) { return divide(d, 0); })
+    .match([](double) { TS_FAIL("Expected Error Type"); },
+           [](MathError e) { TS_ASSERT_EQUALS( MathError::DivideByZero, e ); });
+  }
+  
+  void test_either_computes_result() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    strm.map([](double d) { return multiply(d, 10.0); })
+    .match([](double d) { TS_ASSERT_DELTA(10.0, d, 0.00001); },
+           [](MathError) { TS_FAIL("Expected Value"); });
+  }
+  
+  void test_either_computes_result2() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    bool is_error = strm.map([](double d) { return multiply(d, 10.0); })
+    .match([](double) { return false; },
+           [](MathError) { return true; });
+    TS_ASSERT(!is_error);
+  }
+  
+  void test_either_propogates_error() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    strm.flatmap([](double d) { return divide(d, 0); })
+    .map([](double d) { TS_FAIL("Operating on bad data"); return 0; })
+    .match([](double) { TS_FAIL("Expected Error Type"); },
+           [](MathError e) { TS_ASSERT_EQUALS( MathError::DivideByZero, e ); });
+  }
+  
+  void test_either_propogates_same_error() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    strm.flatmap([](double d) { return log(1.0, d); })
+    .flatmap([](double d) { return log(d, 1.0); })
+    .match([](double) { TS_FAIL("Expected Error Type"); },
+           [](MathError e) { TS_ASSERT_EQUALS( MathError::DivideByZero, e ); });
+  }
+
+  void test_either_propogates_same_error2() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    strm.flatmap([](double d) { return log(0.0, d); })
+    .flatmap([](double d) { return log(d, 1.0); })
+    .match([](double) { TS_FAIL("Expected Error Type"); },
+           [](MathError e) { TS_ASSERT_EQUALS( MathError::DomainError, e ); });
+  }
+  
+  void test_either_propogates_same_error3() {
+    auto strm = stream::either::make_stream<MathObject>(1.0);
+    bool is_error = strm.flatmap([](double d) { return log(0.0, d); })
+    .flatmap([](double d) { return log(d, 1.0); })
+    .match([](double) { return false; },
+           [](MathError) { return true; });
+    TS_ASSERT(is_error);
+  }
+
+};