ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:91275] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
@ 2019-01-26  8:31 ` samuel
  2019-01-26  9:51 ` [ruby-core:91277] " eregontp
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: samuel @ 2019-01-26  8:31 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been reported by ioquatix (Samuel Williams).

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 2.7
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91277] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
  2019-01-26  8:31 ` [ruby-core:91275] [Ruby trunk Feature#15567] Allow ensure to match specific situations samuel
@ 2019-01-26  9:51 ` eregontp
  2019-01-26 10:59 ` [ruby-core:91278] " samuel
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: eregontp @ 2019-01-26  9:51 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by Eregon (Benoit Daloze).


I think the workaround using a variable set after yield (success = true) is not too bad, and clear enough. And the performance would be fine in this case (i.e. there would be no overhead if the JIT profiles the branch and only one branch is taken, like TruffleRuby).

Ensure should always be executed no matter the circumstances IMHO, so I don't like to make it conditional with extra syntax.
Not executing ensure when using break, etc sounds like a bug in the vast majority of cases.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76523

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 2.7
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91278] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
  2019-01-26  8:31 ` [ruby-core:91275] [Ruby trunk Feature#15567] Allow ensure to match specific situations samuel
  2019-01-26  9:51 ` [ruby-core:91277] " eregontp
@ 2019-01-26 10:59 ` samuel
  2019-01-26 15:20   ` [ruby-core:91287] " Austin Ziegler
  2019-01-26 12:03 ` [ruby-core:91283] " shevegen
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 13+ messages in thread
From: samuel @ 2019-01-26 10:59 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by ioquatix (Samuel Williams).


@eregon - thanks for your response.

> Ensure should always be executed no matter the circumstances IMHO, so I don't like to make it conditional with extra syntax.

This isn't about not executing ensure, but allowing user to handle situations like "returned normally" vs "non-standard flow control".

> I think the workaround using a variable set after yield (success = true) is not too bad, and clear enough. And the performance would be fine in this case (i.e. there would be no overhead if the JIT profiles the branch and only one branch is taken, like TruffleRuby).

Personally, I think it's ugly, and I also think it is inefficient. I also don't think it's clearly conveying what the user is trying to do.

The problem is, people write code like this:

```
begin
  ..
ensure
  abnormal_path if $!
end
```

This actually wrong and fails in the case of `throw` wrapped in `catch` as given in the original examples.

It's actually not clear from the code if this is what the user wanted or not. Because you can't express clearly what you are actually interested in.

There should be no overhead when exiting normally in the following situation:

```
begin
  yield
ensure when not return
  return :abnormal
end
```

As soon as you write code like:

```
begin
  yield
  success = true
ensure
  return :abnormal unless success
end
```

you guarantee that the code must pass through the ensure block. But your intention is, only execute this code if the code didn't return normally (or some other flow control).

So, I don't think the argument about always executing ensure holds up - this isn't about changing the semantics of `ensure` but extending it to handle explicitly what people are already doing, albeit probably incorrectly and inefficiently.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76524

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 2.7
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91283] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (2 preceding siblings ...)
  2019-01-26 10:59 ` [ruby-core:91278] " samuel
@ 2019-01-26 12:03 ` shevegen
  2019-01-26 23:26 ` [ruby-core:91295] " samuel
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: shevegen @ 2019-01-26 12:03 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by shevegen (Robert A. Heiler).


I was about to write a long comment, but then I noticed that my reply would be too long, so I shortened
this. So just a few key statements:

- I don't like the proposed syntax suggestion here in particular:

    ensure when not return

The reason why I dislike it is partially due to the syntax verbosity; but also because, more significantly,
because it appears to do conditional checks in very different means than have existed before in ruby.
I would expect "when" usually in a case-structure.

There was this other recent suggestion to also add "when", to allow for chaining after the addition of
the alias "then" to "yield_self", leading to chains of .when .then .when .then; and I also am not a huge fan
of using "when" that way. Of course there is more than one way to do something, but my own personal
preference would be to keep "when" reserved for case-when layouts.

As for catch/throw: I think it is a lot more common to see begin/rescue than catch/throw. Even if this
may be slower (e. g. some people doing control flow logic with begin/rescue), I think the intent and
clarity of intent is much clearer with control flows based on begin/rescue (and ensure) style layout
of code. So to conclude and finish my comment, I think it would be better to not introduce a more
complicated ensure conditional clause as proposed in the suggestion here. IMO you can re-structure
your code to keep ensure simple, so it should not be necessary to add additional conditionals into
ensure, even more so when these are unusual in regards to syntax - it would be the first time that I
would see "ensure when" and I don't quite like that syntax suggestion in this context.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76531

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91287] Re: [Ruby trunk Feature#15567] Allow ensure to match specific situations
  2019-01-26 10:59 ` [ruby-core:91278] " samuel
