LoopPerfect / neither

Either and Maybe monads for better error-handling in C++ ↔️
MIT License
249 stars 18 forks source link

neither composable. #9

Closed basiliscos closed 7 years ago

basiliscos commented 7 years ago

Hi,

I assumed that it should be composable, i.e. something like should work:

struct my_error_t {
    std::string s;
};

struct struct_a {
    int v;
};

struct struct_b {
    double v;
};

neither::Either<my_error_t, struct_a> f1();

neither::Either<my_error_t, struct_b> f2();

void code() {
    auto value = f1()
        .rightMap([](const struct_a& v){
            return f2();
        })
        .rightMap([](const struct_b& v){
            return 5;
        })
        .leftMap([](const auto& my_error){
            return 6;
        }).join();
    // value should be either 5 or 6
}

To let it compile, I have to write something like :

    auto value = f1()
        .rightMap([](const struct_a& v){
            return f2();
        })
        .rightMap([](const auto& v){
            return v.rightMap([](const struct_b& v){
                return 5;
            })
            .leftMap([](const auto& my_error){
                return 6;
            });
        })
        .leftMap([](const auto& my_error){
            return 6;
        }).join();
    // value should be either 5 or 6

what is the sense of the library if it is not composable, i.e. forces me to unwrap in the next right_map the monadic result of the previous right_map and duplicate errors handling ?

WBR, basiliscos

njlr commented 7 years ago

Perhaps a rightFlatMap?

void code() {
    auto value = f1()
        .rightFlatMap([](const struct_a& v){
            return f2();
        })
        .rightMap([](const struct_b& v){
            return 5;
        })
        .leftMap([](const auto& my_error){
            return 6;
        }).join();
    // value should be either 5 or 6
}

rightFlatMap would have a signature like:

Either<L, R> -> R -> Either<L, X> -> Either<L, X>
basiliscos commented 7 years ago

yep, something like that (I'm not too familiar with Haskel)

nikhedonia commented 7 years ago

sounds like a good idea.

We probably can probably infer the return type and make leftMap do leftFlatMap depending on the type.

basiliscos commented 7 years ago

Also, I'm not sure that it is related here, but it would be nice to have movable signature, i.e.

rightFlatMap([](struct_a&& v){
 ...;
}
nikhedonia commented 7 years ago

@

basiliscos commented 7 years ago

Hey, sorry I was busy with other stuff.

But it seems my example still does not work:

#include <neither/either.hpp>
#include <string>

struct my_error_t {
    std::string s;
};

struct struct_a {
    int v;
};

struct struct_b {
    double v;
};

neither::Either<my_error_t, struct_a> f1();

neither::Either<my_error_t, struct_b> f2();

void code() {
    auto e = f1();
    e.rightFlatMap([](const auto& v){
    });
}
clang++  -c -I/home/basiliscos/development/binary.com/mt5-binary/libs/ -std=c++14 n.cpp
n.cpp:22:7: error: no matching member function for call to 'rightFlatMap'
    e.rightFlatMap([](const auto& v){
    ~~^~~~~~~~~~~~
/home/basiliscos/development/binary.com/mt5-binary/libs/neither/either.hpp:173:18: note: candidate template ignored: substitution failure [with RightCase = (lambda at n.cpp:22:20)]: no
      matching function for call to 'ensureEitherLeft'
  constexpr auto rightFlatMap(RightCase const& rightCase) const
                 ^
1 error generated.

Also, note that it would be nice to remove const from rightFlatMap, because I'd like to move the value, RightCase const& rightCase forces to copy right value.

nikhedonia commented 7 years ago

Currently, a mapping function must map to a non-void value. Your Lambda returns a void, hence it fails(PullRequest welcome).

As moves are sideeffects, those are forbidden on l-values as explained in #11.

However they are valid on r-values. This should work:

  Either<int, std::string> e = left(1);
  e.leftMap([](auto x){
    return std::make_unique(x);
  }).leftMap([](auto&& x) { 
    return x;
  });

If you really want to move a value out of a named Either variable you have to move:

  std::move(e).leftMap([](auto&& x){ 
    return x;
  });

 //e is now invalid and must not be used

I'll extend the documentation and add some tests for this case

basiliscos commented 7 years ago

Currently, a mapping function must map to a non-void value. Your Lambda returns a void, hence it fails

I have tried:

    e.rightFlatMap([](const auto& v){
        return struct_b{};
    });

still the same error

nikhedonia commented 7 years ago

struct_b is not an either. If you want to map values use rightMap instead.

The differentiation of rightMap and rightFlatMap is necessary to support nested Eithers in a intuitive way.

basiliscos commented 7 years ago

Indeed that seems works

basiliscos commented 7 years ago

about moving

However they are valid on r-values. This should work:

    neither::Either<int, std::string> e = neither::left(1);
    e.leftMap([](auto x){
        return std::make_unique<int>(x);
    }).leftMap([](auto&& x) {
        return x;
    });

This does not compile :

/home/basiliscos/development/binary.com/mt5-binary/libs/neither/either.hpp:21:11: error: call to deleted constructor of 'const std::unique_ptr<int, std::default_delete<int> >'
  return {x};
          ^                   
...
nikhedonia commented 7 years ago

Gotcha, Eithers constructor doesn't work properly with move-only types. Fixing...

basiliscos commented 7 years ago

Thank you very much for quick feedback & helpful replies :)

nikhedonia commented 7 years ago

should be fixed in the latest commit