ポリグロット・プログラミング

TruffleRubyを使用すると、他のTruffle言語とインタフェースして、複数の言語で記述されたポリグロット・プログラムを作成できます。

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

ネイティブ構成を使用する場合、他の言語にアクセスするには、--polyglotフラグを使用することが必要になります。JVM構成では、自動的に他の言語にアクセスできます。

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

別の言語のコンテキストAPIからRubyコードに対してevalを使用し、Sourceを対話型としてマークすると、毎回同じ対話型トップレベル・バインディングが使用されます。つまり、あるevalでローカル変数を設定すると、次からそれを使用できるようになります。

セマンティクスは、対話型のSourceを使用してすべてのContext.eval()コールに対してINTERACTIVE_BINDING.eval(code)をコールするRubyセマンティクスと同じです。これは、ほとんどのREPLセマンティクスに似ています。

外部言語で書かれたコードのロード

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 ProcMethodUnboundMethodなどをコールします。

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.fooobject.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とみなされる場合、外部値はブールに変換されます。

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

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

Java相互運用性はJVMモード(--jvm)で使用する方が簡単です。Java相互運用性はネイティブ・モードでもサポートされていますが、より多くの設定が必要です。詳細は、ここを参照してください。

Java.type('name')は、java.lang.Integerint[]などの名前が指定されると、Java型を返します。型オブジェクトを使用すると、.newによってインスタンスが作成され、.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の一部であるポリグロット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)

プロセス、ラムダおよびメソッドの実行

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の文字列と記号は、外部言語に渡されるとJava文字列に変換され、Java文字列はRubyに渡されるとRuby文字列に変換されます。

スレッドと相互運用

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

このオプションを有効にすると、timeoutモジュールはタイムアウトが無視されていることを警告し、シグナル・ハンドラはシグナルが捕捉されたことを警告しますが、どちらの機能でも新しいスレッドを開始する必要があるため、ハンドラは実行されません。

埋込み構成

Rubyランチャの外部で使用する場合(たとえば、別の言語のランチャからポリグロット・インタフェースを介して使用する場合、ネイティブのポリグロット・ライブラリを使用して埋め込む場合、またはJavaアプリケーションにGraalVM SDKを介して埋め込む場合)、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とシステム、および他の埋込みコンテキストやホストシステムと干渉する可能性のあるネイティブメモリー・ルーチンを含む大規模なコア・ライブラリがあります。