oden/fine/tests/expression/alternates.fine
John Doty 93c9dcd13b [fine] Suggestion for improvement in match
No special keyword. Exhaustivity is harder.
2024-01-30 06:39:10 -08:00

136 lines
No EOL
3.3 KiB
Text

// 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 v is Foo {
v.x // Magic type binding!
} else if v is 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 weapon is 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 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?)
//
// TODO: What do we do about captured variables?
//
let damage = if weapon is w: MeleeWeapon {
roll_dice(w.damage)
} else if weapon is 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