@ 2019-01-26 15:20   ` Austin Ziegler
  0 siblings, 0 replies; 13+ messages in thread
From: Austin Ziegler @ 2019-01-26 15:20 UTC (permalink / raw
  To: Ruby developers


[-- Attachment #1.1: Type: text/plain, Size: 4981 bytes --]

Ensure is as much about resource management as it is about always running
something:

```ruby
class File
  def self.open(*args)
    file = new(*args)
    file&.open
    if file && file.open? && block_given?
      begin
        yield file
      ensure
        file.close
      end
    end
  end
end
```

Your proposal for a conditional `ensure` breaks that expectation and
complicates the language.

It sounds like what you really want is something like:

```ruby
def foo
  normal_path
rescue exception
  exception_handling_clause
aborted :return, :throw
  abnormal_path
ensure
  always_path
end
```

I’m completely against your proposal as stated, and neutral on a possible
new clause like `aborted`. I don’t see the value.


On Sat, Jan 26, 2019 at 6:00 AM <samuel@oriontransfer.net> wrote:

> Issue #15567 has been updated by ioquatix (Samuel Williams).
>
>
> @eregon - thanks for your response.
>
> > Ensure should always be executed no matter the circumstances IMHO, so I
> don't like to make it conditional with extra syntax.
>
> This isn't about not executing ensure, but allowing user to handle
> situations like "returned normally" vs "non-standard flow control".
>
> > I think the workaround using a variable set after yield (success = true)
> is not too bad, and clear enough. And the performance would be fine in this
> case (i.e. there would be no overhead if the JIT profiles the branch and
> only one branch is taken, like TruffleRuby).
>
> Personally, I think it's ugly, and I also think it is inefficient. I also
> don't think it's clearly conveying what the user is trying to do.
>
> The problem is, people write code like this:
>
> ```
> begin
>   ..
> ensure
>   abnormal_path if $!
> end
> ```
>
> This actually wrong and fails in the case of `throw` wrapped in `catch` as
> given in the original examples.
>
> It's actually not clear from the code if this is what the user wanted or
> not. Because you can't express clearly what you are actually interested in.
>
> There should be no overhead when exiting normally in the following
> situation:
>
> ```
> begin
>   yield
> ensure when not return
>   return :abnormal
> end
> ```
>
> As soon as you write code like:
>
> ```
> begin
>   yield
>   success = true
> ensure
>   return :abnormal unless success
> end
> ```
>
> you guarantee that the code must pass through the ensure block. But your
> intention is, only execute this code if the code didn't return normally (or
> some other flow control).
>
> So, I don't think the argument about always executing ensure holds up -
> this isn't about changing the semantics of `ensure` but extending it to
> handle explicitly what people are already doing, albeit probably
> incorrectly and inefficiently.
>
> ----------------------------------------
> Feature #15567: Allow ensure to match specific situations
> https://bugs.ruby-lang.org/issues/15567#change-76524
>
> * Author: ioquatix (Samuel Williams)
> * Status: Open
> * Priority: Normal
> * Assignee: ioquatix (Samuel Williams)
> * Target version: 2.7
> ----------------------------------------
> There are some situations where `rescue Exception` or `ensure` are not
> sufficient to correctly, efficiently and easily handle abnormal flow
> control.
>
> Take the following program for example:
>
> ```
> def doot
>         yield
> ensure
>         # Did the function run to completion?
>         return "abnormal" if $!
> end
>
> puts doot{throw :foo}
> puts doot{raise "Boom"}
> puts doot{"Hello World"}
>
> catch(:foo) do
>         puts doot{throw :foo}
> end
> ```
>
> Using `rescue Exception` is not sufficient as it is not invoked by `throw`.
>
> Using `ensure` is inefficient because it's triggered every time, even
> though exceptional case might never happen or happen very infrequently.
>
> I propose some way to limit the scope of the ensure block:
>
> ```
> def doot
>         yield
> ensure when raise, throw
>         return "abnormal"
> end
> ```
>
> The scope should be one (or more) of `raise`, `throw`, `return`, `next`,
> `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all
> except for `RUBY_TAG_FATAL`).
>
> Additionally, it might be nice to support the inverted pattern, i.e.
>
> ```
> def doot
>         yield
> ensure when not return
>         return "abnormal"
> end
> ```
>
> Inverted patterns allow user to specify the behaviour without having
> problems if future scopes are introduced.
>
> `return` in this case matches both explicit and implicit.
>
>
>
>
> --
> https://bugs.ruby-lang.org/
>
> Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
> <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>
>


