Experimental feature in GraalVM

多言語プログラミング

TruffleRubyを使用すると、他のTruffle言語と連携して、複数の言語で記述されたプログラムである多言語プログラムを作成できます。

このガイドでは、外国語で記述されたコードのロード方法、言語間のオブジェクトのエクスポートとインポート方法、外国語からのRubyオブジェクトの使用方法、Rubyからの外国語オブジェクトの使用方法、JavaとのインターフェースのためのJava型のロード方法、Javaへの埋め込み方法について説明します。

ネイティブ設定を使用している場合は、他の言語にアクセスするには`--polyglot`フラグを使用する必要があります。JVM設定では、自動的に他の言語にアクセスできます。

他の言語のインストール #

他のGraalVM言語を使用するには、JVMスタンドアロンが必要です。ネイティブスタンドアロンは、追加言語のインストールをサポートしていません。

`ruby`、`llvm`、およびホストJavaの相互運用は、追加のインストールなしで使用できます。

その後、`truffleruby-polyglot-get $LANGUAGE`を使用して他の言語をインストールできます。たとえば、

truffleruby-polyglot-get js
truffleruby-polyglot-get python
truffleruby-polyglot-get wasm
truffleruby-polyglot-get java # for Java on Truffle (aka Espresso)

23.1より前のTruffleRubyバージョンでは、これはGraalVMをインストールし(たとえば、`truffleruby+graalvm`を介して)、`gu install $LANGUAGE`を使用することによって行われました。

別の言語からのRubyコードの実行 #

別の言語のContext APIからRubyコードを`eval`し、`Source`をインタラクティブとしてマークすると、毎回同じインタラクティブなトップレベルバインディングが使用されます。これは、ある`eval`でローカル変数を設定すると、次の`eval`から使用できることを意味します。

セマンティクスは、インタラクティブな`Source`を持つすべての`Context.eval()`呼び出しに対して`INTERACTIVE_BINDING.eval(code)`を呼び出すRubyセマンティクスと同じです。これは、ほとんどのREPLセマンティクスに似ています。

外国語で記述されたコードのロード #

外国語にアクセスするには、rubyコマンドラインに`--polyglot`を渡す必要があることに注意してください。

`Polyglot.eval(id, string)`は、IDで識別される外国語でコードを実行します。

`Polyglot.eval_file(id, path)`は、言語IDで識別されるファイルから外国語でコードを実行します。

`Polyglot.eval_file(path)`は、言語を自動的に決定して、ファイルから外国語でコードを実行します。

Rubyオブジェクトの外国語へのエクスポート #

`Polyglot.export(name, value)`は、指定された名前で値をエクスポートします。

`Polyglot.export_method(name)`は、トップレベルオブジェクトで定義されたメソッドをエクスポートします。

外国語オブジェクトのRubyへのインポート #

`Polyglot.import(name)`は、指定された名前の値をインポートして返します。

`Polyglot.import_method(name)`は、`IS_EXECUTABLE`である必要がある値を指定された名前でインポートし、トップレベルオブジェクトで定義します。

外国語からのRubyオブジェクトの使用 #

JavaScriptを例として使用します。左側の例はJavaScript、右側の例はRubyコードで表現されたRubyオブジェクトに対して実行される対応するアクションです。

`object[name/index]`は、オブジェクトに`[]`メソッドがある場合は`object[name/index]`を呼び出し、名前が`@`で始まる場合はインスタンス変数を読み取り、そうでない場合は名前のバインドされたメソッドを返します。

`object[name/index] = value`は、オブジェクトに`[]= `メソッドがある場合は`object[name/index] = value`を呼び出し、名前が`@`で始まる場合はインスタンス変数を設定します。

`delete object.name`は`object.delete(name)`を呼び出します。

`delete object[name/index]`は`object.delete(name)`を呼び出します。

`object.length`は`object.size`を呼び出します。

`Object.keys(hash)`は、ハッシュキーを文字列として返します。

`Object.keys(object)`は、オブジェクトのメソッドを関数として返します。ただし、オブジェクトに `[]` メソッドがある場合は、空の配列を返します。

`object(args...)`は、Rubyの`Proc`、`Method`、`UnboundMethod`などを呼び出します。

`object.name(args...)`は、Rubyオブジェクトのメソッドを呼び出します。

`new object(args...)`は`object.new(args...)`を呼び出します。

`"length" in obj`は、Rubyの`Array`に対して`true`を返します。

`object == null`は`object.nil?`を呼び出します。

外国語で使用するRubyオブジェクトの作成に関する注意事項 #

