結論
Ractorで以下のコードを動くようにしてみました。
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
Ractorへオブジェクトを渡す
sendメソッドを使ってRactorへとオブジェクトを渡すことができます。
r = Ractor.new do v = Ractor.recv puts v puts v.class end r.send(1) r.take # => 1 # => Integer
しかし、以下のように複数のオブジェクトを渡すことはできません。
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みたいです。
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で指定することもできます。
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メソッドを以下のように書き替えてみました。
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に複数のオブジェクトを渡すことができます。
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
ref: https://github.com/ko1/ruby/blob/ractor/ractor.ja.md
ref: [[JA Ractor report / Koichi Sasada @ko1
追記
ちなみに、モンキーパッチでよければ以下のようにラップしたメソッドを作成すればOKです
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使った方が影響が少ないのでいいかもしれない。
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