Skip to content

lski/Lski.Fn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lski.Fn

Nuget downloads GitHub license

A few simple functional class; Result, Maybe and Either to enable a more functional style of programming in C#. Although written from scratch it is heavily influenced by a couple of others projects, please see the references below.

Semantic Versioning

This project adheres to semantic versioning, so any breaking changes will be restricted to major version increases.

Result

At its simplest a Result can be used to wrap a response from an action that could potentially not succeed. If an action works as desired it can be described as the "happy path" and it can be trickey to handle the "none happy path" where things go wrong, without throwing expensive exceptions. I highly recommend the Railway Oriented Programming video which explains it in better detail.

Returning a result object that states whether an action was successful or not and contains either the data from the action or the error that was encountered as a message.

Create a Successful Result:

// explicitly
var foo = new Bar();
var result = foo.ToSuccess(); // == Result.Success("success");

// implicitly
public Result<Bar> GetString() 
{
    return new Bar();
}

Create a Failed Result:

// explicity
var result = "an error occured".ToFail<Bar>(); // == Result.Fail<string>("An error occured");

// implicitly
public Result<Bar> GetString() 
{
    return new Error("an error occured");
}

Use a Result:

// directly
if (result.IsSuccess) { // or result.IsFailure
    var val = result.Value;
}
else {
    var err = result.Error; 
}

// or fluently 
result.OnSuccess((val) => {
    // Do stuff only if successful
});

result.OnFailure((err) => {
    // Do stuff only if a failure
});

NB: A successful Result can not contain a null value, so you can be certain that a value will not need to be checked for null.

Chain together:

result
    .OnFailure((err) => {
        // Not run
    })
    .OnSuccess((val) => {
        // Do stuff and return a new result
        return DateTime.Now;
    })
    .OnSuccess((val) => {
        // val is now of the return type from the OnSuccess above so a DateTime object
        val.AddDays(1);
    });

Whats the advantage to the above? In large quanities of code this can make it easier to read, but also means that it encourages small pure functions that can be more easily tested. Also smaller blocks of code are generally easier to maintain as the complexity is lower.

Maybe

Although debated, using null is not a recommended practice as it can be an anti pattern and using it has many drawbacks, see references.

For me the biggest pain is because we can never be sure that a variable is null or not we have to constantly check for a null 'value' as it can be amibigous.

string name = anObj.GetName();
if (name != null) {
    // This is the only way to be sure if the value has been set. 
    // And needs to be everywhere the method is used.
}

Another problem can be type checking, if a value is null then checking it with the operator is probably doesnt work as you would expect it too.

string foo = null;
var isNullAString = foo is string;

// isNullAString == false

So we can use Maybe as a way of wrapping a potentially null value.

Using Maybe does two things, it makes it obvious to the subscriber of the variable that the value can be empty, but also it means type checking with "is" works as expected.

Create a Maybe object:

var myVar = "a value".ToMaybe(); // == Maybe.Create("a value");

// implicit casting
public Maybe<string> GetString() 
{
    return "Hello World";
}

Create a 'null' Maybe:

var myVar = Maybe.None<string>(); // == Maybe.Create<string>(null);

Then use HasValue to test for a null value

// directly
if (myVar.HasValue) { // or myVar.HasNoValue
    otherVar = myVar.Value; // Accessing Value when it hasnt one results in an InvalidOperationException
}

// fluently
myVar.Bind((val) => {
    // Only runs if myVar has a value, val is never null
});

Access the underlying value, providing a default value if null:

var val = maybe.Unwrap("a default value");

Type checking is also possible on what would have been a null value:

var bar = Maybe<string>(null);
var isNullMaybe = bar is Maybe<string>;

// isNullMaybe == true

NB The Maybe pattern is sometimes called "Option" as it is in F#

Either

Either<TLeft, TRight> is a wrapper class where you are unsure if an action will give one answer or another, it could be thought of as a more generic version of Result.

Useful when you need to return either one of two values from a function and give type safety. Either also provides the ability to handle different execution paths.

// explicitly
var either = "Hello World".ToLeft<string, int>(); // == Either.Left<string, int>("Hello World");

// implicitly
public Either<string, int> GetValue() 
{
    return "Hello World";
}

Usage:

if (either.IsLeft) {
    // Would throw an exception if not a left-sided, hence the IsLeft check.
    string left = either.ToLeft();
}

// does not throw an exception, but returns the default value if incorrect side
string left = either.ToLeft("a default value"); 

Chaining:

var either = Either.Left<int, string>(100);

either = either
    .Left(val => val + 11);             // runs and returns a new either
    .Right(right => val + "not run")    // doesnt run
    .Left(left => val + " dalmatians"); // runs and returns a new right-sided either

either.ToRight() == "101 dalmatians"    // true

If wanting to handle the possibility of either side at the same time:

var either = Either.Left<int, string>(0);
    
either.LeftOrRight(left => /* Runs */ 10, right => /* Does not run */ "foo");

Returning a resolved value from either side:

var result = either.ToValue(left => left += "world", right => right += " == 10");

As both functions return the same type, in this case a string, the correct func, based on the side of the either, runs and returns a value. A useful way of breaking out of an either to give a resolved value.

References

About

C# functional classes

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages