A rich implementation of ”Maybe” and ”Either” monads for C# with extra utils, extensions, and integrations. The purpose of creating the library was to provide the cleanest declarative interface.
C# in version 7.3 or higher.
Use one of the the commands to install.
CLI | Command |
---|---|
PM | Install-Package CleanMonads |
dotnet | add package CleanMonads |
paket | add CleanMonads |
- Fixed bug in extension method of "Maybe" named "Filter"
You can create the monads in 3 way.
Maybe<int> val = 5;
var val = MaybeFactory.Just(number); // Will throw exception when null;
var val = MaybeFactory.MaybeOf(number); // Create "Nothing" when null.
var val = MaybeFactory.Nothing; // "Nothing" Returns Maybe<INothing>
Maybe<string> val = MaybeFactory.Nothing; // Cast to "Nothing" as Maybe<string>
var val = MaybeFactory.NothingOf<int>(); // Returns "Nothing" of Maybe<int>
//etc...
// Or shorter way, that I recommend.
using static Monads.MaybeFactory;
var val = Just(number);
var val = MaybeOf(number);
var val = Nothing;
var val = NothingOf<int>();
var val = Right(5);
var val = Left(5);
//etc...
var val = 5.ToMaybe();
var val = 5.ToEither("other");
var val = 5.Just();
var val = 5.Nothing();
var val = 5.ToJustIf(x => x == 5);
//etc...
You can be confused about why in "MabyFactory" "Nothing" is represented as Method and Property.
So let's take a look on it:
return Nothing; // returns Maybe<NotDefined>
return NothingOf<int>(); // returns Maybe<int>
As you see property Nothing
returns Maybe<NotDefined>
. This version you should use when there is a possibility to implicitly conversion.
For example:
public Maybe<int> GetId()
{
// In this case you can treat it just like shortened version of NothingOf<int>.
return Nothing;
}
For "reference type" like Maybe<object>
, you can return a simply null.
public Maybe<object> GetId()
{
return null;
}
The NothingOf
you should use when you want to create a "Maybe" of a specific type which has status nothing. Status nothing means empty.
var val = NothingOf<int>;
The "Either" work in a similar way.
return Right("text"); // Returns Either<NotDefined, string>.
return Left("error"); // Returns Either<string, NotDefined>.
return EitherOf("error", beOrNotToBe) // Returns Either<string, string>.
So, when "either" represents two the same types, you should point which side you want to return Like that:
public Either<string, string> GetName()
{
return Right("Jhon");
}
When types are different, you can return just a value.
public Either<string, int> GetId()
{
return 69;
}
The monads give you an ability to use methods similar to "Select" "Where" like in LINQ, but the use case is different because we not operate on the collection of objects. Purpose of using the methods is to make operations on value or filter the value only when the value is available.
Let's take a look.
surname.Map(val => val.ToUpper());
surname.FatMap(Util.Upper); // Utis.Upper returns Maybe<string>
surname.Filter(val => val == "Mardok"); // Accept only surname "Mardok".
For "Maybe" type you can also use method "Or". When the surname is empty method "Or" will return "Maybe" type with "New name" like below.
surname.Or("New name");
You can also use Query LINQ sytax.
using Monads.Extensions.Linq;
var sresult =
from id in GetId()
from employe in GetEmploye(id)
from result in Payment.Payoff(employe, 2900)
where result == true
select result;
From Either to Maybe:
val.AsMaybe();
From Maybe to Either:
val.AsEither("Error");
Basic:
if(id.HasValue()) return id.Value; // Maybe
if(id.IsRight()) return id.Right; // Either
To match value use "Adjust" method or "Do" method for void operations.
var id = GetNumber()
.Adjust(val => val + 5, nothing: 69);
GetNumber()
.Do(
just: Console.WriteLine,
nothing: Console.WriteLine("Error"));
//Or
GetNumber().DoWhenHasValue(Console.WriteLine); // Maybe
GetNumber().DoWhenRight(Console.WriteLine); // Either
You can also use a more simple methods like ValueOr.
GetNumber().ValueOr(69);
GetNumber().RightOr(69);
GetNumber().ValueOrZero();
GetNumber().RightOrLeft(); //For Either<int,int>.
The unsafe way to retrieve value only on your own risk.
using Monads.Extensions.Unsafe;
GetNumber().Value(); // throw InvalidOperationException("Value can not be null.")
GetNumber().ValueOrDefault();
GetNumber().ToNullable(); // Returns for examle Nullable<int>.
GetNumber().ValueOrFail("Error message");// throw InvalidOperationException("Error message")
using Monads.Extensions.Linq;
items.ElementAtOrNoghing(5);
items.FirstOrLeft("Error");
// etc...
// Special methods
items.Values(); // returns existing values.
items.AggregateLefts(); // returns all lefts as string.
items.Rights();
items.Lefts();
// Query syntax
var sresult =
from id in GetId()
from employe in GetEmploye(id)
from result in Payment.Payoff(employe, 2900)
where result == true
select result;
using Monads.Extensions.Parsers;
var val = "25";
val.ParseToInt(); // returns Maybe<int>
val.ParseToInt(left: "Error"); // returns Either<string, int>
// etc...
using Monads.Extensions.Unsafe;
GetNumber().Value(); // throw InvalidOperationException
GetNumber().ValueOrDefault();
GetNumber().NullableValue();
GetNumber().ValueOrFail("Error message");
Optionaly you can use utils to parse string.
For example:
BoolParser.Parse("True"); // returns Maybe<bool>
BoolParser.Parse("True", AppStatus.Erro); // returns Either<AppStatus, bool>
//etc...
Now you can use regex in a more declarative way.
using Monads.Integrations.Regex;
var val = Regex.Match(input, pattern)
.Map(x => x.Value.ToUpper())
.ValueOr("Alternative string");
Instead off:
var match = Regex.Match(input, pattern);
string val;
if (match.Success)
{
val = match.Value.ToUpper();
}
else
{
val = "Alternative string"
}
For more check tests.