Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#

Monad like programming with C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
18 Sep 2013CPOL10 min read 38.4K   207   18   6
A haskell monad/(applicative)functor like interface in C# that extends IEnumerable.

Introduction 

A few months ago I had lesson about function programming in Haskell. Since then I was getting more and more into the function way of thinking. Currently im programming C# alot (Private, at work). And I started to use the functional things like lambdas and delegates more and more.   

I was wondering if I can get something like the Monads and (Applicative)Functors in Haskell. And I simply wanted to play around with C#, functional programming and generics. I ended up with some realy cool stuff I would like to share.  

The things I found at the web when I was googling about C# and monads where about functional programming some isolated Maybe or Identity implementations or some extensions of IEnumerable. But I had something more general in mind.

At the beginning I didn't know much about IEnumerable and the implementation details. I only knew Linq, and that it was established by a (legendary) haskell´er with ideas about monads in mind.

But still I found some usefull or at least complex function definitions that are not in the IEnumerable interface. And I connected all Monads to the IEnumerable and LINQ syntax. (for..in..., where, select). 

Basically in the IEnumerable interface, there are only functions that take one function as an argument. There is no function that takes a List with functions for example. Maybe that was not the intention of IEnumerable when they made it. Or it is not really needed because normally someone uses the Linq syntax (for..in..) and doesnt call the functions directly.  

The complete project with more detailed descriptions can be found under github: 

https://github.com/Muraad/Monad-CSharp   

What you will see? 

A lot of generics and delegates/Func/lambdas. Implementing IEnumerable and make connection to LINQ. And operator overloading.     

What I have done:

I´v defined an interface IMonad that extends IEnumerable, and adds some usefull and complex function over monads. Functions I dont find in the IEnumerable.

And the big difference is IEnumerable is normally only for list like types. And not for types with a single value inside.

So I've defined that interface and implemented a Maybe, Identity, ListMonad and Either monad.  

Background  

What is a Monad and a (Applicative)Functor? 

Basically its all the same. It's like a box that can have a value ore more inside. And things can be done with this value. For example a function can be mapped over it. It can be zipped together with the value(s) inside another box. The list (or better IEnumerable) is the best example for a monad, because everybody know this is a type (a box) that can have other values inside (this box). And you can define functions for a list without  to know something about the types inside the list. 

What is a Maybe?  

A Maybe<T> is a class (box) that can have one value of type T inside or it can be nothing. Its like the nullable in C#.   

Using the code

I made an IMonad<A> interface and some implementations of it. A Maybe<A>, Identity<A>, ListMonad<A> and an Either<R, L>.  

I will explain only the IMonad here, and give some examples how to use the Maybe and ListMonad classes.  

IMonad Interface 

C#
public interface IMonad<A> : IEnumerable<A>
{
 
    #region IMonad_Core_Interface_Function_Definitions
 
    IMonad<B> Fmap<B>(Func<A, B> function);
 
    IMonad<A> Pure(A parameter);
 
    A Return();
 
    IMonad<B> App<B>(IMonad<Func<A, B>> functionMonad);
    IMonad<B> App<B>(IMonad<Func<A, IMonad<B>>> functionMonad);
 
    IMonad<C> Com<B, C>(Func<A, B, C> function, IMonad<B> mOther);
    IMonad<C> Com<B, C>(Func<A, B, IMonad<C>> function, IMonad<B> mOther);
    IMonad<C> Com<B, C>(IMonad<Func<A, B, 
                    C>> functionMonad, IMonad<B> mOther);
    IMonad<C> Com<B, C>(IMonad<Func<A, B, 
                    IMonad<C>>> functionMonad, IMonad<B> mOther);
 
    IMonad<A> Visit(Action<A> function);
    IMonad<A> Visit<B>(Action<A, B> action, IMonad<B> mOther);
 
    IMonad<A> Concat(IMonad<A> otherMonad);
 
    #endregion
 
      
    #region Linq_Enumerable_Connection
 
    IMonad<A> Where(Func<A, bool> predicate);   // filter.
    IMonad<A> Where(Func<A, int, bool> predicate);
 
    IMonad<B> Select<B>(Func<A, B> f);       // fmap
    IMonad<B> Select<B>(Func<A, Int32, B> f);   // fmap with index.

    IMonad<B> SelectMany<B>(Func<A, IMonad<B>> f);
    IMonad<B> SelectMany<B>(Func<A, Int32, IMonad<B>> f);
    IMonad<B> SelectMany<TMonad, B>(Func<A, IMonad<TMonad>> selector, 
                                    Func<A, TMonad, B> function);
    IMonad<B> SelectMany<TMonad, B>(Func<A, Int32, IMonad<TMonad>> selector, 
                                    Func<A, TMonad, B> function);
 
    #endregion
}

IMonad extends IEnumerable, that means every monad is an enumerable even if it holds only one value. I found out that is is very usefull for example for the list monad App and Com functions. Because then foreach can be used on the other monad no matter if it holds only one value or more. The IMonad interface also defines LINQ functions, that every Monad has to implement. When I was playing around and doing some research I recognized that IEnumerable is almost a Monad. But because an Applicative Functor is a monad like type too, I saw that there is no "App" like function defined in IEnumerable. And IEnumerable normaly is for types like collections that holds one ore MORE values. So my IMonad extends IEnumerable. Onyl difference is that some function definitions are "overridden", that they take IMonad´s as arguments and return values instead of IEnumerable´s.  

With the Haskell type classes in front of me, I was starting to design this interface. There were a few changes over time. For example the IEnumerable extension was not there from the beginning on. 

Function explanations: 

Pure 

C#
IMonad<A> Pure(A parameter);  

This function simply takes a value and puts it in the minimal context of the (new created) monad. 

Return 

C#
A Return(); 

Returns the value inside this monad. A problem here is the ListMonad. Because it holds more than one values of type A. The workaround I do is to return the head of the internal list. Another problem here is with Maybe. What to do if someone ask a maybe for its value inside and it is nothing? As a workaround I return the default value of the type inside the maybe. For example for Nothing<int> the Return() function will return zero. Either is also a problem here. My Either implementation is basically a decorator and implemented like                   Either<R,L> : Identity<L>, IMonad<R>. For the left value functions are delegated to the base Identity class. The IMonad<R> is implemented in Either and is the same as the Identity implementation. Based on the input arguments and the generic parameters C# can choose the right function for every function defined in IMonad except for Return().   

Fmap 

C#
IMonad<B> Fmap<B>(Func<A, B> function); 

Basically fmap is the same as the simple Select from IEnumerable. It maps a function over the value(s) inside this monad. The result is packed from fmap into a new monad that is returned. I was starting to write the IMonad interace before I was getting more into LINQ and recognized that there is alread the Select. But I let it inside the interface because I think fmap is a better and more understable name here. In Haskell its called fmap too, and is defined in the functor typeclass. 

Fmap 2nd 

C#
IMonad<B> Fmap<B>(Func<A, IMonad<B>> function); 

Same as Fmap above. Difference is that the function returns a new monad. 

App 

C#
IMonad<B> App<B>(IMonad<Func<A, B>> functionMonad);  

This is an interesting function definition. It comes from the Haskell Applicative typeclass. Thats the function I dont find in IEnumerable. It is like the fmap function, the only difference is that the function that is applied to the value(s) inside the monad, is also inside another monad. So you can have a List(Monad) with functions and apply them to all values in another list(monad) with one line. Or you can have a function that is packed in a maybe and apply them to the value(s) inside of this monad. The result of applying the function(s) to the value(s) inside the monad is packed into a new monad that is returned. 

App 2nd 

C#
IMonad<B> App<B>(IMonad<Func<A, IMonad<B>>> functionMonad); 

The second version of the App function (like its defined in Haskell) is even more interesting. Here the function(s) inside the given monad also returns a monad. So the result is flattened out if there are more than one result values. If the function returns a list(monad) of type B and this monad is a list(monad) of type A then the result type is also a list(monad) of type B and not a list(monad) of list(monad) of type B. The inner monad is always flattened out. This function is something special. It can "break out" of the monad type that function is called from. That means App on a Maybe can return a ListMonad for example. At first it seems there is a problem. How to concatenate the values inside the function result monads of type B together? If you are inside aListMonad that would be no problem. But what about a Maybe that can only store one value? The trick here is to use the firstIMonad that is returned from the first function. All other values inside the next returned monads are concatenated together. Thats why there is a Concat function in the IMonad interface. This is a pattern I will use in all functions, that work with new monads as internal function results. What Concat means for a individual monad is self defined. For a ListMonad it is what someone would expect. For a Maybe for example, I simply return a new Maybe with the value inside that is returned from the Return() function from the given Monad. 

