Skip to content

Instantly share code, notes, and snippets.

@ToJans
Last active July 25, 2020 04:02
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ToJans/e57e2170e050a671e07b to your computer and use it in GitHub Desktop.
Save ToJans/e57e2170e050a671e07b to your computer and use it in GitHub Desktop.
This is a C# implementation showing what functors, applicatives and monads look like.
using System;
namespace FPExplained
{
// This is a C# implementation showing what functors, applicatives and monads look like.
//
// ----><8-----------------------------------
// UPDATE:
// ***********************************
// For a maybe monad, all these constructs look a bit redundant; their reason of existance is more obvious
// when you use a list.
//
// I suggest you to look at the code of the list, before you look at the `maybe` code.
// You can find it here: http://tojans.me/blog/2014/06/19/functors-applicatives-and-monads-for-csharp-developers-part-2/
//
// After implementing the list; I vastly simplified the code for the maybe class and updated this gist;
// now LiftM and LiftA use the same code for the `Maybe` as they do for `FList`.
// ----><8-----------------------------------
// Begin old comment
// *********************************
//
// I would have loved to make it generic example (i.e. using interfaces), but this would complicate
// it too much, so I opted for an example functor/applicative/monad, i.e. the `Maybe` class.
// This is due to the limits of the C# type system, which doesn't allow generic types that take another
// generic type as a parameter, f.e. I would need this construct:
//
// public interface FP<TContext<TObject>> {
// // support for functors
// public TContext<TNewObject> FMap<TNewObject>(Func<TObject, TNewObject> functor);
// // support for applicatives
// public TContext<TNewObject> LiftA<TNewObject>(TContext<Func<TObject, TNewObject>> applicative);
// // support for monads
// public TContext<TNewObject> LiftM<TNewObject>(Func<TObject, TContext<TNewObject>> monad);
// }
//
// I think it would be an interesting exercise to implement other classes, like `List` or `Either`.
//
// For questions, just contact me (@tojans on twitter).
//
public class Maybe<TObject>
{
private TObject obj = default(TObject);
private bool isNothing = true;
private Maybe() { }
private Maybe(TObject obj) { this.obj = obj; this.isNothing = false; }
// A nothing
public static Maybe<TObject> Zero()
{
return new Maybe<TObject>();
}
// A value
public static Maybe<TObject> Bind(TObject obj)
{
return new Maybe<TObject>(obj);
}
// convert a Maybe<Maybe<TValue>> into a Maybe<TValue>
public static Maybe<TObject> Join(Maybe<Maybe<TObject>> MaybeMaybe)
{
return MaybeMaybe.isNothing ? Zero() : MaybeMaybe.obj;
}
// functor
public Maybe<TNewObject> FMap<TNewObject>(Func<TObject, TNewObject> functor)
{
return isNothing
? Maybe<TNewObject>.Zero()
: Maybe<TNewObject>.Bind(functor(this.obj));
}
// applicative - maybe applies a maybe<f[m]> maybe<x[n]>
// returns a Maybe
public Maybe<TNewObject> LiftA<TNewObject>(Maybe<Func<TObject, TNewObject>> applicative)
{
return Maybe<TNewObject>.Join(applicative.FMap(functor => this.FMap(functor)));
}
// monad - returns a joined monad of the result
public Maybe<TNewObject> LiftM<TNewObject>(Func<TObject, Maybe<TNewObject>> monad)
{
return Maybe<TNewObject>.Join(this.FMap(monad));
}
public override string ToString()
{
return isNothing
? "Nothing<" + typeof(TObject).Name + ">"
: "Just<" + typeof(TObject).Name + "> " + obj.ToString();
}
}
class Program
{
static void Main(string[] args)
{
var nothing_int = Maybe<int>.Zero();
var just____100 = Maybe<int>.Bind(100);
var just___1000 = Maybe<int>.Bind(1000);
Console.WriteLine(nothing_int.ToString()); // Nothing<Int32>
Console.WriteLine(just____100.ToString()); // Just<Int32> 100
Console.WriteLine(just___1000.ToString()); // Just<Int32> 1000
// functor
Func<int, int> functor_pow2 = x => x * x;
Console.WriteLine(nothing_int.FMap(functor_pow2).ToString()); // Nothing<Int32>
Console.WriteLine(just____100.FMap(functor_pow2).ToString()); // Just<Int32> 10000
Console.WriteLine(just___1000.FMap(functor_pow2).ToString()); // Just<Int32> 1000000
var applicative_description = Maybe<Func<int, string>>.Bind(i => "This is the value " + i.ToString());
var applicative_____nothing = Maybe<Func<int, string>>.Zero();
// applicative
Console.WriteLine(nothing_int.LiftA(applicative_description).ToString()); // Nothing<String>
Console.WriteLine(just____100.LiftA(applicative_description).ToString()); // Just<String> This is the value 100
Console.WriteLine(just___1000.LiftA(applicative_description).ToString()); // Just<String> This is the value 1000
Console.WriteLine(nothing_int.LiftA(applicative_____nothing).ToString()); // Nothing<String>
Console.WriteLine(just____100.LiftA(applicative_____nothing).ToString()); // Nothing<String>
Console.WriteLine(just___1000.LiftA(applicative_____nothing).ToString()); // Nothing<String>
// monad
Func<int, Maybe<string>> monad_describe_large_numbers =
i => (i > 100)
? Maybe<string>.Bind(i.ToString() + " is a large number")
: Maybe<string>.Zero();
Console.WriteLine(nothing_int.LiftM(monad_describe_large_numbers).ToString()); // Nothing<String>
Console.WriteLine(just____100.LiftM(monad_describe_large_numbers).ToString()); // Nothing<String>
Console.WriteLine(just___1000.LiftM(monad_describe_large_numbers).ToString()); // Just<String> 1000 is a large number
// combine them!
Maybe<Func<string, int>> applicative_strlen = Maybe<Func<string, int>>.Bind(s => s.Length);
Console.WriteLine(nothing_int.FMap(functor_pow2).LiftM(monad_describe_large_numbers).LiftA(applicative_strlen).ToString());
// Nothing<Int32>
Console.WriteLine(just____100.FMap(functor_pow2).LiftM(monad_describe_large_numbers).LiftA(applicative_strlen).ToString());
// Just<Int32> 23
Console.WriteLine(just___1000.FMap(functor_pow2).LiftM(monad_describe_large_numbers).LiftA(applicative_strlen).ToString());
// Just<Int32> 25
applicative_strlen = Maybe<Func<string, int>>.Zero();
Console.WriteLine(just___1000.FMap(functor_pow2).LiftM(monad_describe_large_numbers).LiftA(applicative_strlen).ToString());
// Nothing<Int32>
Console.ReadLine();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment