1

I'm trying to grok how to deal with functions that turn one "Either" into many "Either"s and how you then merge those back into a single stream.

The following turns one string into many numbers, squaring each and ignoring any errors. Given "1,2,Foo,4" it writes out 1,4,16.

It works but I don't understand why Bind(SafeSplit) returns an EitherData which has a Right property I need to dereference.

private static void ValidationPipelineVersion()
{
    var result = Right<Exception, string>("1,2,Foo,4")
        .Bind(SafeSplit)
        .Bind(numStrs => numStrs.Right.Select(SafeParse))
        .Rights() // ignore exceptions for now
        .Map(num => num * num) // Squared
        .Iter(num => WriteLine(num));
}

private static Either<Exception,string[]> SafeSplit(string str)
{
    try
    {
        return str.Split(",");
    }
    catch (Exception e)
    {
        return e;
    }
}

private static Either<Exception,int> SafeParse(string str)
{
    try
    {
        return int.Parse(str);
    }
    catch (Exception e) 
    {
        return e;
    }
}
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
RustyF
  • 13
  • 2
  • Also looking for a cleaner suggestions to the above from those that know the library - thanks. – RustyF Jun 26 '23 at 10:57
  • I don't know that lib, but the first Bind quite obviously gives you some kind of stream consisting of `Either`. So that is what you need to consume in the second `Bind`. I don't know, but maybe you could do `....Bind(SafeSplit).Rights().Bind(numStrs => numStrs.Right.Select(SafeParse)).Rights()...` ? – Fildor Jun 26 '23 at 11:24
  • "Cleaner" is quite an opinionated term. I, for one, would consider it "cleaner" to actually handle exceptions. And for `SafeParse`, I'd probably go with `int.TryParse` instead of an exception throwing Parse. – Fildor Jun 26 '23 at 11:26
  • Yep , cleaner is subjective ;) I would usually handle the exception but the code was just a vehicle for grokking the problem of 'taming' multiple Eithers into a single stream. – RustyF Jun 26 '23 at 12:00
  • ^^ Dough, of course that would be `....Bind(SafeSplit).Rights().Bind(numStrs => numStrs.Select(SafeParse)).Rights()...` – Fildor Jun 26 '23 at 12:03
  • Thanks but that doesn't compile for me - `Either` (returned from `.Bind(SafeSplit)`) doesn't have a member `Rights()` – RustyF Jun 26 '23 at 12:12

1 Answers1

1

I don't understand why Bind(SafeSplit) returns an EitherData which has a Right property I need to dereference.

You don't have to (in fact, I'd advise against it), but it compiles because the LanguageExt library comes with a Bind overload with this signature:

public static IEnumerable<R> Bind<T, R>(
    this IEnumerable<T> self, Func<T, IEnumerable<R>> binder)
{
    return self.BindFast(binder);
}

and since Either<L, R> implements IEnumerable<EitherData<L, R>> the code in the OP compiles.

The reason it's not advisable is because the Right property is partial or unsafe. It works as long as the Either object is a Right value, but the whole point of the type is that you rarely know whether or not that's the case.

Here's an alternative expression that may not look much simpler, but at least is safer:

var result = Right<Exception, string>("1,2,Foo,4")
    .Bind(SafeSplit)
    .Map(numStrs => numStrs
        .Map(SafeParse)
        .Rights()
        .Map(num => num * num) // Square
        .Iter(WriteLine));

Instead of trying to get the value out of the monad, it keeps the outer Either container and performs all actions inside of it.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Thanks @mark-seemann. That was what I was looking for. It looks obvious in retrospect and if this was pure Linq with lists I probably would have spotted that! My real-world example is a tad trickier as it bind some sync functions but this is a good base. – RustyF Jun 26 '23 at 13:48