diff --git a/.buckconfig b/.buckconfig index 5274f36..8debfd6 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,6 +1,14 @@ [project] ignore = .git, .buckd +[parser] + default_build_file_syntax = SKYLARK + [cxx] + should_remap_host_platform = true + +[cxx#linux-x86_64] + cxxflags = -std=c++14 + +[cxx#macosx-x86_64] cxxflags = -std=c++14 - gtest_dep = google.gtest//:gtest diff --git a/.gitignore b/.gitignore index 6fb2d9d..dc2d1d2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ buckaroo buck-out .buckd .buckconfig.local +.buckconfig.d BUCKAROO_DEPS +buckaroo_macros.bzl diff --git a/.travis.yml b/.travis.yml index d7e3caa..1a51c48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,19 +7,27 @@ addons: sources: - ubuntu-toolchain-r-test packages: - - g++-5 + - g++-6 + - gcc-6 + homebrew: + taps: + - facebook/fb + packages: + - buck + +os: + - linux + - osx + +env: + - BUCKAROO_VERSION=buckaroo-redux-alpha-8 + +osx_image: xcode9.3 before_install: - - cd /usr/bin/ && sudo rm g++ && sudo ln -s g++-5 g++ && cd - - - g++ --version - - sudo apt-get install default-jdk - - wget -O buck.deb https://github.com/facebook/buck/releases/download/v2017.09.04.02/buck-2017.09.04.02_all.deb - - sudo dpkg -i buck.deb - - wget -O buckaroo.deb https://github.com/LoopPerfect/buckaroo/releases/download/v1.3.1/buckaroo_1.3.1_amd64.deb - - sudo dpkg -i buckaroo.deb + - ./travis/before-install-$TRAVIS_OS_NAME.sh script: - - buckaroo install - - buck build //:neither - - buck build //:test#linux-x86_64 - - buck run //:test#linux-x86_64 + - ./buckaroo-client install + - buck build -c ui.superconsole=DISABLED :neither + - buck test -c ui.superconsole=DISABLED //... diff --git a/BUCK b/BUCK index 43f2bb3..7f16be3 100644 --- a/BUCK +++ b/BUCK @@ -1,3 +1,6 @@ +load('//:subdir_glob.bzl', 'subdir_glob') +load('//:buckaroo_macros.bzl', 'buckaroo_deps_from_package') + prebuilt_cxx_library( name = 'neither', header_only = True, @@ -18,11 +21,8 @@ cxx_test( srcs = glob([ 'neither/tests/**/*.cpp', ]), - platform_linker_flags = [ - ('^linux.*', [ '-lpthread', ]), - ], - link_style = 'shared', - deps = [ - ':neither', - ], + deps = buckaroo_deps_from_package('github.com/buckaroo-pm/google-googletest') + \ + [ + ':neither', + ], ) diff --git a/README.md b/README.md index 3ca1c4a..8c07885 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - Sponsor - # neither A functional implementation of Either in C++14. @@ -7,7 +5,7 @@ A functional implementation of Either in C++14. [![Travis](https://img.shields.io/travis/LoopPerfect/neither.svg)](https://travis-ci.org/LoopPerfect/neither) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/LoopPerfect/neither/master/license) ``` -buckaroo install loopperfect/neither +buckaroo add github.com/loopperfect/neither ``` ## Examples @@ -26,15 +24,15 @@ auto unsafe = [] { // a function that throws, sometimes we can't avoid it... Either e = Try(unsafe); // let's lift the exception into the typesystem e.left() - .map([](auto const& e) { - return std::cerr << e.what() << std::endl; + .map([](auto const& e) { + return std::cerr << e.what() << std::endl; }); // print error if available int result = e .leftMap([](auto) { return 42; }) // do nothing with exception and map to 42 .rightMap([](auto x) { return x * 2; }) // do further computation if value available .join() // join both sides of either - + ASSERT_TRUE(result == 42); ``` @@ -53,7 +51,7 @@ std::string resultString = compute(5) [](auto errorStr) { return "compute said: " + errorStr; }, // error-case [](auto x) { return "compute said: " + std::to_string(x); } // success-case ); - + std::cout << resultString << std::endl; ``` @@ -93,11 +91,11 @@ Maybe compute(float x) { Maybe x = compute(-4) .map([](auto x){ return x*x;}) .map([](auto x){ return x+1 }); - + if(!x.hasValue) { std::cerr << "error occured" << std::endl; } - + ``` ### Monadic Lifting @@ -112,12 +110,12 @@ auto monadicSum = lift(sum); // transforms sum to: Maybe MonadicSum(Maybe don't use output parameters + - => don't use output parameters - Exceptions are 2-3 orders of magnitude slower if exceptions are thrown - => avoid throwing exceptions - not always possible - Overhead of exceptions grows linear with the callstack - => catch exceptions early - Exceptions are not part of the type-system - - annotating function signatures with `throw` and `noexcept` is not helpful; + - annotating function signatures with `throw` and `noexcept` is not helpful; contract breaches are not detected in compile-time but call `std::terminate` in run-time - handling exceptions is error prone and requires documentation - - => encode errors in the types to enforce propper handling by the API consumer + - => encode errors in the types to enforce proper handling by the API consumer ## Installation This library requires a C++ 14 compiler. -Install with [Buckaroo](https://buckaroo.pm): +Install with [Buckaroo](https://buckaroo.pm): + ``` -buckaroo install loopperfect/neither +buckaroo add github.com/loopperfect/neither ``` -The [Buck](https://www.buckbuild.com) target is `:neither` +The [Buck](https://www.buckbuild.com) target is `:neither` + +Alternatively you can copy & paste the headers to your include path: -Alternatively you can copy & paste the headers to your include path: ``` cp neither/include/*.hpp $InstallPath/include/neither ``` - - Sponsor diff --git a/buckaroo.json b/buckaroo.json deleted file mode 100644 index c9b34eb..0000000 --- a/buckaroo.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "neither", - "dependencies": { - "google/gtest": "1.8.0" - } -} diff --git a/buckaroo.lock.json b/buckaroo.lock.json deleted file mode 100644 index 5ae8232..0000000 --- a/buckaroo.lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "google/gtest": { - "source": { - "url": "https://github.com/google/googletest/archive/ec44c6c1675c25b9827aacd08c02433cccde7780.zip", - "sha256": "bc258fff04a6511e7106a1575bb514a185935041b2c16affb799e0567393ec30", - "subPath": "googletest-ec44c6c1675c25b9827aacd08c02433cccde7780" - }, - "buck": { - "url": "https://raw.githubusercontent.com/nikhedonia/googletest/665327f0141d4a4fc4f2496e781dce436d742645/BUCK", - "sha256": "d976069f5b47fd8fc57201f47026a70dee4e47b3141ac23567b8a0c56bf9288c" - } - } -} \ No newline at end of file diff --git a/buckaroo.lock.toml b/buckaroo.lock.toml new file mode 100644 index 0000000..0b3cb74 --- /dev/null +++ b/buckaroo.lock.toml @@ -0,0 +1,9 @@ +manifest = "eaaa6562a921b2d40d31aed0f4b442994cb5a3135397b66bbff0d1fd868b4abc" + +[[dependency]] +package = "github.com/buckaroo-pm/google-googletest" +target = "//:googletest" + +[lock."github.com/buckaroo-pm/google-googletest"] +versions = [ "branch=master" ] +revision = "cb8d2c1a2fcd344953e6c129a1a699a2c230551d" diff --git a/buckaroo.toml b/buckaroo.toml new file mode 100644 index 0000000..2a1effa --- /dev/null +++ b/buckaroo.toml @@ -0,0 +1,6 @@ +targets = [ "//:neither" ] + +[[dependency]] +package = "github.com/buckaroo-pm/google-googletest" +version = "branch=master" +private = true diff --git a/neither/include/either.hpp b/neither/include/either.hpp index 1ea7c1a..a6d28f6 100644 --- a/neither/include/either.hpp +++ b/neither/include/either.hpp @@ -58,22 +58,22 @@ struct Either { bool const isLeft = 0; constexpr Either( Left const& l ) - : leftValue{l.value} + : leftValue(l.value) , isLeft(1) {} constexpr Either( Right const& r ) - : rightValue{r.value} + : rightValue(r.value) , isLeft(0) {} Either(Left && l ) - : leftValue{std::move(l.value)} + : leftValue(std::move(l.value)) , isLeft(1) {} Either( Right && r ) - : rightValue{std::move(r.value)} + : rightValue(std::move(r.value)) , isLeft(0) {} @@ -116,19 +116,19 @@ struct Either { } static constexpr auto leftOf( L const& l ) { - return Either{ neither::left(l) }; + return Either( neither::left(l) ); } static constexpr auto rightOf( R const& r ) { - return Either{ neither::right(r) }; + return Either( neither::right(r) ); } static constexpr auto leftOf( L && l ) { - return Either{ neither::left(std::move(l)) }; + return Either( neither::left(std::move(l)) ); } static constexpr auto rightOf( R && r ) { - return Either{ neither::right(std::move(r)) }; + return Either( neither::right(std::move(r)) ); } template< @@ -150,12 +150,30 @@ struct Either { return isLeft ? std::move(leftValue) : std::move(rightValue); } + template< + class L2 = L, + class R2 = R, + typename std::enable_if::value && std::is_copy_constructible::value), int>::type = 0 + > + auto join()& + -> std::common_type_t { + return isLeft ? std::move(leftValue) : std::move(rightValue); + } + template constexpr auto join(LeftF const& leftCase, RightF const& rightCase) const -> decltype( isLeft? leftCase( leftValue ) : rightCase( rightValue ) ) { return isLeft ? leftCase( leftValue ) : rightCase( rightValue ); } + template::value && std::is_copy_constructible::value), int>::type = 0 + > + constexpr auto join(LeftF const& leftCase, RightF const& rightCase)& + -> decltype( isLeft? leftCase(std::move(leftValue)) : rightCase(std::move(rightValue)) ){ + return isLeft ? leftCase(std::move(leftValue)) : rightCase(std::move(rightValue)); + } + template constexpr auto leftMap(F const& leftCase) const& -> Either { @@ -173,6 +191,16 @@ struct Either { NextEither::rightOf( std::move(rightValue) ); } + + template::value && std::is_copy_constructible::value), int>::type = 0> + auto leftMap(F const& leftCase)& -> Either { + using NextEither = Either; + return isLeft ? + NextEither::leftOf(leftCase(std::move(leftValue))) : + NextEither::rightOf( std::move(rightValue) ); + } + template constexpr auto rightMap(F const& rightCase) const& -> Either { using NextEither = Either; @@ -182,13 +210,22 @@ struct Either { } template - auto rightMap(F const& rightCase)&& -> Either { + auto rightMap(F const& rightCase)&& -> Either { using NextEither = Either; return isLeft ? NextEither::leftOf( std::move(leftValue) ) : NextEither::rightOf( rightCase( std::move(rightValue) ) ); } + template::value && std::is_copy_constructible::value), int>::type = 0> + auto rightMap(F const& rightCase)& -> Either { + using NextEither = Either; + return isLeft ? + NextEither::leftOf( std::move(leftValue) ) : + NextEither::rightOf( rightCase( std::move(rightValue) ) ); + } + template constexpr auto leftFlatMap(LeftCase const& leftCase) const& -> decltype( ensureEitherRight(leftCase(isCopyable((L2)leftValue)), isCopyable((R2)rightValue))) { @@ -225,6 +262,19 @@ struct Either { return NextEither::rightOf(std::move(rightValue)); } + template::value && std::is_copy_constructible::value), int>::type = 0> + auto leftFlatMap(LeftCase const& leftCase)& + -> decltype( ensureEitherRight(leftCase(std::move(leftValue)), std::move(rightValue))) { + using NextEither = decltype(leftCase(std::move(leftValue))); + + if (!*this) { + return leftCase( std::move(leftValue) ); + } + + return NextEither::rightOf(std::move(rightValue)); + } + template auto rightFlatMap(RightCase const& rightCase)&& -> decltype( ensureEitherLeft(rightCase(std::move(rightValue)), std::move(leftValue))) { @@ -237,6 +287,19 @@ struct Either { return NextEither::leftOf(std::move(leftValue)); } + template::value && std::is_copy_constructible::value), int>::type = 0> + auto rightFlatMap(RightCase const& rightCase)& + -> decltype( ensureEitherLeft(rightCase(std::move(rightValue)), std::move(leftValue))) { + using NextEither = decltype(rightCase(std::move(rightValue))); + + if (*this) { + return rightCase(std::move(rightValue)); + } + + return NextEither::leftOf(std::move(leftValue)); + } + constexpr operator bool()const { return !isLeft; } }; diff --git a/neither/include/maybe.hpp b/neither/include/maybe.hpp index a4c347b..9078e14 100644 --- a/neither/include/maybe.hpp +++ b/neither/include/maybe.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include namespace neither { @@ -13,18 +15,20 @@ template <> struct Maybe {}; template struct Maybe { + using size_type = std::size_t; + union { T value; }; - bool const hasValue = 0; + bool const hasValue; - constexpr Maybe() : hasValue{0} {} + constexpr Maybe() : hasValue{false} {} - constexpr Maybe(T const& value) : value{value}, hasValue{1} {} - constexpr Maybe(T&& value) : value{std::move(value)}, hasValue{1} {} + constexpr Maybe(T const& value) : value{value}, hasValue{true} {} + constexpr Maybe(T&& value) : value{std::move(value)}, hasValue{true} {} - constexpr Maybe(Maybe) : hasValue{0} {} + constexpr Maybe(Maybe) : hasValue{false} {} constexpr Maybe(Maybe const &o) : hasValue{o.hasValue} { if (o.hasValue) { @@ -47,6 +51,10 @@ template struct Maybe { return value; } + constexpr size_type size() const noexcept { return hasValue ? 1: 0; } + + constexpr bool empty() const noexcept { return !hasValue; } + template constexpr auto map(F const &f) const& -> Maybe { @@ -99,12 +107,25 @@ auto maybe(T value) -> Maybe { return {value}; } template auto maybe() -> Maybe { return {}; } +namespace { + + inline + bool equal(Maybe const&, Maybe const&) { + return true; + } + + template + bool equal(Maybe const &a, Maybe const &b) { + if (a.hasValue) { + return b.hasValue && a.value == b.value; + } + return !b.hasValue; + } +} + template bool operator == (Maybe const& a, Maybe const& b) { - if (a.hasValue) { - return b.hasValue && a.value == b.value; - } - return !b.hasValue; + return equal(a, b); } template @@ -112,6 +133,8 @@ bool operator != (Maybe const& a, Maybe const& b) { return !(a == b); } +static const auto none = maybe(); + } #endif diff --git a/neither/tests/either.cpp b/neither/tests/either.cpp index 105c09e..07461e2 100644 --- a/neither/tests/either.cpp +++ b/neither/tests/either.cpp @@ -12,6 +12,7 @@ using namespace std::literals::string_literals; using IntOrStr = Either; using StrOrInt = Either; using StrOrStr = Either; +using PtrOrPtr = neither::Either, std::unique_ptr>; TEST(neither, either_join_left) { @@ -97,7 +98,7 @@ TEST(neither, either_flatMapToUnique) { } -TEST(neither, either_mapToUnique) { +TEST(neither, either_leftMapToUnique) { neither::Either e = left(1); auto u = e.leftFlatMap([](auto x) { return @@ -112,3 +113,137 @@ TEST(neither, either_mapToUnique) { ASSERT_TRUE(*u == 1); } + + +TEST(neither, either_rightMapToUnique) { + neither::Either e = right(1); + + auto u = e.rightFlatMap([](auto x) { return + Either>::rightOf( + std::make_unique(x)); + }).rightMap([](auto&& x) { + return std::move(x); + }).leftFlatMap([](auto&& x) { + return Either, std::unique_ptr>::leftOf( + std::make_unique(2)); + }).join(); + + ASSERT_TRUE(*u == 1); +} + +TEST(neither, either_leftMapFromStoredUnique) +{ + PtrOrPtr e = left(std::make_unique(1)); + auto u = e.leftMap([](auto x){ + return(std::move(x)); + }).join(); + + ASSERT_EQ(*u, 1); +} + +TEST(neither, either_rightMapFromStoredUnique) +{ + PtrOrPtr e = right(std::make_unique(1)); + auto u = e.rightMap([](auto x){ + return(std::move(x)); + }).join(); + + ASSERT_EQ(*u, 1); +} + +TEST(neither, either_leftMapOfConst) +{ + const auto e = IntOrStr::leftOf(1); + const auto f = e.leftMap([](auto x){return x;}); + const auto ret = f.rightMap([](auto x){return 0;}).join(); + + ASSERT_EQ(ret, 1); +} + +TEST(neither, either_rightMapOfConst) +{ + const auto e = IntOrStr::rightOf("Hello World!"); + const auto f = e.leftMap([](auto x){return std::string("Bye World!");}); + const auto ret = f.rightMap([](auto x){return x;}).join(); + + ASSERT_EQ(ret, "Hello World!"); +} + +TEST(neither, either_leftMapNotAltered) +{ + auto e = IntOrStr::leftOf(1); + e.leftMap([](auto x){return 3;}); + const auto ret = e.rightMap([](auto right){return 0;}).join(); + + ASSERT_EQ(ret, 1); +} + +TEST(neither, either_leftMapOfConstNotAltered) +{ + const auto e = IntOrStr::leftOf(1); + e.leftMap([](auto x){return 3;}); + const auto ret = e.rightMap([](auto right){return 0;}).join(); + + ASSERT_EQ(ret, 1); +} + +TEST(neither, either_rightMapNotAltered) +{ + auto e = IntOrStr::rightOf("Hello World!"); + e.leftMap([](auto left){return "";}); + const auto ret = e.leftMap([](auto x){return std::string("Bye World!");}).join(); + + ASSERT_EQ(ret, "Hello World!"); +} + +TEST(neither, either_rightMapOfConstNotAltered) +{ + const auto e = IntOrStr::rightOf("Hello World!"); + e.leftMap([](auto left){return "";}); + const auto ret = e.leftMap([](auto x){return std::string("Bye World!");}).join(); + + ASSERT_EQ(ret, "Hello World!"); +} + +TEST(neither, either_leftFlatMapFromStoredUnique) +{ + PtrOrPtr e = left(std::make_unique(1)); + auto u = e.leftFlatMap([](auto x){ + return PtrOrPtr::leftOf(std::make_unique(0)); + }).join(); + + ASSERT_EQ(*u, 0); +} + +TEST(neither, either_rightFlatMapFromStoredUnique) +{ + PtrOrPtr e = right(std::make_unique(1)); + auto u = e.rightFlatMap([](auto x){ + return PtrOrPtr::leftOf(std::make_unique(0)); + }).join(); + + ASSERT_EQ(*u, 0); +} + +TEST(neither, either_joinFromStoredUnique) +{ + PtrOrPtr e = right(std::make_unique(1)); + auto u = e.join( + [](auto left){ + return left; + }, + [](auto right){ + return right; + } + ); + + ASSERT_EQ(*u, 1); +} + +TEST(neither, either_joinFromStoredUniqueNoFunction) +{ + PtrOrPtr e = right(std::make_unique(1)); + auto u = e.join(); + + ASSERT_EQ(*u, 1); +} diff --git a/neither/tests/maybe.cpp b/neither/tests/maybe.cpp index 10456b8..54b5120 100644 --- a/neither/tests/maybe.cpp +++ b/neither/tests/maybe.cpp @@ -53,3 +53,29 @@ TEST(neither, maybe_comparison) { EXPECT_TRUE(d == d); EXPECT_TRUE(d == e); } + +TEST(neither, maybe_size) { + + auto none = maybe(); + auto some = maybe(1); + + EXPECT_EQ(none.size(), 0); + EXPECT_EQ(some.size(), 1); +} + +TEST(neither, maybe_empty) { + + auto none = maybe(); + auto some = maybe(1); + + EXPECT_TRUE(none.empty()); + EXPECT_FALSE(some.empty()); +} + +TEST(neither, maybe_none) { + auto none1 = none; + auto none2 = none; + + EXPECT_TRUE(none1 == none2); + EXPECT_FALSE(none1 != none2); +} diff --git a/subdir_glob.bzl b/subdir_glob.bzl new file mode 100644 index 0000000..97950bf --- /dev/null +++ b/subdir_glob.bzl @@ -0,0 +1,82 @@ +"""Provides utility macros for working with globs.""" + +def _paths_join(path, *others): + """Joins one or more path components.""" + result = path + + for p in others: + if p.startswith("/"): # absolute + result = p + elif not result or result.endswith("/"): + result += p + else: + result += "/" + p + + return result + +def subdir_glob(glob_specs, exclude = None, prefix = ""): + """Returns a dict of sub-directory relative paths to full paths. + + The subdir_glob() function is useful for defining header maps for C/C++ + libraries which should be relative the given sub-directory. + Given a list of tuples, the form of (relative-sub-directory, glob-pattern), + it returns a dict of sub-directory relative paths to full paths. + + Please refer to native.glob() for explanations and examples of the pattern. + + Args: + glob_specs: The array of tuples in form of + (relative-sub-directory, glob-pattern inside relative-sub-directory). + type: List[Tuple[str, str]] + exclude: A list of patterns to identify files that should be removed + from the set specified by the first argument. Defaults to []. + type: Optional[List[str]] + prefix: If is not None, prepends it to each key in the dictionary. + Defaults to None. + type: Optional[str] + + Returns: + A dict of sub-directory relative paths to full paths. + """ + if exclude == None: + exclude = [] + + results = [] + + for dirpath, glob_pattern in glob_specs: + results.append( + _single_subdir_glob(dirpath, glob_pattern, exclude, prefix), + ) + + return _merge_maps(*results) + +def _merge_maps(*file_maps): + result = {} + for file_map in file_maps: + for key in file_map: + if key in result and result[key] != file_map[key]: + fail( + "Conflicting files in file search paths. " + + "\"%s\" maps to both \"%s\" and \"%s\"." % + (key, result[key], file_map[key]), + ) + + result[key] = file_map[key] + + return result + +def _single_subdir_glob(dirpath, glob_pattern, exclude = None, prefix = None): + if exclude == None: + exclude = [] + results = {} + files = native.glob([_paths_join(dirpath, glob_pattern)], exclude = exclude) + for f in files: + if dirpath: + key = f[len(dirpath) + 1:] + else: + key = f + if prefix: + key = _paths_join(prefix, key) + results[key] = f + + return results diff --git a/travis/before-install-linux.sh b/travis/before-install-linux.sh new file mode 100755 index 0000000..e9fbbd7 --- /dev/null +++ b/travis/before-install-linux.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90 +sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 90 + +c++ --version +g++ --version +gcc --version + +sudo apt-get install -y equivs openjdk-8-jdk + +wget -O buck.deb https://github.com/facebook/buck/releases/download/v2018.10.29.01/buck.2018.10.29.01_all.deb +sudo dpkg -i buck.deb +buck --version + +wget https://github.com/LoopPerfect/buckaroo/releases/download/$BUCKAROO_VERSION/buckaroo-linux -O buckaroo-client +chmod +x ./buckaroo-client +./buckaroo-client version diff --git a/travis/before-install-osx.sh b/travis/before-install-osx.sh new file mode 100755 index 0000000..2e77b82 --- /dev/null +++ b/travis/before-install-osx.sh @@ -0,0 +1,6 @@ +brew tap facebook/fb +brew install buck + +wget https://github.com/LoopPerfect/buckaroo/releases/download/$BUCKAROO_VERSION/buckaroo-macos -O buckaroo-client +chmod +x ./buckaroo-client +./buckaroo-client version