Com 

C#
IMonad<C> ZipWith<B, C>(Func<A, B, C> function, IMonad<B> mOther); 

Put every possible combination of the values inside this ListMonad and the other given IMonad as arguments inside the given function. All function results are put into a new monad. 

Com 2nd 

C#
IMonad<C> Com<B, C>(Func<A, B, IMonad<C>> function, IMonad<B> mOther); 

Same as above only difference is that the function itselfs return IMonad. So the result IMonad is flattned out, and every value inside of it is added to the new result monad. 

Com 3d 

C#
IMonad<C> Com<B, C>(IMonad<Func<A, B, C>> functionMonad, IMonad<B> mOther);

Same as above, where the given function(s) is(are) inside a IMonad. 

Com 4th 

C#
IMonad<C> Com<B, C>(IMonad<Func<A, B, IMonad<C>>> functionMonad, IMonad<B> mOther);

Same as Com 2d only that the functions inside the monad returns a monad them self. The same procedere as used in App is used to produce the result monad. This is the most complex function. 

Visit 

C#
IMonad<A> Visit(Action<A> function);

Apply the given Action to the value(s) inside of this monad. During my testing I found that function very usefull. For example you can apply a lambda with Console.Out.Write... over the value(s) inside the monad. 

Visit 2nd 

C#
IMonad<A> Visit<B>(Action<A, B> action, IMonad<B> mOther);

Visit each combination of the values inside this monad and the other given monad. 

Concat 

C#
IMonad<A> Concat(IMonad<A> otherMonad); 

Concatenates two monads together. There is no general defenition here what concat means for a concrete monad. 

The rest of the function definitions are for the connection to LINQ. That every monad can be used with Linq. What would already possible because IMonad extends IEnumerable. And as long as every monad has a Enumerator, there could be Linq querys on it. But I want that the Linq functions return and work with IMonad´s. 

Linq connection   

Same defintions like in IEnumerable only IEnumerable is replaced with IMonad. So Linq and Monad functions can be mixed up. 

Something to mention here is the case where a Linq function takes a function as argument that uses an index of the current value inside the IEnumerable. For single value monads like Maybe or Identity the index is always zero. 

Single and multi value monads 

There are monads that hold only one value (single enumerable) like maybe and identity, and there are monads that hold one or more like a list monad (or every normal enumerable or a collection). 

Breaking out the the current monad type 

Ever function where a Func<...,IMonad<..>> is involved, has the ability to "break out" of the current monad type, that means the result monad type can be different than the monad where Func was used. All other functions return a monad of the same type. 

Convention 

No monad knows another monad. They all only know IMonad and them self. So there is no special handling between different combination of monads. To change the result monad even if its unknow, a trick described at function App 2nd is used. In short theIMonad that is returned from the first given function is used to concatenate with the next function results.  

 

Maybe playground

