## Error Handling with Monads *Chuck.C.Liang@Hofstra.edu* Computer scientists are human and as such they make mistakes. Famous computer scientist Anthony Hoare made numerous important contributions including Quicksort and Monitors (as in semaphores and monitors). Yet he also admitted to making one horrible mistake. At a software conference in 2009, he apologized for inventing the **null pointer**. In his own words, > *"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years."* In the spirit of admitting mistakes, I confess that during most of the 25+ years that I've been teaching students how to program, I made little effort to properly teach error handling techniques. I've written many programs for teaching my classes but few of them handled errors properly. Take for example the following function (in Java, but it could be in many languages), which returned the largest value in an array of integers: ```java public static int largest(int[] A) { int answer = A[0]; for(int i=1;ianswer) answer = A[i]; return answer; } ``` This function fails to handle multiple errors, one of them is when the actual argument `A` is **null**. It's also possible to have a non-null but zero-length array, in which case `A[0]` would throw and `ArrayIndexOutofBounds` exception. My excuses for writing such erroneous code are numerous: 1. Most academic computer scientists are not engineers and are concerned with more intellectually meaningful problems. Some academics would just say that the domain of the function is "restricted to non-empty arrays", as if that would make the problem go away. 2. It's already so hard to just teach students how to program and appreciate algorithmic principles such as time/space complexity. Adding error-handling code distracts from the main principles and makes teaching that much harder. 3. The error-handling paradigm employed by Java and many other languages, namely the raising and catching of **exceptions**, is not sufficient. I was certainly not alone in making the first two excuses. The first I now find embarrassing; the second has some truth to it, but is still not justifiable. Only the third excuse is something I will still claim as valid today. More and more people are coming to realize that exceptions are not a good approach to error-handling. In the event of an error when calling `largest`, Java will throw either a `NullPointerException` or `ArrayIndexOutOfBoundsException`. In a semester when I'm "good", I would add a line to the above function ``` if (A==null || A.length==0) throw new RuntimeException("invalid input to function 'largest'"); ``` Of course throwing an exception alone is not enough: they have to be caught. But *where should the exception be caught?* First of all, *there is often no way to handle the exceptions locally.* In this example, we cannot effectively add a try-catch block inside the function because it's still obliged to return an integer. But in the event of an empty array, any integer that it returns would surely be wrong. We have no choice but to differ error handling elsewhere. This is unfortunate because we want our function to *encapsulate* an algorithm. However, the error handling aspect of the algorithm escapes the encapsulation. There is also no clear, general strategy as to where to catch the exceptions *externally*. If we simply place a try-catch around every call to the function, we may well run into the same problem again (what to return from the calling function). There is another problem with exceptions, which is that they're *dynamically scoped*. The exception is caught by the nearest `catch` as determined at **runtime**. In other words, if we write: ```java try { if (/*some condition*/) f(); else g(); } catch (NullPointerException npe) { /* error handling code */ } ``` It is not possible to say with certainty that *"any null pointer exception that occurs inside the try block will be handled by the provided error handling code."* It may be the case that function `f` or `g` has it's own exception handler. It depends on what happens at runtime. This dynamic nature of exceptions makes it even harder for us to write code that encapsulate error handling. Even if all exceptions are required to be eventually caught (which subclasses of `RuntimeException` are not), the problem of exception handling is a *global,* and not a *local* one. And in a semester when I'm "bad", I'd just write ``` public static void main(String[] args) throws Exception ``` just to avoid having to catch any darn exception. Whether it's Java or another language, the fact is that exceptions run contrary to the principles of abstraction and information hiding that we'd like to have in high-level programming. Of course it is possible to write error-free programs by catching and handling all exceptions in just the right places, but there are few general strategies on how to do so. Even in situations where error-handling code with exceptions are not difficult to write, the whole process of error-handling can still be tedious, repetitive and distracting from the main task of implementing an algorithm. As an academic computer scientist trained originally in functional and logic programming, I'd much to prefer to write "declarative" code. We call it "referential transparency". Suppose we wish to apply a series of functions to a value, we'd like to just write "`h(g(f(x)))`" or maybe "`x.f().g().h()`". But we often can't, because f, for example, may return null or some invalid value not acceptable to g. We have to break up the simple, declarative code and add a bunch of if-else checks and try-catch blocks. Now the code looks ... decidedly not declarative. ### Monads I'd like to tell my students "whenever you find yourself writing code that's tedious and repetitive, it's time to find a higher form of *abstraction* that will encapsulate the tediousness and allow us to write code that's simple and clear again." Using a variable instead of many constants, using a vector instead of many variables, writing a function that encapsulates an algorithm, writing a generic class are all examples of what I mean by this kind of abstraction. A *monad* is an abstraction that applies to virtually all functions and types. Monads in programming originated from the study of category theory and its application to functional programming languages (e.g. Haskell and OCaml). Most programming languages including C++ and Java are acquiring more functional programming features. #### Some advanced stuff ... Mathematically, a monad is a monoid in the category of endofunctors. In the following definitions I will use both functional programming syntax in the form of *lambda expressions*, as well as object-oriented syntax in the form of method calls on objects. A lambda expression is a nameless function that implements (in Java) an interface containing one required function. For example, `(x -> x*1.5)` would implement **[`java.util.function.Function`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html)**. The type of the lambda expression is inferred from the interface that it instantiates. A monad is defined by specifying a *[Kleisli Triple](https://en.wikipedia.org/wiki/Kleisli_category)*: 1. A map from types to types. In programming language syntax, a template/generic class or interface `M` that maps any type `A` to `M`. 2. A function `unit` from values of type `A` to values of type `M` 3. A function `bind` of type `M -> (A -> M) -> M`. That is, a function that takes an object of type `M` and then a function from `A` to `M`, and returns an object of type `M`. I will write applications of the `bind` combinator in the object-oriented style: `m.bind(f)` to mean that bind is applied to m, then to the function f, where f is usually a lambda expression. This will make the syntax of composing a series of binds much cleaner: `m.bind(f).bind(g).bind(h) ...`. Such a triple must form a (non-commutative) monoid. The `unit` map must be a left-identity of `bind` in the sense that `x -> (unit(x).bind(f))` (unit composed with bind) is equivalent to `f`. It should also be possible to see `unit` as a right-identity in the sense that `a.bind(unit)` is equivalent to `a`. In full-scale functional programming languages such as Haskell, bind is assigned an infix operator `>>=` so we can write these laws more naturally as `unit(x) >>= f = f(x)` and `a >>= unit = a`. Furthermore, `bind` must be associative in the sense that > `(a >>= f) >>= g = a >>= (x -> (f(x) >>= g))` In java-ish syntax, this means > `a.bind(f).bind(g)` is equivalent to `a.bind(x -> f(x).bind(g))`. Associativity is an important component of referential transparency. For example, `unit(3).bind(x -> unit(x*0.5))` may return an object of type `M` that's equivalent to `unit(1.5)`. In a particular monad, the `bind` operation may perform some additional work before applying its functional argument to the encapsulated value, such as checking if the value is null. In this way `bind` can encapsulate "mundane" tasks such as error handling. This may be a lot to take in for someone who's not familiar with functional programming, and certainly can be very confusing to intro-level students. There are various tutorials that try to explain monads in simplified terms. One such tutorial even claims that if you understood what monads are, you will lose the ability to explain it to others. I believe that the best way to teach the monad abstraction is to start with a very specific monad, which also happens to be how monads are used most often in programming: the encapsulation of error-handling. A monad maps programs from an imperfect world, fraught with errors, to a more perfect world of mathematical functions that can be composed without obstruction. ### import java.util.Optional; Java is by now almost three decades old and so much of that language is based on exception handling and the possibility of pointers being null. null is the default value of any reference type. Perhaps no other popular language is more susceptible to the null pointer problem than Java. It will be difficult to completely avoid null, and exceptions, but modern java is beginning to offer some alternatives. It is be possible to define our own monadic type within java (and we will do so below) but Java offers the [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html) type which is intended to be used with the interfaces of [java.util.function](https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html). `Optional` is the first component of the Kleisli triple: a type constructor that maps any type `T` to type `Optional`. The second element, `unit`, comes in the form of the `Optional.of` function, whereas `bind` is called `Optional.flatMap`. In addition to these, there are a number of other functions on the Optional class, such as `map` and `orElse`. Not all of these methods are mathematically pure. The `get` method is definitely not. As the name suggests, an object of type `Optional` *possibly* holds a value of type `A`. It is also possible that the object does not contain a value. An "empty Optional" is formed with the method `Optional.empty()`. (Please note that the requirements of a Kleisli triple says nothing about the contents of objects, only their types, so `.empty` is not considered part of the triple but is specific to the Optional monad). We can rewrite the "largest" function as follows: ```java public static Optional largest(int[] A) { if (A==null || A.length==0) return Optional.empty(); int answer = A[0]; for(int i=1;ianswer) answer = A[i]; return Optional.of(answer); } ``` Notice that the error handling code is local, independent of what happens elsewhere. While the original `largest` function is not a total function on all values of type `int[]`, the new function is. The function does not return an integer. If one tried to use the return value as an integer, it will result in a **compiler error**. Compiler errors are virtually zero-cost compared to runtime errors. ``` int[] A = {4,3,7,2}; int z = 3 * largest(new int[]{4,3,7,2}); // compiler error! ``` The programmer is **forced** to confront the fact that it's possible that no value was returned by the function call. When using the value returned by such a function, we must apply `flatMap` (bind) or a similar function such as map. `map` is a specialized form of `flatMap` that takes an `Optional`, a function `A -> B` and returns an `Optional` object. `a.map(f)` is equivalent to `a.flatMap(x -> Optional.of(f(x)))`. ```java largest(new int[]{4,3,7,2}) .map(x -> 3 * x) .ifPresent(x -> {System.out.println("Result = "+x);}); ``` The key here is that `.map` will have no effect on the monad if it is empty. It will not throw any exceptions; it would just return an empty optional. If the array passed to `largest` is null or of zero length, the code just does nothing. This is because the bind (`flatMap`) of this monad checks if the value is actually present before apply its function to it. The `.ifPresent` method is only different from map in that the mapped procedure does not return a value. On the other hand, we can also write ``` var max = Optional.of(new int[]{}) .flatMap(x->largest(x)) // java won't accept .flatMap(largest) .orElseGet( () -> { //retrieves value or apply procedure to compute it System.err.println("no largest value found, returning 0 as default"); return 0; }); ``` Which would set max to 0 in settings where such a default is appropriate. As another sample application of Optional, we create an object adapter for [`java.util.HashMap`](https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html). The principle methods of this class are `put(key,value)` and `get(key)`. The `get(key)` function of HashMap returns null if no value associated with the key is found. This fact alone must be responsible for some of that billion dollars worth of pain that Hoare initiated. Our object adapter will try to alleviate the suffering. ``` import java.util.Optional; import java.util.HashMap; public class SafeHash { private HashMap HM; // internal object to be adapted to new interface public SafeHash() { HM = new HashMap(); } // constructor public int size() { return HM.size(); } public Optional get(KT key) { VT val = H.get(key); // can be null if (val==null) return Optional.empty(); else return Optional.of(val); }//get public SafeHash put(KT key, VT val) { if (key!=null && val!=null) HM.put(key,val); return this; }//SafeHash returns 'this' so operations can be chained using . } ``` We can apply the new class as follows: ``` var GPV = new SafeHash(); // grade-point value map GPV.put("A", 4.0) .put("B", 3.0) .put("C", 2.0) .put("D", 1.0) .put("F", 0.0) .get("E") // non-existent key, get empty Optional .map(x -> x * 2.0) // still empty Optional, but no error .ifPresent(x -> System.out.println("it's worth twice as much at Hofstra: "+x)). ``` Nothing will be printed by this code, but there is no null pointer exception, which is what you would get with java.util.HashMap.get. Of course, you can also avoid the exception by checking `if (... == null)`, but the point is that there is no requirement to write the `if`. The monadic approach, on the other hand, leads you to write code that would not compile if the possibility of errors are not taken into consideration. #### **Imperfections** As mentioned above, it's difficult to be rid of exceptions and null pointers completely in a language like Java. Even with the Optional class there are ways to circumvent the safety that monads promise. Chief among these is the `Optional.get` method. Given a value `x` of type `Optional`, `x.get()` will return a value of type `A`. But if there is no value to get, then an exception will be throw, and we're back to where we started. An irresponsible programmer can call `.get()` on any Optional-typed object without checking if the Optional actually contains a value. The justification for `.get()` may be that some programmers are just not comfortable with lambda expressions, and will not be able to write any programs without it. But the potential cost is that it could render the concept useless, and Optional.empty() will suffer much of the same consequences as the null pointer. We can at least experiment with trying to live without `.get`. ### A User Defined Monad Type It is, however, possible to define our own Monad. In the following three classes I define for Java the rough equivalent of the *Result* Monad found in Rust and other languages. Yes, since embracing Rust much of my perspective on programming have changed. But the issue of error handling and monads predates Rust and is relevant to all programming languages. `Result` is a disjunctive type: it consists of either a value of type A or a value of type B. Usually, `A` represent successful computations and `B` represent error objects, replacing exception objects. In our examples, `B` is `String`, indicating some error message. The monadic functors `bind`, `map`, etc take functions that instantiate java.util.function interfaces designed for functional programming. In particular, `Function` represent functions `A -> B`. Typically, functions are allowed to be contravariant in the domain (`? super A`) and covariant in the codmain (`? extends B`). `Result` is implemented as a public abstract superclass with two non-public subclasses, `OkResult` holding a value of type `V` and `ErrResult` holding a value of type `E`. The constructors of the subclasses are internal to the package. Publicly, Result objects can only be created by calling the functions `Ok` and `Err`. Besides the required monadic functions (`unit` is `Ok`), I've added several more that might be useful, including `match` and `match_do`. Each of these functors take two functions, to be applied to either an `Ok` value or an error (`Err`) object. Not all methods are mathematically sound, such as `map_mutate`, which changes an existing Result instead of creating a new one. It's added for efficiency. Missing however, is the `.get` function of the Optional class. The closest methods to `.get` are `orElse`, which requires a default value to be specified in case there is nothing to get, and `getOr`, which requires a function to be specified that computes a compatibly typed value from an error object. Our implementation of this monad is purer then even Rust's, which contains a `.get()`-like function (`unwrap`). ```java package ResultMonad; import java.util.function.*; public abstract class Result { // Abstract methods public abstract boolean is_ok(); // contains V value if true public abstract V orElse(V Default); public abstract V getOr(Function errfun); public abstract Result map(Function f); public abstract Result bind(Function> f); public abstract void ifOk(Consumer c); public abstract U match(Function okfun, Function errfun); public abstract void match_do(Consumer okfun, Consumer errfun); public abstract Result map_mutate(Function mut); // Non-abstract methods public Result flatMap(Function> f) { return bind(f); } // flatMap is alias for bind public static Result Ok(A v) { return new OkResult(v); }//static, polymorphic Ok public static Result Err(B e) { return new ErrResult(e); }//static, polymorphic Err // apply a function W*V -> U on two results @SuppressWarnings("unchecked") public Result chain(Result other, BiFunction binop) { return this.match( _ok1 -> other.map(_ok2 -> binop.apply(_ok1,_ok2)), _err -> (ErrResult)this //new ErrResult(_err) ); }//chain // if other result is Err, do not change current result @SuppressWarnings("unchecked") public Result if_and(Result other, BiFunction binop) { return other.match( _ok -> this.map(_ok1 -> binop.apply(_ok1,_ok)), _err -> this ); }//if_and // combine two results into one for a pair public Result,E> zip(Result second) { return this.bind(x -> second.bind(y -> Ok(new pair(x,y)))); }//zip (non-abstract) function // transform into java.util.Optional public java.util.Optional toOptional() { return this.match(_ok -> java.util.Optional.of(_ok), //ok err -> java.util.Optional.empty() //err ); }//toOptional }//Result abstract class ``` ``` class OkResult extends Result { private V val; OkResult(V v) { // constructor accessible only inside package if (v==null) throw new Error("null pointers not allowed in result"); val=v; } public V orElse(V Default) { return val; } public V getOr(Function errfun) { return val; } public boolean is_ok() {return true;} public Result map(Function f) { return new OkResult(f.apply(val)); }//map public Result bind(Function> f) { return f.apply(val); } //bind (flatmap) public void ifOk(Consumer c) { c.accept(val); } public U match(Function okfun, Function errfun) { return okfun.apply(val); }//match public void match_do(Consumer okfun, Consumer errfun) { okfun.accept(val); }//match_do public Result map_mutate(Function mut) { val = mut.apply(val); return this; } }//OkResult ``` ``` @SuppressWarnings("unchecked") class ErrResult extends Result { private E errobj; ErrResult(E e) { errobj = e; } public boolean is_ok() {return false;} public V orElse(V Default) { return Default; } public V getOr(Function errfun) { return errfun.apply(errobj); } public Result map(Function f) { return (ErrResult)this; //get compiler warning if not suppressed //return new ErrResult(errobj); //no warning but not efficient }//map public Result bind(Function> f) { return (ErrResult)this; } //bind (flatmap) public void ifOk(Consumer c) {} public U match(Function okfun, Function errfun) { return errfun.apply(errobj); }//match public void match_do(Consumer okfun, Consumer errfun) { errfun.accept(errobj); }//match_do public Result map_mutate(Function mut) { return this; } }//ErrResult ``` Unlike an empty Optional object, an ErrResult object carries more information, usually in the form of some error message. The following complete program demonstrates the ResultMonad package. I've included two monadic functions, `safemod`, which will not divide by zero, and an adapted version of java's Integer.parseInt function, which is often used to process command-line arguments. ``` import ResultMonad.*; public class resulttesting { // for convenience static Result Ok(A x) { return Result.Ok(x); } static Result Err(B x) { return Result.Err(x); } static Result safemod(int x, int y) { if (y==0) return Err("division of "+x+" by zero"); else return Ok(x%y); } // adopt existing Java method to use Result instead of exceptions static Result parseint(String[] argv, int i) { Result answer = Err("invalid args to parseint"); if (argv==null || i<0 || i>=argv.length) return answer; try { answer = Ok(Integer.parseInt(argv[i])); } catch (Exception e) { answer = Err("string "+argv[i]+" cannot be parsed to integer"); } return answer; }//parseint public static void main(String[] args) { parseint(args,0) // Ok or Err .map(x -> x*x) // Ok if above is Ok, else Err .map_mutate(x -> x-9) // Ok if above is Ok, else Err //.bind(x -> safemod(100,x)) // Ok or Err .match( _ok -> safemod(100,_ok), _err -> Err(_err+";\ncan't continue due to previous error") ) .bind(x -> parseint(args,1).map(y -> (Integer)x-y)) .chain(parseint(args,1), (x,y) -> x+y ) .match_do( _ok -> { System.out.println("Result = "+ _ok); }, // Ok case _err -> { System.out.println("ERROR: "+ _err); } // Err case ); // match_do }//main }//resulttesting // run java resulttesting // java resulttesting 10 20 // java resulttesting 3 // java resulttesting abc // The typecast (Integer)x is required because of possible contravariance // in the domain of the function passed to bind. ``` ### Monads in Other Conventional Languages As demonstrated, the monad can be implemented in any object oriented programming language. Newer languages restrict where `null` is allowed to appear. Kotlin's compiler even enforces that an object's been checked to be null before allowing it to be accessed. C++ 2017 added an optional type, but the monadic functions that are equivalent to bind, map, etc. are not available until C++ 2023 is released. However, I've written an experimental [`option_ptr`](https://cs.hofstra.edu/~cscccl/csc123/option_ptr.cpp) class in C++ that will try to combine the functionality of a monad with that of a smart pointer. The implementation will not rely on virtual functions like in Java in difference to the core C++ concept of Zero Overhead Abstraction. Instead, the internal representation will use `nullptr`, but it will be presented to the user as an empty optional object. ### Continuation There cannot be a "conclusion" to these thoughts because the final word on the effectiveness of the monadic approach to error-handling is yet to be written. We cannot answer the question academically: it would have to come from the cumulative experiences of the software industry in the coming decades. It is a fact that the trend in programming language development is turning against exception-handling and becoming more functionally oriented. The success of new languages such as Rust, which embraces the approach we espoused here, gives us hope that we're on the right path. From a pedagogical perspective, yes there's a lot for programmers to learn in order to apply this new approach to handling errors. But the learning curve would not be as steep once a series of good examples becomes available. These examples will form a new set of *design patterns* for the composition of error-free programs. In the Spring semester of 2023, I for the first time have abandoned exceptions in teaching an intermediate level course on data structures and object oriented programming in Java. It is always difficult to teach higher forms of abstraction. Many students struggle with understanding and appreciating the simplest form of functional abstraction. It gets progressively more difficult as we introduce recursive functions, second-order functions, interfaces and abstract classes, etc. Monads are yet another level of abstraction. Yes, it makes teaching even more difficult, but what choice do we have? We cannot continue to preach to our students a pretty picture of abstract data types and encapsulated algorithms when error handling doesn't fit the picture. Monads allow us to return to the pulpit. The effort that we will spend teaching, and the effort that students will spend on learning these advanced programming techniques are still far less costly than another billion dollars worth of pain. #### **Links To Code**: 1. [ResultMonad](https://cs.hofstra.edu/~cscccl/csc17/ResultMonad/) Java package folder 2. [resulttesting.java](https://cs.hofstra.edu/~cscccl/csc17/resulttesting.java) testing program 3. [option_ptr.cpp](https://cs.hofstra.edu/~cscccl/csc123/option_ptr.cpp) experimental monad/smart pointer in C++