From 93c9dcd13b93d7cf72dc960196e29ed3cf0cdc1f Mon Sep 17 00:00:00 2001 From: John Doty Date: Tue, 30 Jan 2024 06:39:10 -0800 Subject: [PATCH] [fine] Suggestion for improvement in match No special keyword. Exhaustivity is harder. --- fine/tests/expression/alternates.fine | 41 ++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/fine/tests/expression/alternates.fine b/fine/tests/expression/alternates.fine index 79304484..b8760ead 100644 --- a/fine/tests/expression/alternates.fine +++ b/fine/tests/expression/alternates.fine @@ -8,9 +8,9 @@ class Bar { } fun extract_value(v: Foo or Bar) -> f64 { - if match v as Foo { + if v is Foo { v.x // Magic type binding! - } else as Bar { + } else if v is Bar { v.y // Same magic re-binding of the variable to the new type } // No error; exhaustivity analysis should work. } @@ -34,7 +34,7 @@ class Monster { fun print(x:string) {} fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) { - if match weapon as w : RangedWeapon { + if weapon is w : RangedWeapon { distance >= w.minRange and distance <= w.maxRange } else { distance == 1 @@ -42,26 +42,29 @@ fun in_range(weapon: MeleeWeapon or RangedWeapon, distance: f64) { } 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) { + // 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 but we're doing all our - // tricks with variables and scoping here, with one *big* bit of magic which - // is... + // 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` as `TypeExpression` ALMOST means - // `identifier as identifier : TypeExpression` as the shorthand for checking + // 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 = if match weapon as w: MeleeWeapon { + // + // TODO: What do we do about captured variables? + // + let damage = if weapon is 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. + } else if weapon is w: RangedWeapon { w.maxRange - w.minRange }; @@ -75,7 +78,7 @@ fun attack(weapon: MeleeWeapon or RangedWeapon, monster: Monster, distance: f64) } fun more_examples(weapon: MeleeWeapon or RangedWeapon) -> f64 or () { - if match weapon as w: RangedWeapon and w.maxRange > 10 { + 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 @@ -105,20 +108,20 @@ fun test() -> f64 { // A single step of an iterator... let it = new Iterator { current: 0 }; - if match it.next() as v: f64 { + 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 match it.next() as v: f64 { + while it.next() is v: f64 { sum = sum + v; } // Unroll by hand... let it = new Iterator { current: 0 }; loop { - if match it.next() as v: f64 { + if it.next() is v: f64 { sum = sum + v; } else { return sum;