module Exceptions { 
 
  // EXCEPTION BASICS 
   
  fn test() { 
    // any expression can throw exceptions 
    let x = 1 / 0;  // this expression will throw due to integer divide by zero 
 
    // to handle an exception, use try, which returns a Result{T}, where T is 
    // the type of the expression (e.g. int below). 
    let r = try { 1 / 0 } 
 
    // Result is a union with two cases: a value and an exception. 
    //  
    // union Result{T} { 
    //   val : T; 
    //   ex : Except; 
    // } 
    // 
    // Use .ex? and .val? to check to see if the result contains an exception 
    // or a value. 
    let has_exception = r.ex?;    // true if result has exception 
    let has_value = r.val?;       // true if result has a value 
 
    // Use .ex and .val to obtain the exception or value 
    if (r.ex?) { 
      print("Uh oh: ", r.ex); 
    } else { 
      print("It worked! ", r.val); 
    } 
  } 
 
  // DEFINING EXCEPTION TYPES 
 
  // The Except type is an extensible union, which allows it's contents to be defined elsewhere. 
  // 
  // union Except { 
  //   ...    // extensible 
  // } 
  //  
  // Use except to define types that can be thrown.  These types will be added to the Except union. 
  except { 
    SomewhatBad{}           // defines new struct with no fields 
    ReallyBad{x : double}   // defines new struct with field x 
  } 
 
  fn test2() { 
 
    let x = rand() * 10; // generate random number between 0..10 
 
    // use throw to raise an exception with a value 
    // the type of the value thrown must be defined with except 
    let r = try { 
      if (x < 1) { 
        throw SomewhatBad;    // raises an exception with SomewhatBad type 
      } 
      if (x > 5.0) { 
        throw ReallyBad(x);   // raises an exception with an instance of ReallyBad, with a value 
      } 
      x + 1;                  // non-exception value 
    } 
 
    // is can be used to check the exception type 
    if (r.ex? && r.ex is SomewhatBad) { 
      // SomewhatBad exception was raised 
    } 
 
    // match can be used to match and extract the value and the exception value 
    match (r) { 
      Result(val : @val)            => print("It worked! ", val);   // print value 
      Result(ex : SomewhatBad)      => print("somewhat bad");       // handle SomewhatBad exception 
      Result(ex : ReallyBad(@val))  => print("really bad: ", val);  // handle ReallyBad exception 
    } 
 
    // alternatively nested match can be used for the exception types 
    match (r) { 
      Result(val : @val)    => print("It worked! ", val);    // print value 
      Result(ex : @ex)      => match (ex) {                  // nested match 
        SomewhatBad         => print("somewhat bad");        // handle SomewhatBad exception 
        ReallyBad(@val)     => print("really bad: ", val);   // handle ReallyBad exception 
      } 
    } 
 
    // result is a "truthy" value meaning it can be used in conditional expressions 
    // (it's true when there is a value, and false if there is an exception) 
    // result also supports ! which extracts the value (same as using .val) 
    if (r) {              // same as if (r.val?) 
      print("val", r!)    // same as print("val", r.val) 
    } else { 
      print("ex", r.ex) 
    } 
  }   
 
  // except can be used to add exception types defined elsewhere 
  // e.g. they do not need to be defined within the except definition 
 
  struct SizeMustBePositive {size: double} 
 
  // allow SizeMustBePositivestring and int to be thrown 
  except SizeMustBePositive | string | int; 
}