ポリグロット・プログラミング
TruffleRubyを使用すると、他のTruffle言語とインタフェースして、複数の言語で記述されたポリグロット・プログラムを作成できます。
このガイドでは、外部言語で記述されたコードをロードする方法、言語間でオブジェクトをエクスポートおよびインポートする方法、外部言語からRubyオブジェクトを使用する方法、Rubyから外部オブジェクトを使用する方法、Java型をロードしてJavaとインタフェースする方法、およびJavaに埋め込む方法について説明します。
ネイティブ構成を使用する場合、他の言語にアクセスするには、--polyglot
フラグを使用することが必要になります。JVM構成では、自動的に他の言語にアクセスできます。
- 別の言語からのRubyコードの実行
- 外部言語で書かれたコードのロード
- Rubyオブジェクトの外部言語へのエクスポート
- Rubyへの外部オブジェクトのインポート
- 外部言語からのRubyオブジェクトの使用
- Rubyからの外部オブジェクトの使用
- Javaオブジェクトへのアクセス
- スレッドと相互運用
- 埋込み構成
別の言語からの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 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実装のインタフェースに似ています。
Java相互運用性はJVMモード(--jvm
)で使用する方が簡単です。Java相互運用性はネイティブ・モードでもサポートされていますが、より多くの設定が必要です。詳細は、ここを参照してください。
Java.type('name')
は、java.lang.Integer
やint[]
などの名前が指定されると、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はマルチスレッド言語として設計されており、エコシステムの多くはスレッドが使用可能であると想定しています。このことは、スレッドをサポートしていない他のTruffle言語と互換性がない可能性があるため、--single-threaded
オプションを使用して複数のスレッドの作成を無効にできます。このオプションは、次に説明するように、Rubyランチャが埋込み構成の一部として使用されている場合を除き、デフォルトで設定されています。
このオプションを有効にすると、timeout
モジュールはタイムアウトが無視されていることを警告し、シグナル・ハンドラはシグナルが捕捉されたことを警告しますが、どちらの機能でも新しいスレッドを開始する必要があるため、ハンドラは実行されません。
埋込み構成
Rubyランチャの外部で使用する場合(たとえば、別の言語のランチャからポリグロット・インタフェースを介して使用する場合、ネイティブのポリグロット・ライブラリを使用して埋め込む場合、またはJavaアプリケーションにGraalVM SDKを介して埋め込む場合)、TruffleRubyは別のアプリケーション内でより協調的に動作するように自動的に構成されます。これには、割り込みシグナル・ハンドラをインストールしない、Graal SDKからのI/Oストリームを使用するなどのオプションが含まれます。また、前述のようにシングルスレッド・モードも有効になります。
独自のシグナル・ハンドラのインストールなど、埋込み時に適切に機能しない可能性があることを明示的に行った場合は警告も表示されます。
これは、埋め込まれている場合でも、embedded
オプション(別のランチャの場合は--ruby.embedded=false
、通常のJavaアプリケーションの場合は-Dpolyglot.ruby.embedded=false
)を使用して無効にできます。
これは別のオプションですが、埋込み構成では、Context.Builder
にallowNativeAccess(false)
を設定するか、試験段階の--platform-native=false
オプションを使用して、内部機能のNFIの使用を無効にできます。
また、試験段階のオプション--cexts=false
を使用すると、C拡張機能を無効にできます。
ノート: 純粋なJavaScriptなどとは異なり、Rubyの機能は自己完結型の式言語にとどまりません。低レベルのI/Oとシステム、および他の埋込みコンテキストやホストシステムと干渉する可能性のあるネイティブメモリー・ルーチンを含む大規模なコア・ライブラリがあります。