/* A monadic adapter for legacy Java, for error handling. Requires JDK 22+ (pattern matching on records, sealed records) This monad is similar a result in F#. It's intended to to encase exceptions, rendering them inert by capturing them inside an "exonad". Throwing an exception without catching them is in a sense irresponsible: you're making someone else handle your errors. Using this monad, your functions can return optional results in an exonad. Exception objects are automatically caught and sometimes intentionally created, but never thrown. You're forced to deal with the fact that every exonad may contain a value (a "exoval") or contains an inert exception (an "exocept"). You will be able to apply the usually monadic combinators such as map/bind, etc. plus a few more ... */ package noexceptions; import java.util.function.*; // java interfaces have evolved ... public sealed interface Exonad permits Exoval, Exocept { ////// Static functions: static Exonad Value(U v) { if (v!=null) return new Exoval(v); else return Except(new NullPointerException("null is not a value")); } static Exonad Except(Exception e) { if (e==null) return new Exocept(new NullPointerException()); else return new Exocept(e); } static Exonad on_behalf(Supplier supplier) { Exonad answer; try { answer = Value(supplier.get()); // null automatically caught } catch (Exception e) { answer = new Exocept(e); } return answer; }//on_behalf static Exonad on_behalf_of(Runnable r) { return on_behalf( () -> { r.run(); return 0; } ); } // 0 is dummy return value: just means returned successfully, no except static Exonad flatten(Exonad> ee) { return switch (ee) { case Exoval(Exoval(var _)) -> ((Exoval>)ee).val(); case Exoval(Exocept(var _)) -> ((Exoval>)ee).val(); case Exocept(var e) -> Except(e); // must change type }; } //flatten /////// default implementations (but don't change) default boolean is_val() { return switch(this) { case Exoval(var _) -> true; case Exocept(var _) -> false; }; } default Exonad map(Function f) { return switch(this) { case Exoval(T v) -> on_behalf( () -> f.apply(v) ); case Exocept(Exception e) -> Except(e); }; } record pair(A car, B cdr) {} default Exonad map2(Exonad other, BiFunction f) { return switch(new pair,Exonad>(this,other)) { case pair(Exoval(var x), Exoval(var y)) -> on_behalf( () -> f.apply(x,y) ); case pair(Exocept(var e), var _) -> Except(e); case pair(var _, Exocept(var e)) -> Except(e); }; } // short-circuited or default Exonad exo_or(Exonad other) { return switch(this) { case Exoval(var v_) -> this; default -> other; }; } default Exonad map_e(Function ef) { return switch(this) { case Exocept(Exception e) -> Except(ef.apply(e)); default -> this; }; }// warning: using ? for type inference is problematic default Exonad match(Function vf, Function ef) { switch(this) { case Exoval(var v) : return on_behalf( () -> vf.apply(v) ); case Exocept(var e) : if (ef==null) return Except(new NullPointerException("null function")); else return Except(ef.apply(e)); } }//match default Exonad bind(Function> bf) { return switch(this) { case Exoval(var v) -> bf.apply(v); case Exocept(var e) -> Except(e); }; } default Exonad and_then(Function> bf) { return this.bind(bf); }//alias for bind default Exonad if_v(Consumer action) { switch(this) { case Exoval(var v) : on_behalf_of( () -> action.accept(v) ); default : break; } return this; } default Exonad if_e(Runnable r) { switch(this) { case Exocept(var e) : on_behalf_of( () -> r.run() ); default : break; } return this; } default Exonad if_else(Consumer tf, Runnable ff) { switch (this) { case Exoval(var v) -> on_behalf_of( () -> tf.accept(v) ); default -> on_behalf_of( () -> ff.run() ); } return this; } // this is the most unsafe function in interface: null can be // passed to default_val as argument. default T unwrap_or(T default_val) { return switch (this) { case Exoval(var v) -> v; default -> default_val; }; } }//Exonad /* Choice to be made: if record subclasses are made public, then it would be possible to corrupt monad by new Exoval(null). If subclasses are not visible outside of package, however, users would not be able to pattern match directly on an Exonad using switch. */ record Exoval(T val) implements Exonad { @Override public String toString() { return "Value("+val+")"; } } record Exocept(Exception ex) implements Exonad { @Override public String toString() { return "Except("+ex.getMessage()+")"; } } ///////////////////////////// for testing class testingexonad { static Exonad safediv(Double x, Double y) { return Exonad.on_behalf( () -> x/y ); } public static void main(String[] args) { var arg0 = Exonad.on_behalf( () -> Double.parseDouble(args[0]) ); var arg1 = Exonad.on_behalf( () -> Double.parseDouble(args[1]) ); arg0.exo_or(Exonad.on_behalf(()->(double)Integer.parseInt(args[0],16))) .map2(arg1,testingexonad::safediv) .if_else( v -> System.out.println("result = "+v), () -> System.out.println("no result due to errors")); }//main } /* MIT License Copyright (c) 2024 Chuck Liang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */