[IDEA] Refactoring let-chains

I found an extremely easy way how drastically reduce complexity (get rid of if-let and while-let) and refactor let-chains such that everything(let-chains, if, if-let, while, while-let) work nicely!

We add LetExpression

LetExpr :
    let Pattern = Scrutinee

but, it is permitted to use in boolean expressions only:

BoolExpression :
    Expression | LetExpr

and finally we point, where boolean expression are acceptable

PredicateLoopExpression :
   while BoolExpression /*except struct expression*/ BlockExpression
   
IfExpression :
   if BoolExpression /*except struct expression*/ BlockExpression
   (else ( BlockExpression | IfExpression  ) )?
   
LazyBooleanExpression :
     Expression || Expression
   | BoolExpression && BoolExpression

delete if-let and while-let definitions and it is ... done! :tada: :boom:

Ok, almost. We need some rules for binded variables(BVs). BVs could be used

    1. in next right Expressions from "LetExp"
    1. till not-inside-grouped "'||'" if it exists
    1. or till end of this group ")" if it is grouped
    1. or till used as not a part of && / || Expressions
    1. or till end of Expression

For Controllable expressions (for if and while) and "LetExp" is a part of "conditional" Expression

    1. or till end of "conditional" Expression only if left to "LetExp" is not-inside-grouped "'||'"
    1. or till end of "cross-conditional" Expression (IfTrueExpr)

That's means:

let a = c.is_some() && let Some(c) = bar() ;
//      ^^^~~     Error(1) 'c' is out of scope!!

let a = let Some(c) = foo() && true || c.is_none();
// Error(2) 'c' is out of scope!!   ~~^^^

let a = (let Some(c) = bar() && true) && c.is_none();
// Error(3) 'c' is out of scope!!     ~~^^^

let a = baz(let Some(c) = bar() && true) && c.is_none();
// Error(4) 'c' is out of scope!!        ~~^^^

if foo() || let Some(c) = bar() && c.is_some() { 
    let _ = c;
    //  ~~^^^  Error(6) 'c' is out of scope!!
 }

if (foo() || baz()) && let Some(c) = bar() && c.is_some() { 
    let _ = c;
    //  OK
 }

if let Some(c) = baz() && (c.foo || c.bar) { 
    let _ = c;
    //  OK
 }

We have 4 advantages:

(1) We automatically forbid to use LetExpr as part of "simple" not explicitly boolean expressions.

(2) Both if-let and while-let become just a specific case of if and while and and we could cut extra-code.

(3) Let-chains become cost-less control flow operations for if and while.

(3+) We have simple rules, where binded variables could be used.

(4) We could also get rid of match!(expr, pat) macro. Use instead next expressions:

 let match_macro = true && let pat = expr;

Standalone bool-valued let expressions were the original proposal that subsequently led to let chains. We ended up rejecting the additional complexity of allowing bool-valued let in arbitrary expressions: complexity of the scoping rules, and visual/parsing complexity of allowing let in arbitrary places. We entertained the idea of allowing it in the future (which is forward-compatible).

Personally (speaking only for myself and not for the rest of the lang team), I'd rather add is expressions than bool-valued let expressions: x is Some(y) && func(y).

9 Likes

This x is Some(y) looks pretty, but it has same power as LetExpr(+stanalone) and same rules.

// is
let a = foo() is Some(5);

// let
let a = true && let Some(5) = foo();

But only LetExpr allows us to get rid of special cases if-let and while-let.

// is + if-let
if let Some(x) = bar() && x.baz() is Some(42) { /*  */ }

// let + if
if let Some(x) = bar() && let Some(42) = x.baz() { /*  */ }

About rules:

Yes, if "imploded" EXPR is just E1 && E2 && E3 ... && En, then all rules collapse to maximal scope.

@Ratatouille Which examples do you want to see? Useful cases? Replacement of Macro-Match? Rules? Errors?

Ok, here I write in details 7 rules for binded variables(BVs) for future possibilities.

  1. BVs could be used in next right Expressions from "LetExp"
let a = E1 && let Some(x) = foo() && E3<x> && E4<x> && E5<x> && ... && En<x>;

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, .. En, but not in E1


  1. BVs could be used till not-inside-grouped "'||'" if it exists
let a = E1 && let Some(x) = foo() && E3<x> && E4<x> || E5 && ... && En;

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, but not in E5, ... En, E1


3 + 4) BVs could be used till end of this group ")" if it is grouped orBVs could be used till end parent Expressions (except && / if / while)

let a = E1 && (let Some(x) = foo() && E3<x> && E4<x>) && E5 && ... && En;

let a = E1 && baz(let Some(x) = foo() && E3<x> && E4<x>) && E5 && ... && En;

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, but not in E5, ... En, E1


  1. if "LetExp" is a part of "conditional" Expression of if or while, and not-inside-grouped "'||'" exists left to "LetExp" then BVs could be used till end of "conditional" Expression only
if E1 || let Some(x) = foo() && E3<x> && E4<x> E5<x> && ... && En<x> { 
    En1; ... Em
}

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, .. En but not in En1, ... Em, E1.


  1. if "LetExp" is a part of "conditional" Expression of if or while, and just "'&&'" are used then BVs could be used till end of "cross-conditional" Expression (IfTrueExpr )
if E1 && let Some(x) = foo() && E3<x> && E4<x> E5<x> && ... && En<x> { 
    En1<x>; ... Em<x>
}

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, .. En, En1, ... Em, but not in E1.


5 ) if "LetExp" is not a part of "conditional" Expression of if or while, and just "'&&'" are used then BVs could be used till end of this Expression

let a =  E1 && let Some(x) = foo() && E3<x> && E4<x> E5<x> && ... && En<x>;

So, BVs (variable x in our case), which were extracted at E2 could be used in E3, E4, .. En, but not in E1.

is would also work in place of if let or while let.

if foo() is Some(x) { dbg!(x); }

5 Likes

I agree that is would be more intuitive and easier to understand than if let and let-chains. The only issue is that if let is very widespread, so deprecating it in favor of if expr is pattern would cause a lot of churn. But it is doable, with a sufficiently long transition period.

1 Like

I don't think we should deprecate if let or while let, just because we have another syntax that would work.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.