Search code examples
c#language-ext

LanguageExt Fold list of eithers to one either


I have a function that performs a set of queries, each returning an Either<Error, Result>. I collect these into a List<>. I want to fold these individual return values together so that the overall return result is a Either<Error, List<Result>>. I got this far;

class Error
{
    string Message { get; set; }
    List<Error> ChildErrors { get; set; }
}

class Result
{
}

Either<Error, List<Result>> GetResults(List<string> queries)
{
    var internalResults = new List<Either<Error, Result>>();

    foreach (string query in queries)
    {
        internalResults.Add(GetResultForQuery(query));
    }

    var initialState = (new List<Error>(), new List<Result>());

    (List<Error> errors, List<Result> events) =
        internalResults.Fold(initialState, (state, item) =>
    {
        (List<Error> err, List<Result> res) = state;
        item.Right(i => res.Add(i)).Left(e => err.Add(e));
        return (err, res);
    });

    if (errors.Count > 0)
    {
        var groupedError = new Error {
            Message = "One or more problems encountered",
            ChildErrors = errors
        };

        return Left<Error, List<Result>>(groupedError);
    }

    return Right<Error, List<Result>>(events);
}

I believe it should work but it is not particularly elegant. There must be a better way to do it (i.e. more monadic). Any clues?


Solution

  • You should replace System.Collections.Generic.List by one of the LanguageExt data types, e.g. Seq or Lst or Arr ...

    I used Seq here for illustration purposes. You can convert a IEnumerable to Seq by using ToSeq() extension, too.

    Then you can use Sequence like here without any hassle:

    
    class Error
    {
        string Message { get; set; }
        Seq<Error> ChildErrors { get; set; }
    }
    
    class Result
    {
    }
    
    private static Either<Error, Result> GetResultForQuery(string query) =>
        throw new NotImplementedException();
    
    Either<Error, Seq<Result>> GetResults(Seq<string> queries)
    {
        Seq<Either<Error, Result>> internalResults = queries.Map(GetResultForQuery);
    
        return internalResults.Sequence();
    }
    
    Either<Error, Seq<Result>> GetResults2(Seq<string> queries) =>
        queries.Map(GetResultForQuery).Sequence();
    

    GetResults2 is just a short version of GetResults.