Experimental feature in GraalVM

JRuby から TruffleRuby への移行

Gem やアプリケーションで TruffleRuby を試す際は、TruffleRuby チームにご連絡いただくことをお勧めします。

デプロイメント #

JRuby から移行する場合、TruffleRuby を使用する最も簡単な方法は、TruffleRuby JVM スタンドアロンをインストールすることでしょう。

TruffleRuby の Java 相互運用機能が必要ない場合は、TruffleRuby ネイティブスタンドアロンをインストールできます。

Java からの Ruby の使用 #

JRuby は、JSR 223 (javax.script としても知られています)、Bean Scripting Framework (BSF)、JRuby Embed (Red Bridge としても知られています)、および JRuby 直接埋め込み API など、さまざまな方法で Java に Ruby を埋め込むことをサポートしています。

TruffleRuby を埋め込むための最良の方法は、Polyglot API を使用することです。この API は、Ruby だけではなく、多くの言語をサポートするように設計されているため、異なります。

TruffleRuby は、レガシー JRuby コードの実行を容易にするために、JRuby と互換性のある JSR 223 もサポートしています。使用方法については、このドキュメントを参照してください。

Polyglot API を使用するには、JVM スタンドアロンを使用するか、org.graalvm.polyglot:polyglot Maven パッケージに依存する必要があります。

Java を含む他の言語から Ruby を使用する方法の詳細については、ポリグロットドキュメントを参照してください。このドキュメントでは、JRuby との比較のみを示します。

コンテキストの作成 #

JRuby で JSR 223 を使用する場合、次のように記述していたでしょう

ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine scriptEngine = m.getEngineByName("ruby");

または BSF を使用する場合、次のように記述していたでしょう

BSFManager.registerScriptingEngine("jruby", "org.jruby.embed.bsf.JRubyEngine", null);
BSFManager bsfManager = new BSFManager();

または JRuby Embed を使用する場合、次のように記述していたでしょう

ScriptingContainer container = new ScriptingContainer();

または直接埋め込み API を使用する場合、次のように記述していたでしょう

Ruby ruby = Ruby.newInstance(new RubyInstanceConfig());

TruffleRuby では、次のように記述します

Context polyglot = Context.newBuilder().allowAllAccess(true).build();

allowAllAccess(true) メソッドは、Ruby が完全に機能するために必要な寛容なアクセス権を許可します。GraalVM はデフォルトで、ネイティブファイルアクセスなど、安全ではない可能性のある多くの特権を許可しませんが、通常の Ruby インストールではこれらを使用するため、有効にします。これらの特権を付与しないことを選択できますが、これにより Ruby の一部の機能が制限されます。

// No privileges granted, restricts functionality
Context polyglot = Context.newBuilder().build();

通常、コンテキストが適切に破棄されるように、try ブロック内にコンテキストを作成します

try (Context polyglot = Context.newBuilder().allowAllAccess(true).build()) {
}

Context の詳細については、Context API を参照してください。

オプションの設定 #

システムプロパティ、または .option(name, value) ビルダーメソッドを介して、TruffleRuby オプションを設定できます。

コードの評価 #

JRuby でこれらの JRuby 例のいずれかを記述していた場合、使用可能なオプションが提供されます

scriptEngine.eval("puts 'hello'");
bsfManager.exec("jruby", "<script>", 1, 0, "puts 'hello'");
container.runScriptlet("puts 'hello'");
ruby.evalScriptlet("puts 'hello'");

TruffleRuby では、これを次のように記述します

polyglot.eval("ruby", "puts 'hello'");

eval は複数の言語をサポートしているため、毎回言語を指定する必要があることに注意してください。

パラメーター付きのコードの評価 #

JRuby で JSR 223 を使用する場合、バインディングと呼ばれるパラメーターをスクリプトに渡すことができます

Bindings bindings = scriptEngine.createBindings();
bindings.put("a", 14);
bindings.put("b", 2);
scriptEngine.eval("puts a + b", bindings);