フィールドを読み書きするためにRubyオブジェクトを別の言語に渡す場合、渡すのに適したオブジェクトは通常`Struct`です。これは、Rubyから使用できる`object.foo`と`object.foo = value`の両方のアクセサーがあり、`object['foo']`と`object['foo'] = value`にも応答するため、読み取りおよび書き込みメッセージを送信する他の言語から機能します。

Rubyからの外国語オブジェクトの使用 #

`object[name/index]`は、外国語オブジェクトからメンバーを読み取ります。

`object[name/index] = value`は、外国語オブジェクトに値を書き込みます.

`object.delete(name/index)`は、外国語オブジェクトから値を削除します.

`object.size`は、外国語オブジェクトのサイズまたは長さを取得します.

`object.keys`は、外国語オブジェクトのメンバーの配列を取得します.

`object.call(*args)`は、外国語オブジェクトを実行します.

`object.name(*args)`は、外国語オブジェクトで`name`と呼ばれるメソッドを呼び出します.

`object.new(*args)`は、外国語オブジェクトから新しいオブジェクトを作成します(クラスであるかのように)。

`object.respond_to?(:size)`は、外国語オブジェクトにサイズまたは長さがあるかどうかを通知します.

`object.nil?`は、外国語オブジェクトが言語の`null`または`nil`に相当するかどうかを通知します.

`object.respond_to?(:call)`は、外国語オブジェクトを実行できるかどうかを通知します.

`object.respond_to?(:new)`は、外国語オブジェクトを使用して新しいオブジェクトを作成できるかどうかを通知します(クラスの場合)。

`Polyglot.as_enumerable(object)`は、そのサイズまたは長さを使用し、それから読み取ることで、外国語オブジェクトからRubyの`Enumerable`を作成します.

ブール値が予期される場合(たとえば、`if`条件)、外国語の値は可能であればブール値に変換されるか、trueと見なされます.

外国語例外のレスキュー #

外国語例外は、`rescue Polyglot::ForeignException => e`または`rescue foreign_meta_object`によってキャッチできます。 `rescue Exception => e`を使用して、任意の例外(Rubyまたは外国語)をレスキューできます.

これは、外国語例外の先祖から自然に発生します

Java.type("java.lang.RuntimeException").new.class.ancestors
# => [Polyglot::ForeignException, Polyglot::ExceptionTrait, Polyglot::ObjectTrait, Exception, Object, Kernel, BasicObject]

Javaオブジェクトへのアクセス #

TruffleRubyのJava相互運用性インターフェースは、GraalVMのJavaScript実装によっても実装されている、Nashorn JavaScript実装のインターフェースに似ています.

JVMモード(`--jvm`)では、Javaの相互運用性を使用する方が簡単です。Javaの相互運用性はネイティブモードでもサポートされていますが、より多くのセットアップが必要です。詳細については、こちらをご覧ください.

`Java.type('name')`は、`java.lang.Integer`や`int[]`などの名前が与えられると、Java型を返します。型オブジェクトを使用すると、`.new`はインスタンスを作成し、`.foo`は静的メソッド`foo`を呼び出し、`.FOO`または`[:FOO]`は静的フィールド`FOO`を読み取ります。 `java.lang.Class`インスタンスのメソッドにアクセスするには、`MyClass[:class].getName`のように`[:class]`を使用します。 `[:static]`を使用して、`java.lang.Class`インスタンスからJava型に移動することもできます.

エンクロージャモジュールにJavaクラスをインポートするには、`MyClass = Java.type 'java.lang.MyClass'`または`Java.import 'java.lang.MyClass'`を使用します.

Javaへの埋め込み #

TruffleRubyは、GraalVMの一部であるPolyglot APIを介して埋め込まれます。このAPIを使用するには、GraalVMを使用する必要があります.

import org.graalvm.polyglot.*;

class Embedding {
    public static void main(String[] args) {
        Context polyglot = Context.newBuilder().allowAllAccess(true).build();
        Value array = polyglot.eval("ruby", "[1,2,42,4]");
        int result = array.getArrayElement(2).asInt();
        System.out.println(result);
    }
}

埋め込みJavaからのRubyオブジェクトの使用 #

Rubyオブジェクトは、Javaに埋め込まれると、`Value`クラスによって表されます.

配列へのアクセス #

boolean hasArrayElements()
Value getArrayElement(long index)
void setArrayElement(long index, Object value)
boolean removeArrayElement(long index)
long getArraySize()

オブジェクト内のメソッドへのアクセス #

