Lately I have
been playing around with functional programming and F# and I have been trying
to apply functional programming to C# as well. Even though C# is not a pure functional
programming language, by supporting delegates, lambda method syntax and
extension methods, we can produce code written in a functional style without
any problems.
But I will not
go into the details of explaining functional programing paradigm, but rather I
will concentrate on how by using a specific functional structure called monad
we can remove a lot of boilerplate code.
We all have seen
a code like this:
Example 1:
public void AddStudentToSchool(Guid schoolId, StudentDto studentDto)
{
using (ITransaction transaction = _transactionFactory.Create())
{
_securityService.Validate();
School school = _schoolRepository.GetSchoolById(schoolId);
if(school == null)
{
throw new ResourceNotFoundException("School does not
exist");
}
try
{
Student student = new Student(studentDto);
school.AddStudent(student);
}
catch(BusinessRuleException ex)
{
throw new ValidationException(ex.BrokenRules);
}
catch(InvalidStudentException ex)
{
throw new ValidationException(ex.BrokenRules);
}
}
}
Wouldn’t it be
more expressive and easier to read and understand the intention if we could write
the following instead:
Example 2:
public void AddStudentToSchool(Guid schoolId, StudentDto studentDto)
{
using (ITransaction transaction = _transactionFactory.Create())
{
_securityService.Validate();
School school = _schoolRepository.GetSchoolById(schoolId);
school.IfNullThrowResourceNotFound("School does not exist")
.IfNotNull(() =>
{
Student student = new Student(studentDto);
school.AddStudent(student);
})
.OnException<BusinessRuleException>(ex
=> throw new ValidationException(ex.BrokenRules))
.OnException<InvalidStudentException>(ex
=> throw new ValidationException(ex.BrokenRules));
}
}
Well, with the help of extension methods and by borrowing a
structure from functional programming called monad we can.
Wikipedia
defines the monad as following:
“In functional
programming, a monad is a structure
that represents computations defined
as sequences of steps: a type with
a monad structure defines what it means to chain operations, or nest functions of that type together. This allows the
programmer to build pipelines that
process data in steps, in which each action is decorated with additional processing rules provided by the
monad.”
From the
following paragraph, we are interested in the sentence “…This allows the
programmer to build pipelines that process data in
steps …” which is exactly what we want to achieve.
I won’t go into
the details of explaining monads, but in essence a monad is a pattern for doing
function composition with ‘amplified’ types (as Mike Hadlow puts it very
eloquently on his blog series about monads).
Going back to
our code in example 1, if we study the code in essence what we trying to do is:
1.
Get the school from the repository
2.
If school is null throw an exception
3.
If school is not null then
4.
Try to execute more code (by adding the student
to the school)
5.
If an exception is thrown, wrap that exception
into ValidationException
To convert the
above steps in chain of actions we need to make sure that the output of the
previous action will be the input of the next action on the chain. So maybe
monad (also known as Option in F#)
will be adequate.
Before we dive
in with writing code, let’s look at what do we need to implement for our type
to be monad. Definition in the Wikipedia states the following:
For a type to be
regarded a monad, it needs to have a type constructor M and two operations,
bind and return:
·
The return operation
takes a value from a plain type and puts it into a monadic container using the
constructor, creating a monadic value.
·
The bind operation
takes as its arguments a monadic value and a function from a plain type to a
monadic value, and returns a new monadic value.
Maybe
monad is very similar to Nullable<T> type in C#, except our maybe type will be represented by two different
types. The Some type which will indicate a presence of a value and None type which
will indicate an absence of a value. We will implement our maybe type as an
interface and will call it IOptional. So the implementation looks like:
public interface IOptional<T>
{
}
The None type is implemented as :
public class None<T> : IOptional<T>
{
public override string ToString()
{
return "None";
}
}
And Some type is implemented as:
public class Some<T> : IOptional<T>
{
public Some(T value)
{
Value = value;
}
public T Value { get; private set; }
public override string ToString()
{
return Value.ToString();
}
}
Now, to convert
our IOptional<T> into a monad we need to implement the Return and Bind
methods. We will be implementing them as extension methods. So the
implementation looks like:
public static class OptionalExtensions
{
public static IOptional<T> ToOptional<T>(this T value) where T : class
{
if (value == null)
{
return new None<T>();
}
return new Some<T>(value);
}
public static IOptional<T2> Bind<T1, T2>(this IOptional<T1> a, Func<T1, IOptional<T2>> func)
{
var val = a as Some<T1>;
return val != null
? func(val.Value)
: new None<T2>();
}
}
Now our IOptional<T> type is monad. The next step is to start building the
methods that we call chain them together. All the methods will be implemented
as extension methods also.
The first one is
throw if null. So the implementation looks like:
public static IOptional<T> IfNullThrowResourceNotFound<T>(this IOptional<T> value, string message)
where T : class
{
Some<T> some = value as Some<T>;
if (some == null)
{
throw new ResourceNotFoundException(message);
}
return value;
}
The next
implementation is to try and execute an action if the value is not null. So the
implementation looks like:
public static IOptional<Action> IfNotNull<T>(this IOptional<T> value, Action action)
where T : class
{
return value.Bind(t => new Some<Action>(action));
}
Next we will
implement OnException step. The implementation looks like:
public static IOptional<Action> OnException<E>(this IOptional<Action> value, Action<E> action)
where E : Exception
{
return value.Bind(t =>
{
return new Some<Action>(() =>
{
try
{
t();
}
catch (E ex)
{
action(ex);
}
});
});
}
And the last
step is the Execute, which executes the code
public static void Execute(this IOptional<Action> value)
{
value.Bind(t =>
{
t();
return new None<bool>();
});
}
Once we have
finished implementing the extension methods, our code from example 1 looks
like:
School school = _schoolRepository.GetSchoolById(schoolId);
school
.ToOptional()
.IfNullThrowResourceNotFound("School does not exist")
.IfNotNull(() =>
{
Student student = new Student(studentDto);
school.AddStudent(student);
})
.OnException<BusinessRuleException>(ex
=> { throw new ValidationException(ex.BrokenRules);
})
.OnException<InvalidStudentException>(ex
=> { throw new ValidationException(ex.BrokenRules);
})
.Execute();
As you can see,
now our code looks a lot neater and easier to read without any boilerplate
code. I hope you find this useful.
No comments:
Post a Comment