TruffleRuby では、eval メソッドはパラメーターを取りません。代わりに、パラメーターを受け取る proc を返し、この値で execute を呼び出す必要があります

polyglot.eval("ruby", "-> a, b { puts a + b }").execute(14, 2);

プリミティブ値 #

異なる埋め込み API は、プリミティブ値を異なる方法で処理します。JSR 223、BSF、および JRuby Embed では、戻り値の型は Object であり、long のようなプリミティブにキャストでき、instanceof でチェックできます。直接埋め込み API では、戻り値はルート IRubyObject インターフェイスであり、プリミティブを Integer に変換し、そこから Java の long に変換する必要があります

(long) scriptEngine.eval("14 + 2");
(long) bsfManager.eval("jruby", "<script>", 1, 0, "14 + 2");
(long) container.runScriptlet("14 + 2");
ruby.evalScriptlet("14 + 2").convertToInteger().getLongValue();

TruffleRuby では、戻り値は常にカプセル化された Value オブジェクトであり、オブジェクトで可能な場合は long としてアクセスできます。fitsInLong() でこれをテストできます

polyglot.eval("ruby", "14 + 2").asLong();

メソッドの呼び出し #

eval から取得したオブジェクト、またはその他のオブジェクトのメソッドを呼び出すには、JRuby 埋め込み API では、コンテキストにメソッドを呼び出すように要求するか、直接埋め込みの場合は、レシーバーでメソッドを呼び出し、引数を JRuby 型にマーシャリングする必要があります。BSF には、メソッドを呼び出す方法がないようです

((Invocable) scriptEngine).invokeMethod(scriptEngine.eval("Math"), "sin", 2);
container.callMethod(container.runScriptlet("Math"), "sin", 2);
ruby.evalScriptlet("Math").callMethod(ruby.getCurrentContext(), "sin", new IRubyObject[]{ruby.newFixnum(2)})

TruffleRuby では、Value クラスに、オブジェクトの Ruby メソッドを返す getMember メソッドがあり、execute を呼び出すことで呼び出すことができます。引数をマーシャリングする必要はありません

polyglot.eval("ruby", "Math").getMember("sin").execute(2);

プリミティブでメソッドを呼び出すには、ラムダを使用します

polyglot.eval("ruby", "-> x { x.succ }").execute(2).asInt();

ブロックの渡し方 #

ブロックは Ruby 固有の言語機能であるため、JSR 223 や BSF のような言語に依存しない API には表示されません。JRuby Embed API と直接埋め込みでは、callMethod メソッドに Block パラメーターを渡すことができますが、使用する Block オブジェクトをどのように作成するかは不明確です。

TruffleRuby では、呼び出しを実行する Ruby ラムダを返し、渡す Java ラムダを実行するブロックを渡す必要があります

polyglot.eval("ruby", "-> block { (1..3).each { |n| block.call n } }")
  .execute(polyglot.asValue((IntConsumer) n -> System.out.println(n)));

オブジェクトの作成 #

JRuby 埋め込み API は新しいオブジェクトの作成をサポートしていませんが、自分で new メソッドを呼び出すことができます

((Invocable) scriptEngine).invokeMethod(scriptEngine.eval("Time"), "new", 2021, 3, 18);
container.callMethod(container.runScriptlet("Time"), "new", 2021, 3, 18)
ruby.evalScriptlet("Time").callMethod(ruby.getCurrentContext(), "new",
  new IRubyObject[]{ruby.newFixnum(2021), ruby.newFixnum(3), ruby.newFixnum(8)})

TruffleRuby では、newInstance を使用して Ruby の class からオブジェクトを作成できます。canInstantiate を使用して、これが可能かどうかを確認できます

polyglot.eval("ruby", "Time").newInstance(2021, 3, 18);

文字列の処理 #

JRuby の埋め込み API では、toString を使用して Java の String に変換します。TruffleRuby では asString を使用します (および確認するには isString を使用します)。

配列へのアクセス #

