From: "Dan0042 (Daniel DeLorme) via ruby-core" Date: 2023-08-09T03:45:07+00:00 Subject: [ruby-core:114364] [Ruby master Feature#19832] Method#destructive?, UnboundMethod#destructive? Issue #19832 has been updated by Dan0042 (Daniel DeLorme). janosch-x (Janosch M�ller) wrote in #note-11: > A lot of everyday Ruby code seems to be destructive, not so much by setting instance variables, but rather by modifying them (e.g. Arrays or Hashes). 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 in Haskell. Because we are coding in Ruby, I would define a "destructive" operation as something that fails if the object is frozen. And `@buf << 42` does not fail if `self` is frozen. This is how the frozen flag has always worked 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�ller) wrote in #note-11: > Maybe it's worth exploring a "static analysis" variant of this feature? Could it be tied in to RBS? Such an approach might allow for transitivity, which would make it much easier to provide this information for most existing code outside the stdlib. That sounds very cool and very hard to implement. Also it opens a big can of worms in terms of where do you draw the line for how the 'destructive' flag propagates? Which of those foo methods would you consider to be destructive? ```ruby def mut! = @x = 42 #destructive def foo1 = mut! #call a destructive method on self def foo2(buf) = buf << 42 #call a destructive method on an argument def X.indirect(v) = v.mut! def foo3 = X.indirect(self) #indirectly call a destructive method on self def foo4 = (buf = []; buf << 42) #call a destructive method on a new object 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 also be said. There can be value in knowing that a method is provably destructive. 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` instances, which shall behave like: ```ruby String.instance_method(:<<).destructive? # => true String.instance_method(:+).destructive? # => false ``` One main purpose of using these classes is to inspect and make sure how a certain method behaves. Besides arity and owner, whether a method is destructive or not is one important piece of information, but currently, you cannot achieve that from `Method` or `UnboundMethod` instances. The problem is how to implement this. It is best if this information (whether 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 ruby level does not call a destructive method anywhere within its own definition, and no dynamic method calls (`send`, `eval`, etc.) are made, then we can 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 internally-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 destructive side effect). If doing that turns out to be difficult for some or all cases, then a practical approach for the difficult cases is to label the methods as destructive or not, manually. We can perhaps have methods `Module#destructive` and `Module#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 return value can be `"unknown"` (or `:unknown` or `nil`) by default. ```ruby String.instance_method(:<<).destructive? # => "unknown" ``` -- 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-core.ml.ruby-lang.org/