Skip to content

Allow finally keyword on if constructions #1825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
foodwaterwifi opened this issue Aug 29, 2021 · 6 comments
Open

Allow finally keyword on if constructions #1825

foodwaterwifi opened this issue Aug 29, 2021 · 6 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@foodwaterwifi
Copy link

Normally, the finally keyword is used with try-catch constructions and the code therein will run regardless of whether the try code throws or not.

There have been a number of times I've wanted something similar for if constructions. For example (just an example!!!), when parsing a numeral string into a custom numeric type, you might want to check the sign, and if the sign is found, increment the parse index or alternatively shorten the string being parsed to not contain the sign:

if (myString.startsWith("+")) {
    parsedNum.setSign(1);
} else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
} finally {
    myString = myString.substring(1);
}

Without finally, you have to do one of the following things:

  1. Duplicate code (if the code is long, this can be remedied by extracting it into a function of course):
if (myString.startsWith("+")) {
    parsedNum.setSign(1);
    myString = myString.substring(1);
} else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
    myString = myString.substring(1);
}
  1. Indicator variable:
bool hasSign = false;
if (myString.startsWith("+")) {
    parsedNum.setSign(1);
    hasSign = true;
} else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
    hasSign = true;
}

if (hasSign) {
    myString = myString.substring(1);
}
  1. Duplicate condition:
if (myString.startsWith("+") || myString.startsWith("-")) {
    if (myString.startsWith("+")) {
        parsedNum.setSign(1);
    } else if (myString.startsWith("-")) {
        parsedNum.setSign(-1);
    }

    myString = myString.substring(1);
}

These alternatives are fine, but I think it would be more concisely expressed with finally, and be less prone to error.

The exact meaning of finally here would be: execute this code if any branches of the condition are taken; do not execute it if no branches of the condition are taken.

@foodwaterwifi foodwaterwifi added the feature Proposed language feature that solves one or more problems label Aug 29, 2021
@ykmnkmi
Copy link

ykmnkmi commented Aug 30, 2021

also same for loops, like in python

for, while /* , do */ {
  if (some) {
    print('break');
    break;
  }
} finally {
  print('done');
} /* while */

@eernstg
Copy link
Member

eernstg commented Aug 30, 2021

When comparing alternatives and workarounds, you could include the following variant (which avoids code duplication):

bool doFinally = true;

if (myString.startsWith("+")) {
  parsedNum.setSign(1);
} else if (myString.startsWith("-")) {
  parsedNum.setSign(-1);
} else {
  doFinally = false;
}

if (doFinally) {
  myString = myString.substring(1);
}

Similarly:

var doFinally = false;

while (someCondition) { // or `for (...) {`.
  doFinally = true;
  ... // Actual work done by this loop.
}

if (doFinally) ...

OK, we repeat doFinally = true in the loop once for each iteration; but that's probably also going to happen with a language implementation of finally on while/for loops.

So the motivation for having finally on composite statements shouldn't really be code duplication. However, the form using finally might well be considered more readable and, as you mention, less error prone.

@lrhn
Copy link
Member

lrhn commented Aug 30, 2021

What I'd do is:

checkSign: {
  if (myString.startsWith("+")) {
    parsedNum.setSign(1);
  } else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
  } else {
    break checkSign;
  }
  myString = myString.substring(1);
}

Using labeled breaks efficiently allows a lot of control flow patterns that would otherwise requires extra boolean state and double-checking the same thing.
Dart has labeled breaks just like Java and JavaScript.

(About the loop exit block, I do want that! #171).

@PlugFox
Copy link

PlugFox commented Aug 31, 2021

Do you really want to add a whole language construct for the sake of 1 variable that will do the same?

Please, do not turn Dart into JS or Python or Kotlin or something like this.
Target strong strict languages as main course

Use switch-case.

If you can't live without it, use wrappers and extensions:

void main() {
  // If with wrapper
  var myString = '+123';
  int parsedNum = 0;
  doFinally(
    () {
      if (myString.startsWith("+")) {
        parsedNum = 1;
      } else if (myString.startsWith("-")) {
        parsedNum = -1;
      }
    },
    () => myString = myString.substring(1),
  );
  print('$myString : $parsedNum');

  // Loop with extension
  () {
    while (true) {
      if (true) {
        print('break');
        break;
      }
    }
  }.doFinally(() => print('done'));

  // Let
  () {
    return 'Let';
  }.let((it) => print(it));
}

void doFinally(
  final void Function() fn,
  final void Function() doFinally,
) {
  fn();
  doFinally();
}

extension FunctionDoFinallyX on Function {
  void doFinally(final void Function() fn) {
    this();
    fn();
  }
}

extension FunctionLetX<T extends Object?> on T Function() {
  R let<R extends Object?>(final R Function(T it) fn) => fn(this());
}

@munificent
Copy link
Member

if (myString.startsWith("+")) {
    parsedNum.setSign(1);
} else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
} finally {
    myString = myString.substring(1);
}

If I saw that code without knowing anything about the feature, my assumption would be that the finally clause runs after parsedNum.setSign(-1); regardless of whether that statement throws an exception. In other words, it simply becomes a finally clause on the preceding block. Basically just an implicit try.

I'm not opposed to more powerful control flow constructs, but I don't feel this one carries its weight, and my intuition is that finally would be a particularly confusing keyword choice.

@jodinathan
Copy link

checkSign: {
  if (myString.startsWith("+")) {
    parsedNum.setSign(1);
  } else if (myString.startsWith("-")) {
    parsedNum.setSign(-1);
  } else {
    break checkSign;
  }
  myString = myString.substring(1);
}

Just forgot that this existed.
Thanks for sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

7 participants