JRuby の配列は List<Object> を実装しているため、このインターフェイスにキャストしてアクセスできます

((List) scriptEngine.eval("[3, 4, 5]")).get(1);
((List) container.runScriptlet("[3, 4, 5]")).get(1);
((List) bsfManager.eval("jruby", "<script>", 1, 0, "[3, 4, 5]")).get(1);
((List) ruby.evalScriptlet("[3, 4, 5]")).get(1);

TruffleRuby では、getArrayElementsetArrayElement、および getArraySize を使用するか、as(List.class) を使用して List<Object> を取得できます

polyglot.eval("ruby", "[3, 4, 5]").getArrayElement(1);
polyglot.eval("ruby", "[3, 4, 5]").as(List.class).get(1);

ハッシュへのアクセス #

JRuby のハッシュは Map<Object, Object> を実装しているため、このインターフェイスにキャストしてアクセスできます

((Map) scriptEngine.eval("{'a' => 3, 'b' => 4, 'c' => 5}")).get("b");
((Map) scriptEngine.eval("{3 => 'a', 4 => 'b', 5 => 'c'}")).get(4);

TruffleRuby では、現在、ハッシュまたは辞書のようなデータ構造にアクセスするための統一された方法はありません。現時点では、ラムダアクセサーを使用することをお勧めします

Value hash = polyglot.eval("ruby", "{'a' => 3, 'b' => 4, 'c' => 5}");
Value accessor = polyglot.eval("ruby", "-> hash, key { hash[key] }");
accessor.execute(hash, "b");

インターフェイスの実装 #

Ruby オブジェクトを使用して Java インターフェイスを実装したい場合があります (JRuby wiki からコピーした例)

interface FluidForce {
  double getFluidForce(double a, double b, double depth);
}
class EthylAlcoholFluidForce
  def getFluidForce(x, y, depth)
    area = Math::PI * x * y
    49.4 * area * depth
  end
end

EthylAlcoholFluidForce.new
String RUBY_SOURCE = "class EthylAlcoholFluidForce\n  def getFluidForce...";

JSR 223 では、getInterface(object, Interface.class) を使用できます。JRuby Embed では、getInstance(object, Interface.class) を使用できます。直接埋め込みでは、toJava(Interface.class) を使用できます。BSF はインターフェイスの実装をサポートしていないようです

FluidForce fluidForce = ((Invocable) scriptEngine).getInterface(scriptEngine.eval(RUBY_SOURCE), FluidForce.class);
FluidForce fluidForce = container.getInstance(container.runScriptlet(RUBY_SOURCE), FluidForce.class);
FluidForce fluidForce = ruby.evalScriptlet(RUBY_SOURCE).toJava(FluidForce.class);
fluidForce.getFluidForce(2.0, 3.0, 6.0);

TruffleRuby では、as(Interface.class) を使用して、Ruby オブジェクトによって実装されたインターフェイスを取得できます

FluidForce fluidForce = polyglot.eval("ruby", RUBY_SOURCE).as(FluidForce.class);
fluidForce.getFluidForce(2.0, 3.0, 6.0);

JRuby では、Ruby の規約を使用して、Ruby メソッドの名前を get_fluid_force にすることができます。Java の規約を使用して getFluidForce にすることはできません。TruffleRuby は現時点ではこれをサポートしていません。

ラムダの実装 #

知る限り、JSR 223、BSF、JRuby Embed、および直接埋め込みには、Ruby ラムダから Java ラムダを取得するための便利な方法がありません。

TruffleRuby では、as(FunctionalInterface.class) を使用して、Ruby ラムダから Java ラムダ (実際には関数型インターフェイスの実装) を取得できます

BiFunction<Integer, Integer, Integer> adder = polyglot.eval("ruby", "-> a, b { a + b }").as(BiFunction.class);
adder.apply(14, 2).intValue();

一度解析して複数回実行 #

一部の JRuby 埋め込み API では、スクリプトを一度コンパイルしてから複数回 eval できるようにします

