[#114348] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive? — "sawa (Tsuyoshi Sawada) via ruby-core" <ruby-core@...>

Issue #19832 has been reported by sawa (Tsuyoshi Sawada).

15 messages 2023/08/06

[#114365] [Ruby master Bug#19834] Segmentation fault while running in docker — "ramachandran@... (Ramachandran A) via ruby-core" <ruby-core@...>

Issue #19834 has been reported by ramachandran@mallow-tech.com (Ramachandran A).

7 messages 2023/08/09

[#114380] [Ruby master Bug#19837] Concurrent calls to Process.waitpid2 misbehave on Ruby 3.1 & 3.2 — "kjtsanaktsidis (KJ Tsanaktsidis) via ruby-core" <ruby-core@...>

Issue #19837 has been reported by kjtsanaktsidis (KJ Tsanaktsidis).

7 messages 2023/08/11

[#114399] [Ruby master Feature#19839] Need a method to check if two ranges overlap — "shouichi (Shouichi KAMIYA) via ruby-core" <ruby-core@...>

Issue #19839 has been reported by shouichi (Shouichi KAMIYA).

27 messages 2023/08/18

[#114410] [Ruby master Bug#19841] Marshal.dump stack overflow with recursive Time — "segiddins (Samuel Giddins) via ruby-core" <ruby-core@...>

Issue #19841 has been reported by segiddins (Samuel Giddins).

9 messages 2023/08/18

[#114422] [Ruby master Feature#19842] Intorduce M:N threads — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

Issue #19842 has been reported by ko1 (Koichi Sasada).

30 messages 2023/08/21

[#114590] [Ruby master Bug#19857] Eval coverage is reset after each `eval`. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

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

21 messages 2023/08/30

[ruby-core:114364] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive?

From: "Dan0042 (Daniel DeLorme) via ruby-core" <ruby-core@...>
Date: 2023-08-09 03:45:07 UTC
List: ruby-core #114364
Issue #19832 has been updated by Dan0042 (Daniel DeLorme).





janosch-x (Janosch M=FCller) wrote in #note-11:

> A lot of everyday Ruby code seems to be destructive, not so much by setti=
ng instance variables, but rather by modifying them (e.g. Arrays or Hashes)=
.=20



shyouhei (Shyouhei Urabe) wrote in #note-12:

> Do you mean `IO#printf` is not destructive because everything destructive=
 is implemented by `IO#write` and printf is merely calling it?  That sounds=
 counter-intuitive to me.



Ok, I think we have different ideas of what is "destructive", because to me=
 it's not about side-effects that would require a Monad if we were coding i=
n Haskell. Because we are coding in Ruby, I would define a "destructive" op=
eration as something that fails if the object is frozen. And `@buf << 42` d=
oes not fail if `self` is frozen. This is how the frozen flag has always wo=
rked and I can't really imagine introducing a new way of defining/handling =
destructive operations. So in that sense neither `IO#printf` nor `IO#write`=
 are destructive.



janosch-x (Janosch M=FCller) wrote in #note-11:

> Maybe it's worth exploring a "static analysis" variant of this feature? C=
ould it be tied in to RBS? Such an approach might allow for transitivity, w=
hich would make it much easier to provide this information for most existin=
g code outside the stdlib.



That sounds very cool and very hard to implement. Also it opens a big can o=
f worms in terms of where do you draw the line for how the 'destructive' fl=
ag propagates? Which of those foo methods would you consider to be destruct=
ive?

```ruby

def mut! =3D @x =3D 42               #destructive

def foo1 =3D mut!                  #call a destructive method on self

def foo2(buf) =3D buf << 42        #call a destructive method on an argument

def X.indirect(v) =3D v.mut!

def foo3 =3D X.indirect(self)      #indirectly call a destructive method on=
 self

def foo4 =3D (buf =3D []; buf << 42) #call a destructive method on a new ob=
ject created in the method

```



shyouhei (Shyouhei Urabe) wrote in #note-12:

> Asserting that a method is destructive in spite of it does not modify its=
 receiver is kind of safe.  The problem is to prove that a method marked as=
 non-destructive actually never do so.  This is arguably impossible.



I definitely understand what you mean about safety, but the opposite can al=
so be said. There can be value in knowing that a method is provably destruc=
tive. We could fail early if the object is frozen. Maybe the JIT can avoid =
speculative optimizations (and the cost of de-optimization) that don't hold=
 for destructive methods. etc.









----------------------------------------

Feature #19832: Method#destructive?, UnboundMethod#destructive?

https://bugs.ruby-lang.org/issues/19832#change-104107



* Author: sawa (Tsuyoshi Sawada)

* Status: Open

* Priority: Normal

----------------------------------------

I propose to add `destructive?` property to `Method` and `UnboundMethod` in=
stances, which shall behave like:



```ruby

String.instance_method(:<<).destructive? # =3D> true

String.instance_method(:+).destructive? # =3D> false

```



One main purpose of using these classes is to inspect and make sure how a c=
ertain method behaves. Besides arity and owner, whether a method is destruc=
tive or not is one important piece of information, but currently, you canno=
t achieve that from `Method` or `UnboundMethod` instances.



The problem is how to implement this. It is best if this information (wheth=
er or not a method is destructive) can be extracted automatically from the =
method definition.



Unlike owner and arity, it may or may not be straightforward by statically =
analyzing the code. I think that, if a method definition defined at the rub=
y level does not call a destructive method anywhere within its own definiti=
on, and no dynamic method calls (`send`, `eval`, etc.) are made, then we ca=
n say that the method is non-destructive. If it does call, then the method =
is most likely a destructive method (it would not be destructive if the int=
ernally-called destructive method is applied to a different object. Or, we =
could rather call that a destructive method in the sense that it has a dest=
ructive side effect).



If doing that turns out to be difficult for some or all cases, then a pract=
ical approach for the difficult cases is to label the methods as destructiv=
e or not, manually. We can perhaps have methods `Module#destructive` and `M=
odule#non_destructive` which take (a) symbol/string argument(s) and return =
the method name(s) in symbol so that they can be used like:



```ruby

class A

  destructive private def some_destructive_private_method

    ...

  end

end

```



or



```ruby

class A

  def foo; ... end

  def bar; ... end

  def baz; ... end



  non_destructive :foo, :baz

  destructive :bar

end

```



or



```ruby

class A

  non_destructive



  def foo; ... end

  def baz; ... end



  destructive



  def bar; ... end

end

```



When the method is not (yet) specified whether destructive or not, the retu=
rn value can be `"unknown"` (or `:unknown` or `nil`) by default.



```ruby

String.instance_method(:<<).destructive? # =3D> "unknown"

```









--=20

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

 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-c=
ore.ml.ruby-lang.org/

In This Thread