// Examples of alternate types/union types/heterogeneous types. class Foo { x: f64; } class Bar { y: f64; } fun extract_value(v: Foo or Bar) -> f64 { if match v as Foo { v.x // Magic type binding! } else as Bar { v.y // Same magic re-binding of the variable to the new type } // 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) { if match weapon as w : RangedWeapon { distance >= w.minRange and distance <= w.maxRange } else { distance == 1 } } fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) { // This is worse but it works. if match weapon as MeleeWeapon and distance > 1 or match weapon as 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 but we're doing all our // tricks with variables and scoping here, with one *big* bit of magic which // is... // // NOTE: special syntax here: `identifier` as `TypeExpression` ALMOST means // `identifier as 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 = if match weapon as w: MeleeWeapon { roll_dice(w.damage) } else as w: RangedWeapon { // This is the trick here: else as re-uses the expression from the match // and so we can do exhaustivity analysis. 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 match weapon as 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 match it.next() as v: f64 { sum = sum + v; } // Looping by hand over an iterator is pretty clean. let it = new Iterator { current: 0 }; while match it.next() as v: f64 { sum = sum + v; } // Unroll by hand... let it = new Iterator { current: 0 }; loop { if match it.next() as 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