CompiledScript compiled = ((Compilable) scriptEngine).compile("puts 'hello'");
compiled.eval();

TruffleRuby では、解析からラムダを返し、これを複数回実行するだけです。他の Ruby コードと同様に最適化されます

Value parsedOnce = polyglot.eval("ruby", "-> { run many times }");
parsedOnce.execute();

Ruby から Java の使用 #

TruffleRuby は、GraalVM のどの言語からでも、他の GraalVM 言語へ使用できる一貫した Java 相互運用性のための独自のスキームを提供します。これは既存の JRuby-Java 相互運用性とは互換性がないため、移行する必要があります。

一般的なポリグロットプログラミングについては、別の場所で文書化されています。このセクションでは、JRuby との相対的な関係について説明します。

この例は JRuby wiki からのものです

require 'java'

# With the 'require' above, you now can refer to things that are part of the
# standard Java platform via their full paths.
frame = javax.swing.JFrame.new("Window") # Creating a Java JFrame
label = javax.swing.JLabel.new("Hello World")

# You can transparently call Java methods on Java objects, just as if they were defined in Ruby.
frame.add(label)  # Invoking the Java method 'add'.
frame.setDefaultCloseOperation(javax.swing.WindowConstants::EXIT_ON_CLOSE)
frame.pack
frame.setVisible(true)
sleep

TruffleRuby では、代わりにこの方法で記述します

Java.import 'javax.swing.JFrame'
Java.import 'javax.swing.JLabel'
Java.import 'javax.swing.WindowConstants'

frame = JFrame.new("Window")
label = JLabel.new("Hello World")

frame.add(label)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.pack
frame.setVisible(true)
sleep

Java パッケージ名をシミュレートするために Ruby メタプログラミングを使用する代わりに、クラスを明示的にインポートします。Java.import は JRuby の java_import に似ており、ClassName = Java.type('package.ClassName') を実行します。

定数は、Ruby 表記を使用するのではなく、クラスのプロパティを読み取ることで読み取られます。

Java の要求 #

TruffleRuby では require 'java' を使用しないでください。ただし、--jvm モードで実行する必要があります。これはネイティブスタンドアロンでは使用できません。

クラスの参照 #

JRuby では、Java クラスは Java::ComFoo::Bar のように Java モジュールで参照するか、共通の TLD がある場合は com.foo.Bar として参照できます。java_import com.foo.Bar は、Bar をトップレベルの定数として定義します。

TruffleRubyでは、JavaクラスはJava.type('com.foo.Bar')を使って参照します。これは通常、定数に代入します。または、Java.import 'com.foo.Bar'を使って、囲んでいるモジュール内でBarを定義することもできます。

ワイルドカードパッケージインポート #

JRubyでは、include_package 'com.foo'を使用すると、そのパッケージ内のすべてのクラスが現在のスコープで定数として利用できるようになります。

TruffleRubyでは、クラスを明示的に参照します。

メソッドの呼び出しとインスタンスの作成 #

JRubyとTruffleRubyの両方で、JavaメソッドはRubyメソッドと同様に呼び出します。

JRubyは、my_methodのようなメソッド名をJavaの慣例であるmyMethodに書き換え、getFoofooに、setFoofoo=に変換します。TruffleRubyはこれらの変換を行いません。

オーバーロードされたメソッドの呼び出し #

複数のオーバーロードが可能な場合は、明示的に選択する必要があります。たとえば、java.util.concurrent.ExecutorServiceには、submit(Runnable)submit(Callable<T> task)の両方があります。どちらかを指定せずにsubmitを呼び出すと、可能なオーバーロードが表示されます。