-- 
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue

[-- Attachment #1.2: Type: text/html, Size: 6746 bytes --]

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91295] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (3 preceding siblings ...)
  2019-01-26 12:03 ` [ruby-core:91283] " shevegen
@ 2019-01-26 23:26 ` samuel
  2019-01-26 23:30 ` [ruby-core:91296] " samuel
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: samuel @ 2019-01-26 23:26 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by ioquatix (Samuel Williams).


@shevegen - thanks for your feedback.

> The reason why I dislike it is partially due to the syntax verbosity

With regards to syntax, it should use existing keywords, otherwise it can cause more problems. The reason for `not` is already explained, because otherwise user may have to enumerate all other tags which is inconvenient and may break if new tags are introduced in the future. If you can propose better syntax, please do.

>  but also because, more significantly, because it appears to do conditional checks in very different means than have existed before in ruby.

This isn't really a conditional check like `if`, and it's not even a big change internally, it simply exposes a bit more of the exception handling mechanisms. It should be more efficient, because `ensure when not return` can be handled as part of exceptional flow control.

It's more similar to `rescue` than `ensure` in it's overhead. In MRI, `rescue` has some overhead, but it's possible to implement zero-cost exception handling.

The reason why I think this is a good idea is because Ruby provides a lot of flow control statements, but handling these abnormal flow control is very tricky and easy to get wrong. Statements like `ensure ... unless $!` is actually incorrect as well as inefficient. `rescue Exception` doesn't handle all ways a function can exit.

I think the key things to think about would be:

- Is there a better syntax for this proposal?
- Does introducing such feature have any practical downside?
  - Implementation cost.
  - Maintenance cost.
  - Does it introduce technical debt?
- Does it solve a problem that can't be solved some other way?
  - `ensure when not return` can be partly solved some other way but with some overhead.
  - `ensure when raise` is similar to `rescue Exception` but doesn't require `raise` to resume default exception handling.
  - `ensure when retry` and other formulations (`throw`, `return`, `next`, `break`, `redo`, `retry`) have no equivalent right now.
  - `ensure when ..., ..., ...` is practically impossible, each case needs to be handled independently if possible.
- Even if there are existing solutions, does this proposed solution communicate more clearly the intention of the programmer?
- Do we want to expose this level of behaviour to user code?
- Could we limit what tags you can match? I suspect the most important one would be `ensure when not return`, but I can also imagine it could be pretty useful to catch things like `retry` and `redo`.


----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76540

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91296] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (4 preceding siblings ...)
  2019-01-26 23:26 ` [ruby-core:91295] " samuel
@ 2019-01-26 23:30 ` samuel
  2019-01-27  2:42 ` [ruby-core:91299] " merch-redmine
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: samuel @ 2019-01-26 23:30 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by ioquatix (Samuel Williams).


