I am working on using monad transformers in C#. I would like to know if the following code I present, shows that I have understood this. I am fairly new to this so any feedback / comments are really welcome. This example is just for wrapping a maybe monad in a validation monad.
using System;
using NUnit.Framework;
namespace Monads
{
public static class MaybeExtensions
{
public static IMaybe<T> ToMaybe<T>(this T value)
{
if (value == null)
return new None<T>();
return new Just<T>(value);
}
}
public interface IMaybe<T>
{
IMaybe<U> Select<U>(Func<T, U> f);
IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f);
U Fold<U>(Func<U> error, Func<T,U> success);
}
public class Just<T> : IMaybe<T>
{
public Just(T value)
{
this.value = value;
}
public IMaybe<U> Select<U>(Func<T, U> f)
{
return f(value).ToMaybe();
}
public IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f)
{
return f(value);
}
public U Fold<U>(Func<U> error, Func<T,U> success)
{
return success(value);
}
public IValidation<U,T> ToValidationT<U>()
{
return new ValidationMaybeT<U,T>(this, default(U));
}
private readonly T value;
}
public class None<T> : IMaybe<T>
{
public IMaybe<U> Select<U>(Func<T, U> f)
{
return new None<U>();
}
public IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f)
{
return new None<U>();
}
public U Fold<U>(Func<U> error, Func<T,U> success)
{
return error();
}
public IValidation<U,T> ToValidationT<U>(U exceptionalValue)
{
return new ValidationMaybeT<U,T>(this, exceptionalValue);
}
}
public class Customer
{
public Customer(string name)
{
Name = name;
}
public string Name{ get; set; }
}
public interface IValidation<T,U>
{
IValidation<T,V> Select<V>(Func<U,V> f);
IValidation<T,V> SelectMany<V>(Func<U,IValidation<T,V>> f);
}
public class ValidationError<T,U> : IValidation<T,U>
{
public ValidationError(T error)
{
Error = error;
}
public IValidation<T, V> Select<V>(Func<U, V> f)
{
return new ValidationError<T, V>(Error);
}
public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
{
return new ValidationError<T, V>(Error);
}
public T Error{ get; private set; }
}
public class ValidationSuccess<T,U> : IValidation<T,U>
{
public ValidationSuccess(U value)
{
Result = value;
}
public IValidation<T, V> Select<V>(Func<U, V> f)
{
return new ValidationSuccess<T, V>(f(Result));
}
public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
{
return f(Result);
}
public U Result{ get; private set; }
}
public class ValidationMaybeT<T,U> : IValidation<T,U>
{
public ValidationMaybeT(IMaybe<U> value, T error)
{
Value = value;
Error = error;
}
public IValidation<T, V> Select<V>(Func<U, V> f)
{
return Value.Fold<IValidation<T, V>>(() => new ValidationError<T,V>(Error), s => new ValidationSuccess<T,V>(f(s)));
}
ValidationError<T, V> SelectManyError<V>()
{
return new ValidationError<T, V>(Error);
}
public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
{
return Value.Fold(() => SelectManyError <V>(), s => f(s));
}
public IMaybe<U> Value{ get; private set; }
public T Error{ get; private set; }
}
public interface ICustomerRepository
{
IValidation<Exception,Customer> GetById(int id);
}
public class CustomerRepository : ICustomerRepository
{
public IValidation<Exception,Customer> GetById(int id)
{
if (id < 0)
return new None<Customer>().ToValidationT<Exception>(new Exception("Customer Id less than zero"));
return new Just<Customer>(new Customer("Structerre")).ToValidationT<Exception>();
}
}
public interface ICustomerService
{
void Delete(int id);
}
public class CustomerService : ICustomerService
{
public CustomerService(ICustomerRepository customerRepository)
{
this.customerRepository = customerRepository;
}
public void Delete(int id)
{
customerRepository.GetById(id)
.SelectMany(x => SendEmail(x).SelectMany(y => LogResult(y)));
}
public IValidation<Exception,Customer> LogResult(Customer c)
{
Console.WriteLine("Deleting: " + c.Name);
return new ValidationSuccess<Exception,Customer>(c);
//return new ValidationError<Exception, Customer>(new Exception("Unable write log"));
}
private IValidation<Exception,Customer> SendEmail(Customer c)
{
Console.WriteLine("Emailing: " + c.Name);
return new ValidationSuccess<Exception,Customer>(c);
}
ICustomerRepository customerRepository;
}
[TestFixture]
public class MonadTests
{
[Test]
public void Testing_With_Maybe_Monad()
{
new CustomerService(new CustomerRepository()).Delete(-1);
}
}
}
Another smaller sub question is if C# had higher kinded types could I just implement this class once (ValidationT) and it work for all other wrapped monads or is this incorrect?
Select
is often calledmap
andSelectMany
is often calledbind
. (Not a criticism, just an observation) \$\endgroup\$get
,put
, andtell
automagically propogate up a monad stack, this isn't possible in C#. You'd end up with the dreadedlift(lift(lift(foo)))
\$\endgroup\$