Ractorで複数のオブジェクトを渡せるようにしてみた

## 結論

Ractorで以下のコードを動くようにしてみました。

```ruby
r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.send(1, 2)

r.take
# => 1
# => 2
# => Integer
# => Integer
```

## Ractorって？

Ruby3で導入される並行・並列機能を提供するしくみです。
元々はGuildという名前で数年前から議論されてきたものです。

詳しい話は下記の動画を参照して頂ければと思います。

[[JA] Ractor report / Koichi Sasada @ko1](https://www.youtube.com/watch?v=40t8EPpnujg)

## Ractorへオブジェクトを渡す

`send`メソッドを使ってRactorへとオブジェクトを渡すことができます。

```ruby
r = Ractor.new do
    v = Ractor.recv
    puts v
    puts v.class
end

r.send(1)

r.take
# => 1
# => Integer
```

しかし、以下のように複数のオブジェクトを渡すことはできません。

```ruby
r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.send(1, 2)

r.take
# =>wrong number of arguments (given 2, expected 1) (ArgumentError)
```

ただし配列で渡す分にはOKみたいです。

```ruby
r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.send([1, 2])

r.take
# => 1
# => 2
# => Integer
# => Integer
```

実装を見てみると以下のようになっています(CRubyのソースコード内の`ractor.rb`にて)
現在の`send`メソッドは一つのオブジェクトのみを引数に受け取ります。またムーヴして良いかどうかをキーワード引数`move`で指定することもできます。

```ruby
  def send obj, move: false
    __builtin_cexpr! %q{
      ractor_send(ec, RACTOR_PTR(self), obj, move)
    }
  end
```

`__builtin_cexpr!`でCの関数を呼び出し、メソッドが受け取った引数をそのままCの関数に渡しています。余談ですが、最近のCRubyでは内部実装としてRubyの変数をCの関数に渡すようなコードを書くことができるようになっています。

## やったこと

Ractorの`send`メソッドを以下のように書き替えてみました。

```ruby
  def send obj, *arg, move: false
    obj = arg.unshift obj unless arg.empty?
    __builtin_cexpr! %q{
      ractor_send(ec, RACTOR_PTR(self), obj, move)
    }
  end
```

まず、`send`メソッドは必ず一つのオブジェクトを引数として受け取っています。その挙動を維持するために`obj, *arg, move: false`のように引数を書き換えています。
また`send(1, 2)`のように複数オブジェクトが渡された場合は`*arg`に配列として引数が渡されます。

`arg`が空の配列でない場合、複数オブジェクトが渡されていることになり、最終的にCの関数に渡される`obj`を第一引数と可変長引数をマージしたものへと変換しています。

あとは修正したCRubyのソースコードをビルドすればOKです。

これで以下のようにRactorに複数のオブジェクトを渡すことができます。

```ruby
r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.send(1, 2)

r.take
# => 1
# => 2
# => Integer
# => Integer
```

## 参考

ref: [Guild → Ractor](http://atdot.net/~ko1/activities/2020_ruby3summit.pdf)
ref: [https://github.com/ko1/ruby/blob/ractor/ractor.ja.md](https://github.com/ko1/ruby/blob/ractor/ractor.ja.md)
ref: [[JA[ Ractor report / Koichi Sasada @ko1](https://www.youtube.com/watch?v=40t8EPpnujg)

## 追記

ちなみに、モンキーパッチでよければ以下のようにラップしたメソッドを作成すればOKです

```ruby
class Ractor
    def multi_send(obj, *args, move: true)
        obj = args.unshift obj unless args.empty?
        send(obj, move: move)
    end 
end

r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.multi_send(1, 2)

r.take
```

モンキーパッチだと範囲が広いので実務とかで使うなら`refinements`使った方が影響が少ないのでいいかもしれない。

```ruby
module RefineRactor
    refine Ractor do
        def multi_send(obj, *args, move: true)
            obj = args.unshift obj unless args.empty?
            send(obj, move: move)
        end 
    end
end

using RefineRactor

r = Ractor.new do
    v1, v2 = Ractor.recv
    puts v1
    puts v2
    puts v1.class
    puts v2.class
end

r.multi_send(1, 2)

r.take

```