One more thing to bear in mind is that even if you don't like, if you don't need it, you don't need to use it in your code. But for situations like mine where I have a handful of use cases, and efficiency is a concern (to an extent) as well as readability and correctness, I would welcome such a feature. If you don't need it you won't be affected by it, but if you need it, there is actually no other practical solution in the language right now.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76541

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91299] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (5 preceding siblings ...)
  2019-01-26 23:30 ` [ruby-core:91296] " samuel
@ 2019-01-27  2:42 ` merch-redmine
  2019-01-29 21:21 ` [ruby-core:91322] " samuel
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: merch-redmine @ 2019-01-27  2:42 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by jeremyevans0 (Jeremy Evans).


ioquatix (Samuel Williams) wrote:
> One more thing to bear in mind is that you can avoid this feature if you don't like it. Because it's for some very specific situations. It doesn't affect existing code in any way. But for situations like mine where I have a handful of use cases, and efficiency is a concern (to an extent) as well as readability and correctness, I would welcome such a feature. If you don't need it you won't be affected by it, but if you need it, there is actually no other practical solution in the language right now.

In the very rare cases where you want to treat a non-local, non-exception exit differently from a normal exit, as Eregon mentioned, there is a simple existing practical solution that is unlikely to be significantly less efficient:

~~~ruby
def doot
  ret = yield
  normal_exit = true
  ret
ensure                                                                                                                                                                                                              
  # Did the block return normally
  return "abnormal" if $! || !normal_exit
end
~~~

I think we should definitely not add syntax in an attempt to make it easier to treat a non-local, non-exception exit differently than a normal exit, as doing so is usually a mistake.

You may not consider the above approach a simple and practical solution.  However, considering you have already conceded that your proposal is for "very specific situations" and you only have "handful of use cases", even if you consider it neither simple nor practical, it's still easy and possible.  New syntax is not necessary to support what you want, and I do not think new syntax should be added for "very specific situations" with only a "handful of use cases" when it is already possible to get the behavior you want with existing syntax.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76545

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91322] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (6 preceding siblings ...)
  2019-01-27  2:42 ` [ruby-core:91299] " merch-redmine
@ 2019-01-29 21:21 ` samuel
  2019-01-30 12:23 ` [ruby-core:91340] " eregontp
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: samuel @ 2019-01-29 21:21 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by ioquatix (Samuel Williams).


So, @jeremyevans0, I agree with everything you say.

That being said, this proposal goes beyond just that single case. For example, there is no easy way to capture `retry` or `redo` exiting a block.

Looking at your example, e.g.

```ruby
def doot
  ret = yield
  normal_exit = true
  ret
ensure                                                                                                                                                                                                              
  # Did the block return normally
  return "abnormal" if $! || !normal_exit
end
```

I certainly prefer

```ruby
def doot
  yield
ensure when not return                                                                                                                                                                                                             
  return "abnormal"
end
```

It's clearer what's going on (especially if function is more complex than 1 line of actual code), it should be more efficiently implemented by VM since it only affects non-normal flow control and there is no conditional checks required.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76572

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91340] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (7 preceding siblings ...)
  2019-01-29 21:21 ` [ruby-core:91322] " samuel
@ 2019-01-30 12:23 ` eregontp
  2019-01-30 12:25 ` [ruby-core:91342] " eregontp
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: eregontp @ 2019-01-30 12:23 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by Eregon (Benoit Daloze).


ioquatix (Samuel Williams) wrote:
> For example, there is no easy way to capture `retry` or `redo` exiting a block.

Which is intended, it would break the semantics of those if they were caught, or it would be very surprising if some ensure blocks are not run in these cases even though they exit the scope.

I think there is < 1% case where it's sensible to not run ensure in this kind of conditions, and the workaround seems good enough for these rare cases.

> it should be more efficiently implemented by VM since it only affects non-normal flow control and there is no conditional checks required.

Please don't assume that. A check will be needed too, and it can already optimize very well with the local variable (e.g., zero overhead on TruffleRuby if only using the normal path, or only the exception path).

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76589

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91342] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (8 preceding siblings ...)
  2019-01-30 12:23 ` [ruby-core:91340] " eregontp
