- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (早期アクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
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 では、getArrayElement
、setArrayElement
、および 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
に書き換え、getFoo
をfoo
に、setFoo
をfoo=
に変換します。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オブジェクトを理解します。