8000 Proposal: add "use" to anonymous blocks · Issue #35499 · dart-lang/sdk · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Proposal: add "use" to anonymous blocks #35499

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

Closed
lukepighetti opened this issue Dec 26, 2018 · 7 comments
Closed

Proposal: add "use" to anonymous blocks #35499

lukepighetti opened this issue Dec 26, 2018 · 7 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-stale Closed as the issue or PR is assumed stale

Comments

@lukepighetti
Copy link
lukepighetti commented Dec 26, 2018

Hello,

I have been using anonymous blocks to break up long procedural code that will not be reused anywhere. I ran into an interesting idea for a language feature that I think would be a great addition to Dart. I can imagine there being a lot of apathy towards this proposal due to procedural programming being seen as "old", but since all applications use procedural to some extent I think its worth considering.

Here is a function using anonymous blocks. Its not a shining example, but it gets us started! The nice thing about anonymous blocks is that they encapsulate the scope within steps in procedural code while preserving the order to the reader. Do this, then this, then this. All the code is there and easy to reason about. The problem is that these anonymous blocks perform shadowing and its not clear what variables from the parent scope are being used.

myFunction(){
  final foo = "foo";
  
  String bar;

  {
    final result = "$foo bar";
    bar = result;
  }

  String baz;

  {
    final result = "$bar baz";
    baz = result;
  }

  return baz;
}

Many people would rewrite this as below. Problem is, its not sequential, harder to understand (although so common many would argue this is not a consideration), and makes non-repetitive code very repetitive in a way that normally supports reuse but in this case reuse is not valued.

_generateBar(String foo){
  final result = "$foo bar";
  return result;
};

_generateBaz(String bar){
  final result = "$bar baz";
  return result;
};

myFunction(){
  final foo = "foo";

  final bar = _generateBar(String foo);
  final baz = _generateBaz(String baz);

  return baz;
}

Proposal: Allow strict scope for anonymous blocks.

Introduce a use keyword that restricts the scope of an anonymous block to the variables provided to use.

myFunction(){
  final foo = "foo";

  final bar = use foo {
    final result = "$foo bar";
    return result;
  }

  final baz = use bar {
    final result = "$bar baz";
    return result;
  }

  return baz;
}

An example with multiple arguments might be:

final a = "a";
final b = "b";

final c = use a, b {
  return "$a $b";
}
@srawlins
Copy link
Member

CC @eernstg and @leafpetersen I think this could be moved to the language repo.

I actually just like even a simpler concept: let blocks (anonymous, try, ...) return values. This helps assign final and non-nullable variables:

final a = {
  if (foo.complicatedExpression || otherThings.something) {
    return 1;
  } else if (some.other.conditions) {
    return 2;
  } else {
    return 3;
  }
}

String /* ! */ b = try {
  // something about a connection;
} on NetworkException catch (e) {
  // guaranteed block exit like throw.
  throw MyDifferentException;
  // or a special value
  return "MAGIC_STRING_VALUE";
}

@lukepighetti
Copy link
Author
lukepighetti commented Dec 26, 2018

Apologies if this is in the wrong repo, still getting the hang of the dart-lang organization layout.

allowing blocks to return values would be darn cool, address half of the above proposal, and make my above example very concise and readable

myFunction(){
  final foo = "foo";

  final bar = {
    final result = "$foo bar";
    return result;
  }

  final baz = {
    final result = "$bar baz";
    return result;
  }

  return baz;
}

The other half of the proposal (use) allows the developer to force a block to act like a pure function, forcing it to use only its inputs. (Maybe this is a separate proposal?) Happy to hear more thoughts on this.

@lukepighetti
Copy link
Author
lukepighetti commented Dec 26, 2018

Although, you can already achieve this like so:

myFunction(){
  final foo = "foo";

  final bar = (){
    final result = "$foo bar";
    return result;
  }();

  final baz = (){
    final result = "$bar baz";
    return result;
  }();

  return baz;
}

@miyoyo
Copy link
miyoyo commented Dec 26, 2018

@srawlins Your examples are doable today with closures

final a = (){
  if (foo.complicatedExpression || otherThings.something) {
    return 1;
  } else if (some.other.conditions) {
    return 2;
  } else {
    return 3;
  }
}();
String /* ! */ b = (){
  try {
    // something about a connection;
  } on NetworkException catch (e) {
    // guaranteed block exit like throw.
    throw MyDifferentException;
    // or a special value
    return "MAGIC_STRING_VALUE";
  }
}();

The last one is also slightly ambiguous, as there are two blocks

try {
  // something about a connection;
}

and

on NetworkException catch (e) {
  // guaranteed block exit like throw.
  throw MyDifferentException;
  // or a special value
  return "MAGIC_STRING_VALUE";
}

This can be confusing syntax. Also, this would make this use case more complex to understand, and more prone to syntax errors

String test() {
  try {
    // something about a connection;
  } on NetworkException catch (e) { 
    return "MAGIC_STRING_VALUE";
  }
}
String test() {
  var b = try {
    // something about a connection;
  } on NetworkException catch (e) { 
    return "MAGIC_STRING_VALUE";
  }
}

These two are very similar and the error can be glanced over

@vsmenon vsmenon added the area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). label Jan 2, 2019
@lrhn
Copy link
Member
lrhn commented Jan 3, 2019

As I understand the proposed feature, it's not about abstraction, but about static scope restriction. It ensures that the block can only see the mentioned names from the surrounding scope.

While I can see the charm of it, I'm not sure it scales to more complex blocks. Would you have to mention use identical to be able to use the global identical function inside a block? Or will it only hide access to local variables?
If the latter, then it can actually be used to suppress a shadowing declaration, like:

int foo = 42;
func() {
  int foo = 37;
  int tmp;
  use tmp { tmp = foo; }  // copy *outer* foo.
  ...
}

That might be a little too clever. :)

In either case, having to list a lot of variables might be prohibitive. So, maybe it should have something like show/hide from imports:

hide bar { ... can't see bar from surrounding scope...}
show bar { ... can only see bar from surrounding scope ... }

That kind of scope management is probably not worth the complexity it introduces.
Local functions should be small enough that it's "obvious" what is going on.

I'd be more inclined to add syntax that explicitly declares one or more variables with a statement block as initializer, so it's clear that this variable initialization is performed by the block, instead of the not-obviously unrelated declaration and block:

int foo;
{ some block that happens to initialize foo }

Imagine if you could do:

int foo { 
  int tmp = 0;
  for (int i = 0; i < 25; i++) tmp += i * i;
  foo = tmp;
}

where foo can not be read inside the block, but it can be assigned to. Then it's much clearer (modulo the syntax not being very good) what logic is going on.

@lukepighetti
Copy link
Author
lukepighetti commented Jan 3, 2019

Yes the original idea was to restrict the ability for a block to access outside variables regardless of them being local or global. This would just promote writing blocks as pure-ish functions.

I really like the idea you are proposing. I wanted to add another (orthogonal?) idea for consideration.

It seems to me that in dart there is only one block modifier async such as

foo() async => /// etc

What if we were to add another, perhaps pure

Example:

final global = "👹";

foo(String argument) pure => "$argument bar"; // is valid
foo(String argument) pure => "$argument, $global, bar"; // is invalid

pure would force a function to only consume its inputs. I am unsure if this is more of a linter warning type of feature or a language level feature.

@eernstg
Copy link
Member
eernstg commented Jan 4, 2019

Drive-by comment: We have had several requests for constant function literals, all the way back to 2012, and they would be required to denote the same function also in the case where more than one entity is associated with a name from an enclosing scope. This would imply that local variables and instance variables from enclosing scopes cannot be accessed, but it would allow access to static and top-level variables, and it wouldn't prevent side-effects.

@lukepighetti, would that address the request of this issue? It seems rather similar to your pure modifier, although it would need to be generalized to apply to local functions as well as function literals.

@lrhn lrhn added the closed-stale Closed as the issue or PR is assumed stale label Apr 9, 2025
@lrhn lrhn closed this as not planned Won't fix, can't repro, duplicate, stale Apr 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-stale Closed as the issue or PR is assumed stale
Projects
None yet
Development

No branches or pull requests

6 participants
0