From: shyouhei@... Date: 2020-09-14T01:29:29+00:00 Subject: [ruby-core:100004] [Ruby master Bug#17159] extend `define_method` for Ractor Issue #17159 has been updated by shyouhei (Shyouhei Urabe). Description updated ### The reason I use `#define_method` (4) I sometimes use it to alias _a part_ of a module, like this: ```ruby class Foo %i[sin cos tan].each do |sym| define_method(sym, Math.instance_method(sym)) end end p Foo.new.sin(3.14) ``` There seems be no reason to reject such usages. ### Capturing local variables C++ since C++11 have had lambdas. In the language you can explicitly specify how you want to capture a variable each time when you create a lambda. As of C++20 there are 12 different specifier. Some of them exist for template metaprogramming (we can ignore such things), but I think there are several interesting cases. ```C++ #include int main() { int x, y, z; x = y = z = 1; auto f = [x, &y, &z]() mutable { auto g = [x, y, &z]() mutable { printf("#1: x, y, z = %d, %d, %d\n", x, y, z); x = y = z = 4; }; x = y = z = 3; g(); }; x = y = z = 2; f(); printf("#2: x, y, z = %d, %d, %d\n", x, y, z); } ``` This program outputs ``` #1: x, y, z = 1, 2, 3 #2: x, y, z = 2, 3, 4 ``` Compilicated? But the `s: s` proposal is very much like this. You can mix call-by-reference and call-by-value. I agree this gives us maximum freedom, but at a cost of complexity. C++ also has simpler specifier which has no such headaches: ```C++ #include int main() { int x, y, z; x = y = z = 1; auto f = [=]() mutable { auto g = [=]() mutable { printf("#1: x, y, z = %d, %d, %d\n", x, y, z); x = y = z = 4; }; x = y = z = 3; g(); }; x = y = z = 2; f(); printf("#2: x, y, z = %d, %d, %d\n", x, y, z); } ``` The above should print: ``` #1: x, y, z = 1, 1, 1 #2: x, y, z = 2, 2, 2 ``` And I think this behaviour is much more understandable. The `ractorise: true` proposal is on this line. I'd push this way. ---------------------------------------- Bug #17159: extend `define_method` for Ractor https://bugs.ruby-lang.org/issues/17159#change-87553 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal * Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN ---------------------------------------- Ractor prohibits use of non-isolated `Proc`s. Non-isolated example is here: ```ruby s = "foo" pr = Proc.new{ p s } ``` This Proc `pr` can not be shared among ractors because outer variable `s` can contain an unshareable object. Also outer binding is a mutable object. Sharing it can lead race conditions. Because of these reasons, `define_method` is also a problem on a multi-Ractor program. (current implementation allows it just because check is not implemented, and it leads BUG). I think there are several patterns when `define_method` is needed. (1) To choose method names on-the-fly ```ruby name = ... define_method(name){ nil } ``` (2) To embed variables to the code ```ruby 10.times{|i| define_method("foo{i}"){ i } } ``` (3) To use global state by local variables ```ruby cnt = 0 define_method("inc"){ cnt += 1 } ``` (4) Others I can't imagine ---- (1) is easy. We can allow `define_method(name, &Proc{nil}.isoplate)`. (3) can never be OK. It introduces data races/race conditions. For this purpose one need to use shared hash. ```ruby STATE = SharedHash.new(cnt: 0) define_method("inc"){ STATE.transaction{ STATE[:cnt] += 1 }} ``` I think there are many (2) patterns that should be saved. To help (2) pattern, the easiest way is to use `eval`. ```ruby 10.times{|i| eval("def foo#{i} #{i}; end") } ``` However, `eval` has several issues (it has huge freedom to explode the program, editor's syntax highlighting and so on). Another approach is to embed the current value to the code, like this: ```ruby i = 0 define_method("foo", ractorise: true){ i } #=> equivalent to: # define_method("foo"){ 0 } # so that if outer scope's i changed, not affected. i = 1 foo #=> 0 s = "" define_method("bar", ractorise: true){ s } #=> equivalent to: # define_method("bar"){ "" } # so that if outer scope's s or s's value, it doesn't affect s << "x" bar #=> "" ``` However, it is very differenct from current Proc semantics. Another idea is to specify embedding value like this: ```ruby i = 0 define_method("foo", i: i){ i } #=> equivalent to: # define_method("foo"){ 0 } # so that if outer scope's i changed, not affected. i = 1 foo #=> 0 s = "" define_method("bar", s: s){ s } #=> equivalent to: # define_method("bar"){ "" } # so that if outer scope's s or s's value, it doesn't affect s << "x" bar #=> "" ``` `i: i` and `s: s` are redundant. However, if there are no outer variable `i` or `s`, the `i` and `s` in blocks are compiled to `send(:i)` or `send(:s)`. But I agree these method invocation should be replaced is another idea. Thoughts? Thanks, Koichi -- https://bugs.ruby-lang.org/ Unsubscribe: