// Examples of alternate types/union types/heterogeneous types. class Foo { x: f64; } class Bar { y: f64; } fun extract_value(v: Foo or Bar) -> f64 { match v { v:Foo -> v.x, v:Bar -> v.y, } // No error; exhaustivity analysis should work. } // This is Bob Nystrom's example from // https://journal.stuffwithstuff.com/2023/08/04/representing-heterogeneous-data/ // class MeleeWeapon { damage: f64; } class RangedWeapon { minRange: f64; maxRange: f64; } class Monster { health: f64; } fun print(x:string) {} fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) { weapon match { w:RangedWeapon -> distance >= w.minRange and distance <= w.maxRange, _ -> distance == 1 } } fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) { // This is worse than Bob's final version but but it works. `is` operator // should be the same precedence as `and` and left-associative, so the // `and` that follows the declaration in the `is` still has the variables // from the `is` binding in scope. if weapon is MeleeWeapon and distance > 1 or weapon is w : RangedWeapon and (distance < w.minRange or distance > w.maxRange) { print("You are out of range") return } // Bob says he doesn't want to do flow analysis and we're not, we're using // scoping rules and associativity to make the re-bindings work. // // NOTE: special syntax here: `identifier` is `TypeExpression` ALMOST means // `identifier is identifier : TypeExpression` as the shorthand for checking // local variables. The *almost* part is that the effective type of the // variable changes but not the binding. (Is this what we want?) // let damage = match weapon { w:MeleeWeapon -> roll_dice(w.damage), w:RangedWeapon -> w.maxRange - w.minRange, }; if monster.health <= damage { print("You kill the monster!"); monster.health = 0 } else { print("You wound the monster."); monster.health = monster.health - damage } } fun more_examples(weapon: MeleeWeapon or RangedWeapon) -> f64 or () { if weapon is w: RangedWeapon and w.maxRange > 10 { // w is still in scope here; the `and` is bound into a predicate expression // and breaks exhaustivity w.minRange } } // Some fun with iterators class Finished {} let FINISHED = new Finished {}; class Iterator { current: f64; fun next(self) -> f64 or Finished { if self.current < 10 { let result = self.current; self.current = self.current + 1; return result; } FINISHED } } fun test() -> f64 { let sum = 0; // A single step of an iterator... let it = new Iterator { current: 0 }; if it.next() is v: f64 { sum = sum + v; } // Looping by hand over an iterator is pretty clean. let it = new Iterator { current: 0 }; while it.next() is v: f64 { sum = sum + v; } // Unroll by hand... let it = new Iterator { current: 0 }; loop { if it.next() is v: f64 { sum = sum + v; } else { return sum; } } // Not in this test but `for` over an object should turn into something // like the above. } /// @ignore WIP // @no-errors