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 <cstdio>

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 <cstdio>

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: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>