Bogoware.Monads
0.1.2
See the version list below for details.
dotnet add package Bogoware.Monads --version 0.1.2
NuGet\Install-Package Bogoware.Monads -Version 0.1.2
<PackageReference Include="Bogoware.Monads" Version="0.1.2" />
paket add Bogoware.Monads --version 0.1.2
#r "nuget: Bogoware.Monads, 0.1.2"
// Install Bogoware.Monads as a Cake Addin #addin nuget:?package=Bogoware.Monads&version=0.1.2 // Install Bogoware.Monads as a Cake Tool #tool nuget:?package=Bogoware.Monads&version=0.1.2
Bogoware Monads
Yet another functional library for C#
Monads: quick introduction
Monads are a powerful tool to model operations in a functional way and it's not a coincidence that they are the cornerstone of functional programming. It's not our mission to explain what monads are and how they works:, there are plenty of resources on the web that face the question from different persepectives.
For the objective of this introduction let's say taht they can be considered as a sort of safe container that encapsulate the result of an operation and provides method methods that allow to manipulate the result in a safe way, ensuring that the operation will be executed only if it is fine.
This contract is enough to shield code from performing any further processing in case of errors or missing data.
The benefit of adopting a functional approach is that it allows to model operations in a way that is more readable and easier to reason about, moreover it allows to write code that is more robust and less prone to errors.
C# functional challenges
C# has a good support to functional programming but there are some limitations that imply challenging design descisions.
Bogoware Monads
This library provides the well knows Result
and Maybe
monads (also known as Either
, Optional
, Option
in
other contexts):
The
Result<T>
monad is used to model operations that can fail.
The
Maybe<T>
monad is used to model operations that can return a value or not.
Moreover the library provides the Error
abstract class that complements the Result<T, E>
monad to
provide an ergonimic approach to error management at application-wide scale.
Result<T>
design goals
The Result<T>
monad is used to model operations that can fail or return a value.
The Result<T>
monad is a generic type where T
is the type of the value returned by the operation uppon success.
Result<T>
provides a set of methods that allow to chain operations in a functional way:
Map
allows to transform the value returned by the operation, thus modelliing the happy flowBind
allows to chain operations that return aResult<T>
.Match
allows to handle the result of the operation.RecoverWith
allows to recover from an error by returning aResult<T>
Ensure
allow to assert a condition on the value returned by the operationExecuteIfSuccess
allows to execute an action if the operation succeedsExecuteIfFailure
allows to execute an action if the operation fails
There are also some unsafe methods intended to switch to the procedural way. They are intended to support developers that aren't familiar with the functional approach and may need to switch to the procedural way to get things done.
These methods should be avoided as much as possible because they break the functional approach and make the code less robust and exposed to unwanted exceptions:
GetValueOrThrow
allows to extract the value from theResult<T>
monad.GetErrorOrThrow
allows to extract the error from theResult<T>
monad.
The benefit of sticking to the Result<T>
monad is that it allows to model operations in a way that is more
readable and easier to reason about, moreover it allows to write code that is more robust and less prone to errors.
Error
design goals
The Error
class is used to model errors and work inconjunction with the Result<T>
monad.
There are two types of errors:
LogicError
s: these errors are caused by the application logic and should be handled programmatically. For example:InvalidEmailError
,InvalidPasswordError
,InvalidUsernameError
, etc.RuntimeError
s: these errors are caused by external sources and are not related to domain logic. For example:DatabaseError
,NetworkError
,FileSystemError
, etc.
Distinguishing between LogicError
s and RuntimeError
s is important because it allows to handle them differently.
LogicError
s should be handled programmatically and can be safely reported to the user in case of malformed request- while
RuntimeError
s should be handled by the infrastructure and aren't meant to be reported to the user.
A typical Asp.Net Core application should handle LogicError
s by returning a BadRequest
response to the client
and RuntimeError
s by returning an InternalServerError
response to the client for example.
Error
hierarchy: best practices
Every application should model its own logic errors by deriving from the LogicError
class a root class
that represents the base class for all logic errors.
From this root class the application should derive a class for the different kinds of logic errors that can occur. Each class should model a specific logic error and provide the necessary properties to describe the error.
In the following example we model two logic errors: NotFoundError
and InvalidOperationError
:
public abstract class ApplicationError: LogicError
{
public int ErrorCode { get; }
protected ApplicationError(string message, int errorCode)
: base(message)
{
ErrorCode = errorCode;
}
}
public class NotFoundError : ApplicationError
{
public string ResourceName { get; }
public string ResourceId { get; }
public NotFoundError(string message, int errorCode, string resourceName, string resourceId)
: base(message, errorCode)
{
ResourceName = resourceName;
ResourceId = resourceId;
}
}
public class InvalidOperationError : ApplicationError
{
public string OperationName { get; }
public string Reason { get; }
public InvalidOperationError(string message, int errorCode, string operationName, string reason)
: base(message, errorCode)
{
OperationName = operationName;
Reason = reason;
}
}
As shown in the project FluentValidationSample the FluentValidation
library
can be used to model validation errors.
In contrast to LogicError
s, RuntimeError
s are generated by the Result.Execute()
methods to encapsulate exceptions
thrown by the application.
Maybe<T>
design goals
Before stating what is intended to be achieved with the Maybe
monad, let's clarify that it's not intended to be used as a replacement for Nullable<T>
essentailly because some fundamental libraries, such as Entity Framework, rely on Nullable<T>
to model class attributes and the support to structural types is still limited. A more pragmatic approach is to use Nullable<T>
to model class attributes and Maybe<T>
to model return values and or method paramethers.
The benefit of using Maybe
over Nullable<T>
is that Maybe
provides a set of methods that allow to chain operations in a functional way. This becomes very useful when dealing with operations that can return a value or not, like when querying a database.
The presence of an implicit conversion from Nullable<T>
to Maybe<T>
allows to lift up Nullable<T>
values to Maybe<T>
values and use the Maybe<T>
methods to chain operations.
Practical rule: use
Nullable<T>
to model class attributes andMaybe<T>
to model return values and or method paramethers. .
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net7.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
9.0.9 | 433 | 9/12/2024 | |
9.0.9-alpha.0.1 | 46 | 9/12/2024 | |
9.0.7 | 86 | 7/30/2024 | |
9.0.5 | 382 | 2/15/2024 | |
9.0.4 | 132 | 2/12/2024 | |
9.0.3 | 101 | 2/12/2024 | |
9.0.2 | 110 | 2/12/2024 | |
9.0.1 | 118 | 2/12/2024 | |
9.0.0 | 116 | 2/12/2024 | |
8.0.2 | 114 | 1/31/2024 | |
8.0.1 | 95 | 1/31/2024 | |
8.0.0 | 217 | 12/12/2023 | |
0.2.1 | 127 | 12/12/2023 | |
0.2.0 | 500 | 9/21/2023 | |
0.1.19 | 522 | 7/6/2023 | |
0.1.18 | 210 | 7/5/2023 | |
0.1.17 | 194 | 7/5/2023 | |
0.1.16 | 220 | 7/1/2023 | |
0.1.15 | 217 | 6/30/2023 | |
0.1.14 | 241 | 6/30/2023 | |
0.1.13 | 234 | 6/28/2023 | |
0.1.12 | 204 | 6/28/2023 | |
0.1.11 | 230 | 6/27/2023 | |
0.1.10 | 220 | 6/27/2023 | |
0.1.9 | 212 | 6/27/2023 | |
0.1.8 | 202 | 6/27/2023 | |
0.1.7 | 210 | 6/27/2023 | |
0.1.6 | 200 | 6/26/2023 | |
0.1.5 | 205 | 6/26/2023 | |
0.1.4 | 215 | 6/24/2023 | |
0.1.3 | 191 | 6/24/2023 | |
0.1.2 | 202 | 6/23/2023 | |
0.1.1 | 208 | 6/23/2023 | |
0.1.0 | 208 | 6/23/2023 | |
0.0.3 | 210 | 6/23/2023 | |
0.0.3-alpha.0.30 | 78 | 4/27/2023 | |
0.0.3-alpha.0.25 | 79 | 4/25/2023 | |
0.0.3-alpha.0.23 | 85 | 4/24/2023 | |
0.0.3-alpha.0.10 | 81 | 4/19/2023 | |
0.0.3-alpha.0.9 | 77 | 4/19/2023 | |
0.0.3-alpha.0.3 | 84 | 4/18/2023 |