C#
public static void MaybePlayaround()
{
    // Just 5, use implicit operator for *Maybe* to make a Just directly.
    Maybe<int> justInt = 5;
    Console.WriteLine("A new Just<double>: " + justInt.ToString());
    Maybe<int> nothingInt = 0;      // same as nothingInt = new Nothing<int>();
    Console.WriteLine("A new Nothing<double>: " + nothingInt.ToString());
 
    // justInt = 0; or justInt = new Nothing<int>() would make a Nothing out of the justInt

    Console.WriteLine("A new ListMonad<char>: ");
    var listMonadChar = new ListMonad<char>() { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }
                        .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    Console.WriteLine("A new ListMonad<int>: ");
    var listMonadInt = new ListMonad<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
                        .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    Console.WriteLine("A new ListMonad<double>: ");
    var listMonadDouble = new ListMonad<double>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
                            .Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
 
    var intToDoubleFunctin = new Func<int, double>(x => { return x * 0.5; });
    Console.WriteLine("A new Just with a function inside: f(x) = x * 0.5 ");
    var justFunction = new Just<Func<int, double>>(intToDoubleFunctin);
    Console.WriteLine(justFunction.ToString());
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Visits each combination of the Just 5 and the ListMonad<char>" + 
                        "using a lambda and Console.Write inside: ");
    justInt.Visit((i, c) => { Console.Out.Write(i + "" + c + ", "); }, listMonadChar);
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // Outputs: 1a, 1b, 1c, 1d, 1e,

    Console.WriteLine("Same with Nothing<int> will output nothing: ");
    nothingInt.Visit((i, c) => { Console.Out.Write(i + "" + c + ", "); }, listMonadChar);
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Visits each combination of the Just 5 and the ListMonad<int> \n" +
                        "using a lambda and Console.Write inside. Add both values in print out: ");
    justInt.Visit((x, y) => { Console.Out.Write( x + y + ", "); }, listMonadInt);
 
    Console.WriteLine("\nSame with Nothing<int>:");
    nothingInt = (Maybe<int>)nothingInt
                    .Visit((x, y) => { Console.Out.Write(x + y + ", "); }, listMonadInt);
    Console.WriteLine(nothingInt.ToString());
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
 
    Console.Write("Fmap f(x) = x * 0.5 over the Just<int>(5): ");
    var justDouble = justInt.Fmap(intToDoubleFunctin).Visit((x) => { Console.Out.Write(x + "\n"); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("App Just<Func>(f(x) = x * 0.5) over the Just<int>(5): ");
    justDouble = justInt.App(justFunction).Visit((x) => { Console.Out.Write(x + "\n"); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("App Just<Func> over the Just<int>(5), \n where the functions returns a new " + 
                    "ListMonad<int>() \n with two times the value inside the Just 5. Output: ");
    var function = new Just<Func<int, IMonad<int>>>((x) => { return new ListMonad<int>(){x, x};});
    var intListMonad = justInt.App(function).Visit( (x) => { Console.Out.Write(x + ", "); } );
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // The result is a ListMonad<int>
    // Output: 5, 5,

    Console.WriteLine("Create a new ListMonad with" + 
       " Func<int, int, double> (x*y, x/y, x%y) inside.");
    Console.WriteLine("Combinate Just 5 and that functions. Result is Just<int>.");
    Console.WriteLine("Only last value is returned because this" + 
       " Com function cannot break out of the Just.");
    Console.WriteLine();
    var functionListMonad = new ListMonad<Func<int, int, double>>();
    functionListMonad.Add( (x, y) => { return x*y;});
    functionListMonad.Add( (x, y) => { return x/ (y==0 ? 1 : y);});
    functionListMonad.Add((x, y) => { return x % (y == 0 ? 1 : y); });
    functionListMonad.Visit((x) => { Console.Out.WriteLine("Func: " + x); });
    var result = justInt.Com(functionListMonad, listMonadInt).Visit((x) => 
        { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
    // Output: 5

    Console.WriteLine("Create a new ListMonad with \n" +
             "Func<int, int, IMonad<double>> (x+y, x-y, x*y, x/y, x%y) inside.\n" +
             "Where every function packs the result in a new ListMonad<double>.\n" +
             "Combine the Just 5 and the ListMonad<double> with all the functions.\n" +
             "The result ListMonad´s are flattned out, and only one result ListMonad<double> "+
             " with all result values is returned: ");
    Console.WriteLine();
    var functionListMonadTwo = new ListMonad<Func<int, double, IMonad<double>>>();
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>() { x + y }; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>() { x - y }; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x * y}; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x / (y == 0 ? 1 : y)}; });
    functionListMonadTwo.Add((x, y) => { return new ListMonad<double>(){x % (y == 0 ? 1 : y)}; });
    functionListMonadTwo.Add((x, y) => { return new Nothing<double>(); });
    var resultTwo = justInt.Com(functionListMonadTwo, listMonadDouble)
                    .Visit((x) => { Console.Out.Write(x + ", "); });
    // Output: 5*0, 5*1, 5*2,... 5*1, 5/1, 5/2, 5/3, ... 5%1, 5%1, 5%2, 5%3,....
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Do the same with the Nothing<int>: ");
    resultTwo = nothingInt.Com(functionListMonadTwo, listMonadDouble)
                .Visit((x) => { Console.Out.Write(x + ", "); });
    // Output: 5*0, 5*1, 5*2,... 5*1, 5/1, 5/2, 5/3, ... 5%1, 5%1, 5%2, 5%3,....
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("Comb. J(5) and the ListMonad<int> with one function ( f(x,y) = x+y ): ");
    var resultThree = justInt.Com((x, y) => { return x + y; }, intListMonad)
                        .Visit((x) => { Console.Out.Write(x); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.Write("Map a f(x, y) = x*y over the Just 5 and a new Just<int>(10) using LINQ: ");
    var query = from f in new Just<Func<int, int, int>>((x, y) => { return x * y; })
                from x in justInt
                from y in new Just<int>(10)
                select f(x, y);
 
    query.Visit((x) => { Console.Out.Write(x + ", "); });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
}  

 Image 1

Image 2

ListMonad playground

C#
public static void ListMonadPlayground()
{
    Console.Out.WriteLine("Create two lists [1..5] and [J(1)..(J5)]: ");
    ListMonad<int> listMonadInt = new ListMonad<int>() 
                                    { 1, 2, 3, 4, 5 };
 
    ListMonad<double> listMonadDouble = new ListMonad<double>()
                                        {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
 
    // Because Maybe class has an implicit operator
    // it can be written very cool and easy like a normal list.
    ListMonad<Maybe<int>> listMonadMaybeInt = new ListMonad<Maybe<int>>() 
                                                { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 
    // Functions for Fmap and first App function.
    Func<int, double> intDoubleFunc1 = (x) => { return 0.5 * (double)x; };
    Func<int, double> intDoubleFunc2 = (x) => { return 0.7 * (double)x; };
 
    Console.WriteLine("Fmap f(x) = 0.5 * x over [1,..5,]");
    listMonadInt.Fmap(intDoubleFunc1).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("App [f(x)=0.5*x, f(x)=0.7*x] over [1,..,5]");
    var listMonadintDoubleFunc = 
      new ListMonad<Func<int, double>>(){ intDoubleFunc1, intDoubleFunc2 };
    listMonadInt.App(listMonadintDoubleFunc).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for second App function.
    Func<int, IMonad<double>> intIMonadIntDoubleFunc1 = 
                                (x) => { return new Just<double>(x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc2 = 
                                (x) => { return new Just<double>(x * x *x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc3 = 
                                (x) => { return new Just<double>(x * x * x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc4 = 
                                (x) => { return new Just<double>(x * x * x * x * x); };
    Func<int, IMonad<double>> intIMonadIntDoubleFunc5 = 
                                (x) => { return new ListMonad<double>(){x+1, x-1}; };
 
    var listMonadIMonadIntDoubleFunc = new ListMonad<Func<int, IMonad<double>>>();
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc1);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc2);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc3);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc4);
    listMonadIMonadIntDoubleFunc.Add(intIMonadIntDoubleFunc5);
 
    Console.WriteLine("App [Just(x^2), Just(x^3), Just(x^4), Just(x^5] over [1,..,5]");
    listMonadInt.App(listMonadIMonadIntDoubleFunc).Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for combination 
    Func<int, double, double> intDoubleDoubleFunc1 = 
                                (x, y) => { return (double)x + y; };
    Func<int, double, double> intDoubleDoubleFunc2 = 
                                (x, y) => { return (double)x - y; };
    Func<int, double, double> intDoubleDoubleFunc3 = 
                                (x, y) => { return (double)x * y; };
    Func<int, double, double> intDoubleDoubleFunc4 = 
                                (x, y) => { return (double)x / y; };
    Func<int, double, double> intDoubleDoubleFunc5 = 
                                (x, y) => { return (double)x % y; };
 
    var listMonadIntDoubleDoubleFunc = new ListMonad<Func<int, double, double>>()
                                        {intDoubleDoubleFunc1,
                                        intDoubleDoubleFunc2,
                                        intDoubleDoubleFunc3,
                                        intDoubleDoubleFunc4,
                                        intDoubleDoubleFunc5};
 
    Console.WriteLine("Combination with 'normal' result value and function + :");
    listMonadInt.Com(intDoubleDoubleFunc1, listMonadDouble)
                .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Comb. with 'normal' result value and functions [+, -, *, /, %]: ");
    listMonadInt.Com(listMonadIntDoubleDoubleFunc, listMonadDouble)
                    .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Functions for combination with IMonad as result.
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc1 = 
        (x, y) => { return new Just<double>((double)x + y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc2 = 
        (x, y) => { return new Just<double>((double)x - y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc3 = 
        (x, y) => { return new Just<double>((double)x * y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc4 = 
        (x, y) => { return new Just<double>((double)x / y); };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc5 = 
        (x, y) => { return new ListMonad<double>(){(double)x % y}; };
 
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc6 = 
        (x, y) => { return new ListMonad<double>() { (double)x * y * y, (double) x * y * y * y }; };
            
    Func<int, double, IMonad<double>> intDoubleIMonadDoubleFunc7 = 
        (x, y) => { return new Nothing<double>(); };
 
    var listMonadIntDoubleIMonadDoubleFunc = new ListMonad<Func<int, double, IMonad<double>>>()
                                            {intDoubleIMonadDoubleFunc1,
                                            intDoubleIMonadDoubleFunc2,
                                            intDoubleIMonadDoubleFunc3,
                                            intDoubleIMonadDoubleFunc4,
                                            intDoubleIMonadDoubleFunc5,
                                            intDoubleIMonadDoubleFunc6,
                                            intDoubleIMonadDoubleFunc7};
 
    Console.WriteLine("Combination with IMonad function results.");
    Console.WriteLine("List1[1,..,5], List2[1.0,..,9.0] and function +");
    listMonadInt.Com(intDoubleIMonadDoubleFunc1, listMonadDouble)
                    .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Combination with IMonad function results.");
    Console.WriteLine("List1[1,..,5], List2[1.0,..,9.0] and " +
                        "functions [+, -, *, /, %, [x*y*y, x*y*y*y], Nothing]");
    listMonadInt.Com(listMonadIntDoubleIMonadDoubleFunc, listMonadDouble)
                .Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    // Visit with other IMonad
    Console.WriteLine("Visit with other IMonad and add (+) values in output.");
    listMonadInt.Visit<double>((x, y) => { Console.Write(x * y + ", "); }, listMonadDouble);
    Console.WriteLine("___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("Function applying with Linq: ");
    var query = from f in listMonadIntDoubleDoubleFunc
                from x in listMonadInt
                from y in listMonadDouble
                select f(x, y);
    query.Visit((x) => { Console.Write(x + ", "); });
    Console.WriteLine("");
    Console.ReadLine();
 
    Console.WriteLine("Function applying with Linq: ");
    var query2 = from f in listMonadIntDoubleIMonadDoubleFunc
                    from x in listMonadInt
                    from y in listMonadDouble
                    select f(x, y);
    query2.Visit((x) => { Console.Write(x.ToString() + ", "); });
    Console.WriteLine("\n");
    Console.ReadLine();
 
} 

Image 3

Image 4

Image 5

Image 6

Operator overloading playground

I'd done a few operator overloadings for ListMonad.

Now fma, app, combine and concat functions can be used via operators! 

C#
public static void ListMonadOperatorPlayground()
{
    int counter = 0;
 
    Console.Out.WriteLine("Create two lists [0..9]: ");
    ListMonad<double> listMonadDouble = new ListMonad<double>() 
                                        { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
 
    ListMonad<double> listMonadDoubleTwo = new ListMonad<double>() 
                                            { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
 
    // Functions for second App function.
    Func<double, IMonad<double>> doubleIMonadDoubleFun1 =
                                (x) => { return new Just<double>(x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun2 =
                                (x) => { return new Just<double>(x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun3 =
                                (x) => { return new Just<double>(x * x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun4 =
                                (x) => { return new Just<double>(x * x * x * x * x); };
    Func<double, IMonad<double>> doubleIMonadDoubleFun5 =
                                (x) => { return new ListMonad<double>() { x + 1, x - 1 }; };
 
    var listMonadFunc1 = new ListMonad<Func<double, IMonad<double>>>();
    listMonadFunc1.Add(doubleIMonadDoubleFun1);
    listMonadFunc1.Add(doubleIMonadDoubleFun2);
    listMonadFunc1.Add(doubleIMonadDoubleFun3);
    listMonadFunc1.Add(doubleIMonadDoubleFun4);
    listMonadFunc1.Add(doubleIMonadDoubleFun5);
 
    // Functions for combination 
    Func<double, double, double> doubleDoubleDoubleFunc1 =
                                (x, y) => { return (x + y); };
    Func<double, double, double> doubleDoubleDoubleFunc2 =
                                (x, y) => { return x - y; };
    Func<double, double, double> doubleDoubleDoubleFunc3 =
                                (x, y) => { return x * y; };
    Func<double, double, double> doubleDoubleDoubleFunc14 =
                                (x, y) => { return x / y; };
    Func<double, double, double> doubleDoubleDoubleFunc5 =
                                (x, y) => { return x % y; };
 
    var listMonadFunc2 = new ListMonad<Func<double, double, double>>()
                                        {doubleDoubleDoubleFunc1,
                                        doubleDoubleDoubleFunc2,
                                        doubleDoubleDoubleFunc3,
                                        doubleDoubleDoubleFunc14,
                                        doubleDoubleDoubleFunc5};
 
 
    // Functions for combination with IMonad as result.
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc1 =
        (x, y) => { return new Just<double>(x + y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc2 =
        (x, y) => { return new Just<double>(x - y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc3 =
        (x, y) => { return new Just<double>(x * y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc4 =
        (x, y) => { return new Just<double>(x / y); };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc5 =
        (x, y) => { return new ListMonad<double>() { x % y }; };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc6 =
        (x, y) => { return new ListMonad<double>() { x * y * y, x * y * y * y }; };
 
    Func<double, double, IMonad<double>> intDoubleIMonadDoubleFunc7 =
        (x, y) => { return new Nothing<double>(); };
 
    var listMonadFunc3 = new ListMonad<Func<double, double, IMonad<double>>>()
                                            {intDoubleIMonadDoubleFunc1,
                                            intDoubleIMonadDoubleFunc2,
                                            intDoubleIMonadDoubleFunc3,
                                            intDoubleIMonadDoubleFunc4,
                                            intDoubleIMonadDoubleFunc5,
                                            intDoubleIMonadDoubleFunc6,
                                            intDoubleIMonadDoubleFunc7};
 
    Console.WriteLine("fmap f(x) = x * 0.5 over [1,0..9.0] with \" / \" operator");
 
    var result = (listMonadDouble / ((x) => { return x * 0.5; })).Visit((x) =>
                                                            {
                                                                Console.Out.Write(x + ", ");
                                                                counter++;
                                                                if (counter % 9 == 0)
                                                                    Console.WriteLine("");
                                                            });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    Console.WriteLine("App functions [x^2, x^3, x^4, x^5, [x+1, x-1]] \n" +
                        " over [1,0..9.0] with \" * \" operator");
 
    var resultTwo = (listMonadDouble * listMonadFunc1).Visit((x) =>
                                                            {
                                                                Console.Out.Write(x + ", ");
                                                                counter++;
                                                                if (counter % 9 == 0)
                                                                    Console.WriteLine("");
                                                            });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
    // Create a tupel with the ListMonad with functions inside 
    // and with the other ListMonad with double values inside
    // because the \"*\" operator can have only one other argument.
    // it sad there are no way for custom operators in C#
    // and there are no operator that take tree arguments and can be overloaded.
    var funcMonadTupel = new Tuple<IMonad<Func<double, double, double>>,
                                    IMonad<double>
                                    >(listMonadFunc2, 
                                    listMonadDoubleTwo);
 
    counter = 0;
    Console.WriteLine("Combinate [1.0,..,9.0] with [1.0,..,9.0] and functions \n" +
                        " [x+y, x-y, x*y, x/y, x%y]");
    var resultThree = (listMonadDouble * funcMonadTupel)
                        .Visit((x) =>
                        {
                            Console.Out.Write(x + ", ");
                            counter++;
                            if (counter % 9 == 0)
                                Console.WriteLine("");
 
                            if (counter % (9 * 9) == 0)
                                Console.WriteLine("---------------------------------------");
                        });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
 
    var funcMonadTupelTwo = new Tuple<IMonad<Func<double, double, IMonad<double>>>,
                                    IMonad<double>>
                                    (listMonadFunc3,
                                    listMonadDoubleTwo);
 
    var resultFour = (listMonadDouble * funcMonadTupelTwo)
                        .Visit((x) =>
                        {
                            Console.Out.Write(x + ", ");
                            counter++;
                            if (counter % 9 == 0)
                                Console.WriteLine("");
 
                            if (counter % (9 * 9) == 0)
                                Console.WriteLine("----------------------------------------");
                        });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
 
 
    Console.WriteLine("[1.0,..,9.0] + [1.0,..,9.0] + Just(1000.0) + Nothing \n" +
                        " Fmap -> App -> Com -> Com2nd -> Visit \n" + 
                        " This will take a while!! Are you ready, then press enter :-D :");
    Console.ReadLine();
 
    // Concat both double ListMonad´s to get a bigger list
    // and only to show that its possible 
    // concat a Just(1000.0) and a Nothing<double> to the result list too.
    var resultFive = (listMonadDouble + listMonadDoubleTwo)
                        .Concat(new Just<double>(1000.0))
                        .Concat(new Nothing<double>());
 
    var resultSix = (ListMonad<double>)resultFive;
 
    // This line is done the whole operatione!
    // Without one loop.
    resultSix = resultSix / ((x) => { return x * 100.0; }) * funcMonadTupel *funcMonadTupelTwo;
 
    resultSix.Visit((x) =>
                    {
                        Console.Out.Write(x + ", ");
                        counter++;
                        if (counter % 9 == 0)
                            Console.WriteLine("");
 
                        if (counter % (9 * 9) == 0)
                            Console.WriteLine("-------------------------------------------");
                    });
    Console.WriteLine("\n___________________________________________________________");
    Console.ReadLine();
} 

Image 7

Image 8

Image 9

Conclusion 

At the beginning I had no idea where I would end. But I like what I produced. And I got a deeper understanding about functional programming, what a type is, or how it can be seen, and much more. And I even saw what you can only do with a real function programming language like Haskell. I read an article about the Rusty programming language from Mozilla and the tutorial. Im very interested in this language! 

There are a lot of improvements that can be done. For example T Return() could be changed to void Return(out T) to get rid of the problem with Either and return. Btw. till now I've not tested the Either class. I got no compilation errors, so something should happen Big Grin | <img src=

History  

  • September, 18, 2013 - First version published.    
  • Edit: Com and Com2nd function description were wrong. The results are packed in a new IMonad (not always a ListMonad) like its done by the App functions. 
This article was originally posted at https://github.com/Muraad/Monad-CSharp

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
I´m a computer science student at the University of Applied Science in munich.
And i´m working (min. 10-20 h/week) at a small systems engineering company as a software engineer/developer for two years now. We make laser cutting cnc machines, vision engineering and other custom things, mostly electronic and software.

I have experience in different languanges (C/C++/C#/Java/Haskell/Matlab/Mathematica).
I like programming, and thinking in abstract ways.

Comments and Discussions

 
GeneralWhy reimplement Nullable, IEnumerable, IQueryable and Linq in less readable way? Pin
peSHIr16-Oct-13 20:11
peSHIr16-Oct-13 20:11 
GeneralRe: Why reimplement Nullable, IEnumerable, IQueryable and Linq in less readable way? Pin
Muraad Nofal21-Oct-13 10:16
Muraad Nofal21-Oct-13 10:16 
GeneralMy vote of 5 Pin
Erich Ledesma18-Sep-13 22:44
Erich Ledesma18-Sep-13 22:44 
GeneralMy vote of 5 Pin
gs_virdi18-Sep-13 22:44
gs_virdi18-Sep-13 22:44 
GeneralMy vote of 5 Pin
Southmountain18-Sep-13 6:21
Southmountain18-Sep-13 6:21 
GeneralRe: My vote of 5 Pin
Muraad Nofal18-Sep-13 8:32
Muraad Nofal18-Sep-13 8:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.