$ ruby -e 'Java.type("java.util.concurrent.Executors").newFixedThreadPool(1).submit {}'
-e:1:in `main': Multiple applicable overloads found for method name submit (candidates: [
  Method[public java.util.concurrent.Future java.util.concurrent.AbstractExecutorService.submit(java.util.concurrent.Callable)],
  Method[public java.util.concurrent.Future java.util.concurrent.AbstractExecutorService.submit(java.lang.Runnable)]],
  arguments: [RubyProc@4893b344 (RubyProc)]) (TypeError)

次のようにして特定のオーバーロードを選択できます。

executor = Java.type("java.util.concurrent.Executors").newFixedThreadPool(1)
executor['submit(java.lang.Runnable)'].call(-> { 1 })
# or
executor.send("submit(java.lang.Runnable)") { 1 }

定数の参照 #

JRubyでは、Javaの定数はRubyの定数としてモデル化され、MyClass::FOOのように参照します。TruffleRubyでは、プロパティとして読み取るために、読み取り表記を使用します。MyClass.FOOまたはMyClass[:FOO]のように参照します。

JARファイルからのクラスの使用 #

JRubyでは、requireを使用してクラスとJARをクラスパスに追加できます。TruffleRubyでは、現時点では通常どおり-classpath JVMフラグを使用します。

追加のJava固有メソッド #

JRubyは、Javaオブジェクトに対してこれらのメソッドを定義しています。代わりに、これらの同等のものを使用してください。

java_class - classを使用します。

java_kind_of? - is_a?を使用します。

java_object - サポートされていません。

java_send - __send__を使用します。

java_method - サポートされていません。

java_alias - サポートされていません。

Java配列の作成 #

JRubyでは、Java::byte[1024].newを使用します。

TruffleRubyでは、Java.type('byte[]').new(1024)を使用します。

Javaインターフェースの実装 #

JRubyには、インターフェースを実装するいくつかの方法があります。たとえば、Swingボタンにアクションリスナーを追加するには、次の3つの方法のいずれかを使用できます。

class ClickAction
  include java.awt.event.ActionListener

  def actionPerformed(event)
   javax.swing.JOptionPane.showMessageDialog nil, 'hello'
  end
end

button.addActionListener ClickAction.new
button.addActionListener do |event|
  javax.swing.JOptionPane.showMessageDialog nil, 'hello'
end
button.addActionListener -> event {
  javax.swing.JOptionPane.showMessageDialog nil, 'hello'
}

TruffleRubyでは、インターフェースを生成するために常に最後のオプションを使用します。

button.addActionListener -> event {
  JOptionPane.showMessageDialog nil, 'hello'
}

実行時のJavaクラスの生成 #

JRubyは、become_java!を使用してRubyクラスを具象Javaクラスに変換することをサポートしています。

TruffleRubyはこれをサポートしていません。JavaとRuby間のインターフェースとして、適切なJavaインターフェースを使用することをお勧めします。

Javaクラスの再オープン #

TruffleRubyではJavaクラスを再オープンできません。

Javaクラスのサブクラス化 #

TruffleRubyではJavaクラスをサブクラス化できません。代わりに、コンポジションまたはインターフェースを使用してください。

Javaを使用したTruffleRubyの拡張 #

JRubyはJavaで記述された拡張機能をサポートしています。これらの拡張機能は、MRI C拡張機能インターフェースと同様に、JRubyの内部全体である非公式なインターフェースに対して記述されています。

TruffleRubyは、現時点ではこのようなJava拡張機能の記述をサポートしていません。上記のように、Javaインターオペラビリティを使用することをお勧めします。

ツール #

スタンドアロンクラスとJAR #

JRubyは、jrubycを使用してRubyからスタンドアロンのソースクラスとコンパイルされたJARへのコンパイルをサポートしています。

TruffleRubyは、RubyコードをJavaにコンパイルすることをサポートしていません。JavaからRubyへのエントリポイントとして、Polyglot APIを使用することをお勧めします。

Warbler #

JRubyは、エンタープライズJava WebサーバーにロードするためのWARファイルの構築をサポートしています。

TruffleRubyは、現時点ではこれをサポートしていません。

VisualVM #

VisualVMは、JRubyと同様にTruffleRubyで動作します。

さらに、ヒープダンプツールを使用すると、VisualVMはJavaオブジェクトではなく、Rubyオブジェクトを理解します。

お問い合わせ