Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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?

share|improve this question
    
+1 for the effort, but I wonder if maybe the non-standard names will be confusing to people familiar with monads? For instance, it looks like your Select is often called map and SelectMany is often called bind. (Not a criticism, just an observation) –  Matt Fenwick Dec 3 '13 at 14:52
3  
@MattFenwick: You are completely correct, however much of LINQ is inspired by monads and C# uses the terms Select and SelectMany instead of map and bind. It does this faithfully enough that anybody writing monadic code in C# should be quite comfortable using Select/SelectMany. –  Phoshi Dec 3 '13 at 15:00
6  
I question whether monad transformers are a good idea in C#. For one, they already are semi-unpleasant to use in Haskell (Many people want a better system for combining effects) and two, the reason why they don't suck is because of the typeclass-y prolog we do to make it so that operations like get, put, and tell automagically propogate up a monad stack, this isn't possible in C#. You'd end up with the dreaded lift(lift(lift(foo))) –  jozefg Dec 3 '13 at 15:06
    
Just a note, your validation type can be generalized to the Either type, or disjoint union. –  Matt H Dec 3 '13 at 20:29
    
@jozefg Are you saying that because you have type classes in Haskell / Scala you do not need to manually convert like how I do with .ToTValidationT()? Secondly I am unsure what you mean by the operations get / put and lift? Can you further explain? –  Blair Davidson Dec 4 '13 at 0:59
show 1 more comment

migrated from programmers.stackexchange.com Dec 5 '13 at 14:41

This question came from our site for professional programmers interested in conceptual questions about software development.

1 Answer

There isn't much to review here. However I did find this:

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>();
    }
}

Here I find the condition would be best written like this:

if (id < 0)
{
    var exception = new Exception("Customer Id is less than zero");
    return new None<Customer>().ToValidationT<Exception>(exception);
}
else
{
    return new Just<Customer>(new Customer("Structerre")).ToValidationT<Exception>();
}

And as I typed this I noticed you were instantiating System.Exception. You should be using a more specific exception here, InvalidArgumentException seems appropriate - throwing System.Exception is bad.

Also the CustomerService is tied to the Console, you may prefer injecting some IServiceOutputProvider which could be implemented with console output, but just as well with a MessageBox output - basically CustomerService has too many responsibilities here, writing to the console has nothing to do with logging results or sending an email.

share|improve this answer
    
I don't agree with using if-else and bracers and stuff here, but throwing a more specific exception is good advice. +1 –  Simon André Forsberg Dec 19 '13 at 23:38
    
@SimonAndréForsberg instantiating the exception on the same line as instantiating the None<T> and calling the .ToValidationT<T> is a little too much to put on a single line. It deserves a scope. Thus a proper else block. –  Mat's Mug Dec 19 '13 at 23:43
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.