Problem Statement
The World Feet Organization (WFO) has tasked me with calculating the sum of shoe sizes provided by client applications. The data sent to my API might contain invalid shoe sizes, such as negative values. My goal is to use always-valid domain models, as it is common practice in domain-driven design.
I've always used Result classes that expose the success or failure object (similar to this question). If any object was not created with a valid state, the failure (Exception or Error) is returned, otherwise the success (the valid object) is returned.
Question
Using a functional programming paradigm with C# LanguageExt, how would one achieve this? The code below does the job, but the null values used to skip the failure matching is just painful to look at. I just don't know how to access the values in any other way using this Result type.
Code Example
using LanguageExt.Common;
namespace ConsoleAppAlwaysValidFunctional;
public class Application
{
public Result<double> CalculateSumOfShoeSizes()
{
var footOne = Foot.Create(8.0);
var footTwo = Foot.Create(8.0);
var footThree = Foot.Create(18.0);
if (footOne.IsFaulted) // also, how do I pass the inner exception?
{
var e1 = footOne.Match(null, exception => exception);
return new Result<double>(e1); // like this?
}
if (footTwo.IsFaulted)
{
return new Result<double>(new Exception("Second foot is not valid"));
}
if (footThree.IsFaulted)
{
return new Result<double>(new Exception("Third foot is not valid"));
}
// all three objects are valid, extract the shoe sizes
var firstShoeSize = footOne.Match(success => success.ShoeSize, null);
var secondShoeSize = footTwo.Match(success => success.ShoeSize, null);
var thirdShoeSize = footThree.Match(success => success.ShoeSize, null);
var sum = firstShoeSize + secondShoeSize + thirdShoeSize;
return new Result<double>(sum);
}
}
public class Foot
{
public double ShoeSize { get; private set; }
private Foot(double shoeSize)
{
this.ShoeSize = shoeSize;
}
public static Result<Foot> Create(double shoeSize)
{
if (shoeSize < 0)
{
var exception = new Exception("Shoe size can't be negative");
return new Result<Foot>(exception);
}
var f = new Foot(8.0);
return new Result<Foot>(f);
}
}
You shouldn't be working with
Result<A>s directly. Instead, work withTry<A>, which is a delegate that returns aResult.Try<A>is a monad, so you can bind them together and use the LINQ query syntax to simplify a lot of your code.First change
Createto return aTry<Foot>Then you can use LINQ to bind all the shoe sizes together. The exceptions are also propagated in the way you expect:
I think it would be more idiomatic to make
CalculateSumOfShoeSizesreturnTry<double>. Note that this makes it lazy:and only consume the
Trywhen you actually need it, by invoking it (adding()at the end). Then you would get aResult<double>. And then you can useIfFail,IfSucc,Match,match, and other methods to inspect it.If you want some of the
Createcalls to throw another exception that you specify, it's kind of hard to do withTryandResult. I would convert toEither:Note that the code loses its laziness.