From: Austin Ziegler Date: 2019-01-26T10:20:37-05:00 Subject: [ruby-core:91287] Re: [Ruby trunk Feature#15567] Allow ensure to match specific situations --===============1716323205== Content-Type: multipart/alternative; boundary="00000000000049e5fc05805dfde9" --00000000000049e5fc05805dfde9 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Ensure is as much about resource management as it is about always running something: ```ruby class File def self.open(*args) file =3D 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=E2=80=99m completely against your proposal as stated, and neutral on a po= ssible new clause like `aborted`. I don=E2=80=99t see the value. On Sat, Jan 26, 2019 at 6:00 AM 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 =3D tr= ue) > is not too bad, and clear enough. And the performance would be fine in th= is > 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` a= s > 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 i= n. > > 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 =3D 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: > > --=20 Austin Ziegler =E2=80=A2 halostatue@gmail.com =E2=80=A2 austin@halostatue.c= a http://www.halostatue.ca/ =E2=80=A2 http://twitter.com/halostatue --00000000000049e5fc05805dfde9 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Ensure is as much about resource management as it is about= always running something:

```ruby
class File<= /div>
=C2=A0 def self.open(*args)
=C2=A0 =C2=A0 file =3D new(= *args)
=C2=A0 =C2=A0 file&.open
=C2=A0 =C2=A0 if fi= le && file.open? && block_given?
=C2=A0 =C2=A0 = =C2=A0 begin
=C2=A0 =C2=A0 =C2=A0 =C2=A0 yield file
=C2= =A0 =C2=A0 =C2=A0 ensure
=C2=A0 =C2=A0 =C2=A0 =C2=A0 file.close
=C2=A0 =C2=A0 =C2=A0 end
=C2=A0 =C2=A0 end
=C2= =A0 end
end
```

Your prop= osal for a conditional `ensure` breaks that expectation and complicates the= language.

It sounds like what you really want is someth= ing like:

```ruby
def foo
=C2= =A0 normal_path
rescue exception
=C2=A0 exception_handl= ing_clause
aborted :return, :throw
=C2=A0 abnormal_path=
ensure
=C2=A0 always_path
end
```<= /div>

I=E2=80=99m completely against your proposal as st= ated, and neutral on a possible new clause like `aborted`. I don=E2=80=99t = see the value.


On Sat, Jan 26, 2019 at 6:00= AM <samuel@oriontransfer.ne= t> 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 situ= ations like "returned normally" vs "non-standard flow contro= l".

> I think the workaround using a variable set after yield (success =3D t= rue) 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 al= so don't think it's clearly conveying what the user is trying to do= .

The problem is, people write code like this:

```
begin
=C2=A0 ..
ensure
=C2=A0 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 o= r not. Because you can't express clearly what you are actually interest= ed in.

There should be no overhead when exiting normally in the following situatio= n:

```
begin
=C2=A0 yield
ensure when not return
=C2=A0 return :abnormal
end
```

As soon as you write code like:

```
begin
=C2=A0 yield
=C2=A0 success =3D true
ensure
=C2=A0 return :abnormal unless success
end
```

you guarantee that the code must pass through the ensure block. But your in= tention 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 t= o handle explicitly what people are already doing, albeit probably incorrec= tly and inefficiently.

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

* 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 suff= icient to correctly, efficiently and easily handle abnormal flow control.
Take the following program for example:

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

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

catch(:foo) do
=C2=A0 =C2=A0 =C2=A0 =C2=A0 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 t= hough exceptional case might never happen or happen very infrequently.

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

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

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

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

```
def doot
=C2=A0 =C2=A0 =C2=A0 =C2=A0 yield
ensure when not return
=C2=A0 =C2=A0 =C2=A0 =C2=A0 return "abnormal"
end
```

Inverted patterns allow user to specify the behaviour without having proble= ms 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=3Dunsubscribe= >
<http://lists.ruby-lang.org/cgi-bin/m= ailman/options/ruby-core>


--
--00000000000049e5fc05805dfde9-- --===============1716323205== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline Unsubscribe: --===============1716323205==--