// This is my implementation of a "Result Monad" for reporting results as an // alternative to runtime exceptions. It's similar to the 'Maybe' monad // in Elm and the 'Option' monad found in many languages. C# has the // Option monad in its libraries but it's not a new language feature, just // a class that's been defined and which can be used. Java now also has // an Option class but it's bit cumbersome to use (a lot of people hate it) // because Java doesn't have enough of the features of a functional programming // language. C# is a bit better. My version in C# is also a bit more // agreesive in that it will try to take care of null errors autmatically // instead of relying on the programmer. // In C#, lambda terms are written as in x=>y=>x (lambda x:lambda y.x) // and the pre-define Func delegate type defines the type of functions. // Func is the type of functions that takes a string and returns // an int. The type of the K combinator is Func>, which is // usually written A->B->A where A and B are generic type variables. // Func are functions that take two args (type A and type B) and returns // a value of type C. Note that given any function f of such a type, // x=>y=>f(x,y) is the "Curried" form of the function, which has type // Func>. Also, the conventional way of defining methods: // int f(int x) { return x*x} is equivalent to: // int f(int x) => x*x; // These features are used in the implementation. using System; using System.Collections.Generic; ///// Build a MONAD Called Result (oop style) /* A function maps values to values. A "functor" maps functions to functions. A monad is first of all a functor: it maps functions to functions but also maps types to types. More formally, a monad M is a functor MF that maps objects of type T to objects of type M. It also maps functions from type A to type B (Func) to Func,M>. This is the "fmap" or "flatmap" function defined below. It also must implement functions: 'unit' (or 'return') of type Func> 'bind' of type Func,Func>,M> (type M -> (A -> M) -> M ) and optionally: 'join' (or flatten), of type Func>,M> This is the mathematical minimum and in practice we may define more. But it is important that once an object is "lifted" into a M wrapper object, that we can't access it directly anymore: the only way to use the value is through these functions. Otherwise we loose the guarantees that the monad provides. In my implementation, I combine functional abstraction with object oriented abstraction, with two subclasses that implement a common interface. The interface is called 'Result' and the two subclasses of Result are 'ValueResult' or 'ErrorResult'. */ public interface Result { bool isError(); // is result a valid value or an error Result fmap( Func f); // U is a new generic var in this fun Result bind(Func> bf); // Result unit(U x); } // unit, or return, is better defined as a static function external to // the interface class ValueResult : Result { public T val; // the value wrapped inside the monad, and must stay wrapped public static bool nullallowed=false; // is null considered a valid result public ValueResult(T x) { val =x; //Console.WriteLine("val set to "+x);//debug } public bool isError() { return false; } public Result fmap(Func f) { return this.bind( x=>returnit(f(x)) ); } public Result bind(Func> bf) { if (nullallowed || val!=null) return bf(val); else { return new ErrorResult("Invalid value "+val); } // if (!nullable() || !EqualityComparer.Default.Equals(val,default(T))) }//bind public override String ToString() => "Value "+val; public static Result returnit(U x) => ResultMonad.unit(x); // alias }// ValueResult class ErrorResult: Result { public String msg; // error message associated with error public ErrorResult(String m) { msg=m; Console.Error.WriteLine("Error Log: "+msg); } public override String ToString() => "Error: "+msg; public bool isError() => true; public Result fmap(Func f) => new ErrorResult("Propagated "+msg); public Result bind(Func> bf) { String newmsg = "Propagated "+msg; Console.Error.WriteLine("Bind Error: "+newmsg); return new ErrorResult("Propagated "+msg); } }//ErrorResult //// this part is conventional java code that doesn't use the monad yet: public class list { public int head; public list tail; public list(int h, list t) {head=h; tail=t;} } public class ResultMonad { static list cons(int h, list t) { return new list(h,t); } static int car(list t) => t.head; static list cdr(list t) => t.tail; static int lastint(list m) { while (cdr(m)!=null) m=cdr(m); return car(m); } static int length(list m) { int cx=0; for(;m!=null;m=cdr(m),cx++); return cx; } // add value to end of list (destructively) -- WITH INTENSIONAL ERROR static void add(list m, int x) { int len = length(m); while (0 < len--) m=cdr(m); m.tail = cons(x,null); // set tail to point to new cell // where is the error? }//add public static void Main() { list M = cons(2,cons(4,cons(6,null))); list N = null; //Console.WriteLine( lastint(M) ); monadtest(); }//main ////////////////// Testing the MONAD BELOW: public static Result unit(T x) // never wrap null in a ValueResult { if (ValueResult.nullallowed || x!=null) return new ValueResult(x); else return new ErrorResult("null pointer"); } public static Result returnit(U x) => unit(x); // alias // more functions for convenience: public static Result Value(U x) => new ValueResult(x); public static Result Error(String x) => new ErrorResult(x); // the last value function, redone with monad public static Result lastval(list m) { var M = unit(m);// put inside mondad while (!(M.fmap(cdr).isError())) M = M.fmap(cdr); return M.fmap(car); } // adding something to the end of the list, WITH SAME MISTAKE public static Result wrongadd(list m, int x) { int len = length(m); var M = unit(m); while (0 < len--) M=M.fmap(cdr); // won't crash! return M.fmap( n => {n.tail=cons(x,null); return n;} ); //m.tail = cons(x,null); // set tail to point to new cell }//add public static Result correctadd(list m, int x) { int len = length(m); var M = unit(m); while (0 < --len) M=M.fmap(cdr); // won't crash! return M.fmap( n => {n.tail=cons(x,null); return n;} ); //m.tail = cons(x,null); // set tail to point to new cell }//add // iterate through list and perform action (f: int->void) public static void forlist(list m, Action f) { var M = unit(m); while (!M.isError()) { M.fmap(car).fmap( x=> { f(x); return 0;}); M=M.fmap(cdr); } } public static void monadtest() ////////***** monadtest { var M = cons(2,cons(4,cons(6,null))); list Nil = null; var RM = unit(M); // type inferred as Result Result RNil = unit(Nil); // to get an ErrorResult object instead of null-pointer exception: //car(Nil); // get null-pointer exception Result result1 = RM.fmap(car); Console.WriteLine(result1); result1 = RNil.fmap(car); Console.WriteLine(result1); result1 = RM.fmap(cdr).fmap(cdr).fmap(car); Console.WriteLine("third value: "+result1); Console.WriteLine( RM.fmap(cdr).fmap(cdr).fmap(cdr).fmap(car) ); // no 4th Console.WriteLine( lastval(M) ); // wrongadd var M2 = wrongadd(M,8); // actually constructive, need to reflect... // still not a compiler error! if (!M2.isError()) { // not required by compiler forlist(M, x=>Console.Write(x+" ")); Console.WriteLine(" -- end of list"); } M2 = correctadd(M,8); // actually constructive, need to reflect... if (!M2.isError()) { // not required by compiler forlist(M, x=>Console.Write(x+" ")); Console.WriteLine(" -- end of list"); } }//monadtest }//ResultMonad main class