@ 2019-01-30 12:25 ` eregontp
  2019-02-07  7:08 ` [ruby-core:91457] " matz
  2019-02-12  1:16 ` [ruby-core:91511] " samuel
  11 siblings, 0 replies; 13+ messages in thread
From: eregontp @ 2019-01-30 12:25 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by Eregon (Benoit Daloze).


@ioquatix Could you give one or two real-world examples where this would be useful?

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76591

* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91457] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (9 preceding siblings ...)
  2019-01-30 12:25 ` [ruby-core:91342] " eregontp
@ 2019-02-07  7:08 ` matz
  2019-02-12  1:16 ` [ruby-core:91511] " samuel
  11 siblings, 0 replies; 13+ messages in thread
From: matz @ 2019-02-07  7:08 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by matz (Yukihiro Matsumoto).

Status changed from Open to Rejected

The language feature similar to `ensure` can be observed in many languages (`unwind-protocol` in Lisp, `defer` in Go, `finally` in Java and others), but neither of them considers the situation. That indicates there's no real-world use-case for it. So currently I believe there's no benefit enough the implementation cost and making the language more complex.

Matz.


----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76717

* Author: ioquatix (Samuel Williams)
* Status: Rejected
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [ruby-core:91511] [Ruby trunk Feature#15567] Allow ensure to match specific situations
       [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
                   ` (10 preceding siblings ...)
  2019-02-07  7:08 ` [ruby-core:91457] " matz
@ 2019-02-12  1:16 ` samuel
  11 siblings, 0 replies; 13+ messages in thread
From: samuel @ 2019-02-12  1:16 UTC (permalink / raw
  To: ruby-core

Issue #15567 has been updated by ioquatix (Samuel Williams).


Okay, thanks for your time and input. That makes sense.

----------------------------------------
Feature #15567: Allow ensure to match specific situations
https://bugs.ruby-lang.org/issues/15567#change-76771

* Author: ioquatix (Samuel Williams)
* Status: Rejected
* Priority: Normal
* Assignee: ioquatix (Samuel Williams)
* Target version: 
----------------------------------------
There are some situations where `rescue Exception` or `ensure` are not sufficient to correctly, efficiently and easily handle abnormal flow control.

Take the following program for example:

```
def doot
	yield
ensure
	# Did the function run to completion?
	return "abnormal" if $!
end

puts doot{throw :foo}
puts doot{raise "Boom"}
puts doot{"Hello World"}

catch(:foo) do
	puts doot{throw :foo}
end
```

Using `rescue Exception` is not sufficient as it is not invoked by `throw`.

Using `ensure` is inefficient because it's triggered every time, even though exceptional case might never happen or happen very infrequently.

I propose some way to limit the scope of the ensure block:

```
def doot
	yield
ensure when raise, throw
	return "abnormal"
end
```

The scope should be one (or more) of `raise`, `throw`, `return`, `next`, `break`, `redo`, `retry` (everything in `enum ruby_tag_type` except all except for `RUBY_TAG_FATAL`).

Additionally, it might be nice to support the inverted pattern, i.e.

```
def doot
	yield
ensure when not return
	return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having problems if future scopes are introduced.

`return` in this case matches both explicit and implicit.




-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2019-02-12  1:17 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <redmine.issue-15567.20190126083143@ruby-lang.org>
2019-01-26  8:31 ` [ruby-core:91275] [Ruby trunk Feature#15567] Allow ensure to match specific situations samuel
2019-01-26  9:51 ` [ruby-core:91277] " eregontp
2019-01-26 10:59 ` [ruby-core:91278] " samuel
2019-01-26 15:20   ` [ruby-core:91287] " Austin Ziegler
2019-01-26 12:03 ` [ruby-core:91283] " shevegen
2019-01-26 23:26 ` [ruby-core:91295] " samuel
2019-01-26 23:30 ` [ruby-core:91296] " samuel
2019-01-27  2:42 ` [ruby-core:91299] " merch-redmine
2019-01-29 21:21 ` [ruby-core:91322] " samuel
2019-01-30 12:23 ` [ruby-core:91340] " eregontp
2019-01-30 12:25 ` [ruby-core:91342] " eregontp
2019-02-07  7:08 ` [ruby-core:91457] " matz
2019-02-12  1:16 ` [ruby-core:91511] " samuel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).