boolean hasMembers()
boolean hasMember(String identifier)
Value getMember(String identifier)
Set<String> getMemberKeys
void putMember(String identifier, Object value
boolean removeMember(String identifier)

Proc、ラムダ、およびメソッドの実行 #

boolean canExecute()
Value execute(Object... arguments)
void executeVoid(Object... arguments)

クラスのインスタンス化 #

boolean canInstantiate() {
Value newInstance(Object... arguments)

プリミティブへのアクセス #

boolean isString()
String asString()
boolean isBoolean()
boolean asBoolean()
boolean isNumber()
boolean fitsInByte()
byte asByte()
boolean fitsInShort()
short asShort()
boolean fitsInInt()
int asInt()
boolean fitsInLong()
long asLong()
boolean fitsInDouble()
double asDouble()
boolean fitsInFloat()
float asFloat()
boolean isNull()

JRuby移行ガイドには、さらにいくつかの例が含まれています.

スレッドと相互運用 #

Rubyはマルチスレッド言語として設計されており、エコシステムの多くはスレッドが使用可能であることを期待しています。これは、スレッドをサポートしていない他のTruffle言語と互換性がない可能性があるため、`--single-threaded`オプションを使用して複数スレッドの作成を無効にすることができます。このオプションは、以下で説明する埋め込み構成の一部として、Rubyランチャーが使用されない限り、デフォルトで設定されます.

このオプションが有効になっている場合、`timeout`モジュールはタイムアウトが無視されていることを警告し、シグナルハンドラはシグナルがキャッチされたことを警告しますが、ハンドラを実行しません。これらの両方の機能は新しいスレッドを開始する必要があるためです.

埋め込み構成 #

Rubyランチャー以外で使用する場合 - 例えば、ポリグロットインターフェースを介して別の言語のランチャーから、ネイティブポリグロットライブラリを使用して埋め込む場合、またはGraalVM SDKを介してJavaアプリケーションに埋め込む場合 - TruffleRubyは、別のアプリケーション内でより協調的に動作するように自動的に構成されます。これには、割り込みシグナルハンドラをインストールしない、Graal SDKからのI/Oストリームを使用するなどのオプションが含まれます。また、上記のようにシングルスレッドモードも有効になります。

埋め込み時にうまく動作しない可能性のある操作(独自のシグナルハンドラのインストールなど)を明示的に実行すると、警告が表示されます。

これは、埋め込みの場合でも、embeddedオプション(別のランチャーからは--ruby.embedded=false、通常のJavaアプリケーションからは-Dpolyglot.ruby.embedded=false)を使用して無効にすることができます。

これは別のオプションですが、埋め込み構成では、Context.BuilderallowNativeAccess(false)を設定するか、実験的な--platform-native=falseオプションを使用して、内部機能のNFIの使用を無効にすることをお勧めします。

また、実験的なオプション--cexts=falseは、C拡張機能を無効にすることができます。

注:純粋なJavaScriptなどとは異なり、Rubyは自己完結型の式言語以上のものです。低レベルのI/O、システム、ネイティブメモリルーチンを含む大規模なコアライブラリがあり、他の埋め込みコンテキストまたはホストシステムと干渉する可能性があります。

内部コンテキスト #

TruffleRubyは、*内部コンテキスト*、つまり複数の分離された実行/評価コンテキストの作成をサポートしています(これはGraalVM言語で一般的にサポートされています)。概念的には、同じプロセスで複数のRubyインタープリターを実行するのと似ています。これは他の言語でも使用できるため、たとえば、外部/デフォルトのコンテキストでRubyコードを実行し、いくつかの内部コンテキストでJavaScriptコードを実行することができます。これは、JavaScriptのように共有メモリマルチスレッドをサポートしていない言語と相互運用する場合に非常に役立ちます。スレッドごとに1つ以上の内部コンテキストを作成し、外部コンテキストでマルチスレッドRubyを使用できます。

内部コンテキストのオブジェクトは他のコンテキストに渡すことができ、それらは外部オブジェクトとして扱われます。

Polyglot::InnerContext.new do |context|
  context.eval('ruby', "p Object.new") # prints #<Object:0xd8>
  p context.eval('ruby', "Object.new") # prints #<Polyglot::ForeignObject[Ruby] Object:0x131d576b>
end

これは、コンテキストを離れるすべてのオブジェクトをプロキシで自動的にラップすることで機能します。つまり、たとえば、外部オブジェクトでメソッドが呼び出された場合、そのオブジェクトが属するコンテキストで実行されます。

お問い合わせ