The many ways of polyglot programming with GraalVM

原文はこちら。
The original article was written by Michael Simons (software engineer, Neo4j).
https://medium.com/graalvm/3-ways-to-polyglot-with-graalvm-fb28c1542b45

Introduction

私はMichael Simons、Neo4jのソフトウェア・エンジニアとして働いています。Neo4jは同名のグラフデータベースの名前でもあります。私の仕事は、オブジェクトマッピングフレームワーク (Neo4j-OGM) と Spring Data Neo4j に主に注力しています。

Neo4j
https://neo4j.com/
Neo4j-OGM – An Object Graph Mapping Library for Neo4j
https://github.com/neo4j/neo4j-ogm
Spring Data Neo4j
https://spring.io/projects/spring-data-neo4j

ブログ、Twitterなどは以下からどうぞ。

Just another nerd blog: Michael Simons writes about Java, Spring and Software architecture.
https://info.michael-simons.eu/
Twitter
https://twitter.com/rotnroll666

GraalVMブログへの寄稿を招待してくれたOleg、Neo4j、そしてこの仕事をサポートしてくれたMichael Hungerに感謝します。

GraalVMとそのエコシステムについて学ぶための最高のリソースは、実のところリファレンスマニュアルとSlack上のコミュニティサポートと考えています。マニュアルは網羅的にして素晴らしい内容です。

GraalVM Reference Manuals
https://www.graalvm.org/reference-manual/
GraalVM Community
https://www.graalvm.org/community/

First contact

GraalVMと最初に接触したのは2017年にさかのぼります。私はJCrete 2017でJaroslavに会いましたが、彼はGraalにご執心でした。当時、Jaroslavが説明したすべてのことを理解していたかと言えば嘘になりますが、でもその内容はすでにクールに見えました。

数年後の2019年、突然、Javaプログラムのネイティブコンパイルが大きな話題になりました。QuarkusやMicronautのような新しいフレームワークは、GraalVMのnative-imageとSubstrateVMを活用できるようになっています。native-imageは、Javaプログラムからネイティブ実行ファイルを構築する役割を担うGraalVMの機能です。これは、ahead-of-time(AOT)モードのGraalコンパイラを使用してコンパイルします。SubstrateVMは、必要最低限のものだけを装備したランタイムです。これらのプログラムは高速に起動し、多くの場合、メモリフットプリントが削減されます。

Quarkus
https://quarkus.io/
Micronaut
https://micronaut.io/

ちょうどいいときにちょうどいい場所にいたので、Quarkusのコンセプトを説明してくれたSanneに会いました。彼は私にQuarkusのコンセプトを説明するとともに、Neo4j、私たちのデータベースドライバ、そしてドライバをGraalVMネイティブイメージと互換性のあるものできそうか、Quarkusに貢献できそうかを尋ねてきました。

Sanneからの情報で武装して、私はそれを実現することができました。Neo4j Java Driverは、SSLサポートを含むGraalVMネイティブイメージと互換性があります。また、ネイティブSpring Data Neo4jのサポートも提供できました。

Neo4j Java Driver
https://github.com/neo4j/neo4j-java-driver
GraalVM Native ImageをサポートするSpring Data Neo4jのサンプル
https://github.com/spring-projects-experimental/spring-graalvm-native/tree/master/spring-graalvm-native-samples/data-neo4j

このエントリで、native-imageでの作業をこの記事にまとめてみました。

About the tooling available to create native GraalVM images
https://info.michael-simons.eu/2020/09/15/about-the-tooling-available-to-create-native-graalvm-images/

native-imageについては、このエントリの後半で聞くことにします。

Examples

このエントリで使ったサンプルコードは全てGitHubリポジトリからご覧いただけます。

これらのコードはトランザクションや接続などのリソースが漏れないことをテスト済みですが、当然ながらPoC(概念検証)のコードでしかありません。

Truffle Language Implementation Framework

Truffleフレームワーク(または単にTruffle)は、GraalVM上でのPolyglotプログラミング体験を提供する基盤となるフレームワークです。GraalVM上でPolyglotプログラミングをするだけなら、ほとんど気にならないでしょう。GraalVM上で組み込み言語を実行したり、Polyglotオプション設定を使って特定のサポートされている言語のためのGraalランチャーを使用したりすることができます。おそらく、org.graalvm.polyglotの下にあるPolyglot APIを使用するか、言語拡張機能の中にある対応する名前空間を使用することになるでしょう。

もう少し詳しく知りたい場合は、Truffleが純粋なJavaライブラリであり、Truffleを使って言語インタプリタがターゲット言語のためのJITコンパイラとしてGraalVMコンパイラを利用できるようにしていることを理解すれば十分です。これは、アノテーションを付けたメソッドと、もちろんTruffle Java APIを使って実現します。Truffleへのアクセスにより、例えばRubyアプリケーションはJavaアプリケーションと同じJVM上で実行できます。また、ホストのJVMベースの言語とゲスト言語は、互いに直接相互運用し、同じメモリ空間でデータをやりとりできます。GraalVM上で独自の言語を実装したい場合は、以下のドキュメントをご覧ください。

Truffle Language Implementation Framework
https://www.graalvm.org/graalvm-as-a-platform/language-implementation-framework/

Truffle内で実装された言語の外部のPolyglot値を提供するため、いわゆるpolyglot interoperability protocol (多言語相互運用プロトコル)を開発しました。このpolyglot protocolは、全ての言語が外部polyglot値のために利用する標準化されたメッセージのセットから構成されています。このプロトコルを使えば、お互いを知らずとも任意の言語の組み合わせにおいてお互いを知らずとも相互運用性をGraalVMがサポートできます。

以後、polyglotコンテキストを初期化する言語に対し、”host language (ホスト言語)” という用語を使います。そして、ホスト言語から呼び出される言語に対し、”target language(ターゲット言語)”という用語を使います。ターゲット言語自体は別のサポートされる言語を呼び出すことができるため、システム全体がpoly-polyglot(訳注:polyはmanyを表す接頭辞なので、文字通りだと多・多言語という意味ですが、ホスト言語からターゲット言語を呼び出せるだけでなく、ターゲット言語から別の言語を呼び出すこともできる、という意図のようです)になります。

Running polyglot applications

GraalVMのインストールが必要です。ダウンロードリンクからダウンロードするか、もしくは可能であればSDKMan!を使ってください。

GraalVM Downloads
https://www.graalvm.org/downloads/
SDKMan!
https://sdkman.io/

追加の言語はguユーティリティを使ってインストールする必要があります。guはGraalVM Component Updaterと呼ばれるGraalVMのツールです。

筆者の環境は以下のようになっています。

➜  echo $GRAALVM_HOME
/Library/Java/JavaVirtualMachines/graalvm-ce-java11-20.1.0/Contents/Home
➜  echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/graalvm-ce-java11-20.1.0/Contents/Home
➜  java --version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02, mixed mode, sharing)
➜  $GRAALVM_HOME/bin/gu list
ComponentId     Version    Component name      Origin
---------------------------------------------------------
graalvm         20.1.0     GraalVM Core
R               20.1.0     FastR               github.com
llvm-toolchain  20.1.0     LLVM.org toolchain  github.com
native-image    20.1.0     Native Image        github.com
python          20.1.0     Graal.Python        github.com
ruby            20.1.0     TruffleRuby         github.com

gu にはパスが通っている必要があります。追加言語、たとえはJavaScriptやRubyだけでなく、native-imageツールは以下のようにインストールできます。

gu install ruby
gu install native-image

Java 11でのsingle-file source-codeプログラムのおかげで、以下のようなJavaプログラムを作成し、

import org.graalvm.polyglot.*;
class Polyglot {
    public static void main(String[] args) {
        Context polyglot = Context.create();
        Value result = polyglot.eval("js", 
            "[10,10,20,2].reduce((a,v) => a +v)");
        System.out.println(result.asInt());
    }
}

以下のコマンドで

java Polyglot.java

GraalVM上で実行できます。そして組み込まれたJavaScriptを実行し、42が表示されます。

別のホスト言語のためのネイティブランチャーがあります。ホスト言語ではなく別のターゲット言語を呼び出したい場合、JavaScriptの場合は js --polyglot --jvm のようなpolyglotオプションを付けて実行する必要があります。

Scenarios

polyglot programmingを行う必要性は、多くの場合、問題を解決したいと思っている言語に欠けているものがあることに起因しています。例えばR言語のように、ある言語はアナリティクスが得意だったり、アナリティクスの優れたライブラリを持っていても、お気に入りのグラフデータベースに接続するためのライブラリが不足していたりすることがあります。

動的スクリプト言語はJavaのようなコンパイル言語よりも開発がはるかに簡単になることがあります。これは、データベースのスクリプトのストアドプロシージャでよく見られます。例えばOracle DatabaseにはPL/SQLがあり、新しい関数をデータベースに入れ込むのがとても簡単です。私は、Neo4jの中にRubyやJavaScriptの形で同じようにしたいのです。

上記のユースケース次第で、言語Xのライブラリを言語Yに持ち込むのか、それともホストランタイムに別の言語を持ち込むのか、という問題が出てきます。

Bringing a Java library to a supported target language

「まだRドライバを持っていないのだが、それにもかかわらずRからNeo4jに接続できるか?」
これは、ネイティブイメージとは別に、GraalVMを使ってNeo4jを動かそうとしたときに最初に直面したことの一つです。もちろん可能です。

Image for post
Neo4j Javaドライバーを使ってRubyやR、PythonからNeo4jデータベースに接続できます。

GraalVMのpolyglotコンテキストは、サポート対象のターゲット言語上で、クラスへのアクセスを可能にする “Java “コンストラクトを提供します。Javaオブジェクトのインスタンスにホスト言語の構文でアクセスします。値を正しく変換するのはTruffleがよろしくやってくれます。

Rの場合、以下のような感じです。スクリプト(こちらから引用)はNeo4j Java Driverを使い、ローカルホスト上で動作するデータベースへ接続し、Graphクエリを実行しています。 

graphDatabase <- java.type('org.neo4j.driver.GraphDatabase')
authTokens <- java.type('org.neo4j.driver.AuthTokens')
config <- java.type('org.neo4j.driver.Config')
# This is a call to the static factory method named `driver`
driver <- graphDatabase$driver(
    'bolt://localhost:7687',
    authTokens$basic('neo4j', 'secret'),
    config$builder()
        $withMaxConnectionPoolSize(1)
        $build()
)
findConnections <- function (driver) {
query <- '
        MATCH (:Person {name:$name})
          -[:ACTED_IN]->(m)<-[:ACTED_IN]-(coAct)
        RETURN DISTINCT coAct
    '
    session <- driver$session()
    # The R list (which behaves like an associative array) is
    # automatically converted to a Java Map
    records <- session$run(query, list(name="Tom Hanks"))$list()
coActors <- list()
    i <- 1
    for (record in records) {
        coActors[[i]] <-record$get('coAct')$get('name')$asString()
        i <- i + 1
    }
    session$close()
    return(coActors)
}
connections <- findConnections(driver)
for(connection in connections) {
    print(connection)
}
driver$close()

JavaScriptやRuby、Pythonのサンプルは非常に類似しています。ドライバーの初期化方法に対応する必要がありますが、それとは別にして、利用しているエコシステムにとどまり続けることもできます。

Hosting another language in Java

Neo4jをカスタムストアドプロシージャで拡張できます。Javaで記述する必要がありますが、インストールもしくはアップグレードするためには、Neo4jを再起動する必要があります。そのかわりにスクリプト言語を使えるといいと思いませんか?

User-defined procedures
https://neo4j.com/docs/java-reference/current/extending-neo4j/procedures-and-functions/procedures/

Image for post
Neo4jデータベースでJavaScriptで記述されたカスタムストアドプロシージャをGraalVM上で動作させる

GraalVM SDK (org.graalvm.sdk:graal-sdk) を使用すると、実のところ非常に簡単に実現できます。Neo4jのストアドプロシージャは上記のPolyglot.javaとあまり変わらないように見えます。

import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
public class ExecuteJavaScript {
    @Context
    public GraphDatabaseService db;
    @Procedure(value = "scripts.execute")
    @Description("Executes the script at the given URL.")
    public void execute(
        @Name("scriptUrl") String scriptUrl
    ) throws IOException {
        var uri = Files.readString(Path.of(URI.create(scriptUrl)));
        try (var context = org.graalvm.polyglot.Context.newBuilder()
            .allowAllAccess(true).build()
        ) {
            var bindings = context.getPolyglotBindings();
            bindings.putMember("db", db);
            context.eval("js", uri);
        }
    }
}

上記のコードはNeo4jのコードです。特に今回の目的のために、GraphDatabaseService dbが注入されています。このサービスはNeo4j APIへのアクセスを提供します。Cypherを実行したり、ノードを見つけてトラバースしたりできます。bindings.putMember("db", db) を使ってターゲット言語からアクセスできるよう、このサービスをpolyglotバインディングに入れ込みます。ここでもTruffleは、ターゲット言語からアクセスできるように、この複雑なサービスを変換してくれます。

以下はこの関数を使って呼び出すスクリプトの例です。

const collectors = Java.type('java.util.stream.Collectors')
function findConnections(to) {
    const query = `
        MATCH (:Person {name:$name})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActor)
        RETURN DISTINCT coActor`
    const db = Polyglot.import("db")
    const tx = db.beginTx()
    const names = tx.execute(query, {name: to})
        .stream()
        .map(r => r.get('coActor').getProperty('name'))
        .collect(collectors.toList())
    tx.close()
    return names
}
names = findConnections('Tom Hanks')
names.forEach(name => console.log(name))

const db = Polyglot.import("db") はこの前にバインディングに入れ込んだサービスへのアクセス方法を示しています。

では、どのようにこれをNeo4Jで呼び出すのでしょうか?Cypherステートメントを使う場合、 CALL scripts.execute('file:///path/to/script.js') という形で呼び出します(Cypher はクエリ言語です)。

完全なコードは以下のリポジトリにあります。

neo4j-polyglot-stored-procedures
https://github.com/michael-simons/neo4j-polyglot-stored-procedures

Bringing Java over to C

この章の完全な例は以下からご利用いただけます。

neo4j-java-driver-native-lib
https://github.com/michael-simons/neo4j-java-driver-native-lib

C言語から直接GraalVM polyglotを間違いなく使用できますが、共有ライブラリを作成して、polyglotアプローチをnative-imageと組み合わせたいと思うかもしれません。C用のpolyglot APIについては、GraalVMリファレンスマニュアルをチェックしてください。

native-imageについてはよく耳にしますが、ほとんどの場合、実際の実行ファイルを作成することを目的としています。しかし、--sharedというコマンドラインスイッチを使用すれば、このツールで共有Cライブラリを作成することができます。これは多言語相互運用の全く新しい世界を開きます。

Javaまたはサポートされているターゲット言語といった、GraalVMネイティブイメージ上で動作するものを使い、CやC#プログラム、Foreign Function Interfaces(FFI)を使用できる任意のプログラムで利用可能なライブラリを作成できます。

ではどのように動作するのでしょうか?再度申し上げますが、現在Neo4jの観点でこれに取り組んでいます。例えば認証の詳細をNeo4jサーバーとクエリに渡し、結果を表示する関数を呼び出したい、としましょう。コードは以下のような感じです。

import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.CContext;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
public final class DriverNativeLib {
    @CEntryPoint(name = "execute_query_and_print_results")
    public static long executeQueryAndPrintResults(
        IsolateThread isolate, 
        CCharPointer uri, CCharPointer password,
        CCharPointer query
    ) {
        // Some interaction with Neo4j
        return 4711L;
    }
}

再度 org.graalvm.sdk:graal-sdk からパッケージをインポートしています。 @CEntryPoint を使って、native-imageが生成するDLLにエントリポイントを定義します。

ビルドプロセスにより、以下のようなファイルが生成されます。

driver-native-lib-0.0.1-SNAPSHOT.jar
generated-sources
graal_isolate.h
graal_isolate_dynamic.h
libneo4j.dylib
libneo4j.h
libneo4j_dynamic.h

Cプログラムからこれを呼び出すのは非常に間単です。

// Tested with
//   Apple clang version 12.0.0 (clang-1200.0.32.2)
//   Target: x86_64-apple-darwin19.6.0
// Compile from the project roots with
//   gcc -Wall -Ltarget -Itarget target/libneo4j.dylib src/main/c/executeQueryAndPrintResults.c -o target/executeQueryAndPrintResults
// And run as
//   target/executeQueryAndPrintResults
#include <stdio.h>
#include <stdlib.h>
#include "node.h"
#include "libneo4j.h"
int main(void) {
    graal_create_isolate_params_t isolate_params;
    graal_isolate_t* isolate;
    graal_isolatethread_t *thread = NULL;
    int ret = graal_create_isolate(&isolate_params, &isolate, &thread);
    if( ret != 0) {
        fprintf(stderr, "graal_create_isolate: %d\n", ret);
        exit(0);
    }
    int count = execute_query_and_print_results(thread, "bolt://localhost:7687", "secret", "MATCH (m:Movie) RETURN m");
    fprintf(stdout, "Number of movies printed: %d\n", count);
    if (graal_detach_thread(thread) != 0) {
        fprintf(stderr, "graal_detach_thread error\n");
        return 1;
    }
}

同じようにして、共有Cライブラリを使ってCプログラムからJavaに呼び出すことができるようになりました。GraalVMのC-APIに関するドキュメントは、Native Image C API にあります(他に比べると少し記載が少ないですが…)。

Native Image C API
https://www.graalvm.org/reference-manual/native-image/C-API/

このエントリの最初の章でリンクしたソースコード・リポジトリにもまた、標準のRubyから生成されたライブラリを使うためのコードが含まれています。すでにGraalVMのRuby実装があるのになぜこれが有用なのでしょうか?それは選択の自由ってやつです。様々な理由で標準のRubyにこだわりたい人もいるでしょう。その場合、GraalVMの「標準的な」polyglotの手段を利用できません。

GraalVMチームのAleksandarからの多大なるサポートのおかげで、大学時代からのC言語の知識を引っ張り出すことができました。

GraalVMチームのAleksandar氏の素晴らしいサポートのおかげで、私は大学時代のC言語の知識をちりばめることができました。JVMから結果を表示してもあまり意味がありません。ここからCの世界にデータを返したいと思っています。

そのために、こんな感じのC構造体を定義しています。

typedef struct c_node_struct {
    long id;
    char *label;
    char *name;
} c_node;

これはもちろん完成からはほど遠いですが、ちゃんと動作します。当然ながらこれをJVMに入れる必要があるのですが、そのためにはGraalVM SDKともう一つの追加ライブラリ、(org.gralvm.nativeimage:svmにある)GraalのSubstrateVMを使います。

package org.neo4j.examples.drivernative;
import java.util.Collections;
import java.util.List;
import org.graalvm.nativeimage.c.CContext;
import org.graalvm.nativeimage.c.struct.CField;
import org.graalvm.nativeimage.c.struct.CPointerTo;
import org.graalvm.nativeimage.c.struct.CStruct;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.word.PointerBase;
import com.oracle.svm.core.c.ProjectHeaderFile;
@CContext(DxriverNativeLib.CInterfaceTutorialDirectives.class)
public final class DriverNativeLib {
    static class CInterfaceTutorialDirectives implements CContext.Directives {
        @Override
        public List<String> getHeaderFiles() {
            return Collections.singletonList(
                ProjectHeaderFile.resolve(
                    "org.neo4j.examples.drivernative", "node.h")
            );
        }
    }
    @CStruct("c_node")
    interface CNodePointer extends PointerBase {
        @CField("id")
        void setId(long id);
        @CField("label")
        CCharPointer getLabel();
        @CField("label")
        void setLabel(CCharPointer label);
        @CField("name")
        CCharPointer getName();
        @CField("name")
        void setName(CCharPointer name);
        CNodePointer addressOf(int index);
    }
    @CPointerTo(CNodePointer.class)
    interface CNodePointerPointer extends PointerBase {
        void write(CNodePointer value);
    }
    private DriverNativeLib() {
    }
}

( @CContext を通して)共有イメージのコンテキストを定義するクラスを確認できます。確認します。ディレクティブを使って構造体のヘッダーファイルをインポートしています。これとマーカーインターフェースのPointerBaseに基づいて、この構造体へのJavaペンダントを定義できます。このインターフェースを実装してはいけません。驚くようなことではありませんが、ポインターとポインターへのポインターがあり、後者はCのやり方で配列を扱うためのものです。

どのように使うのでしょうか。確かにPolyglotプログラミングのような感じです。Cの世界に残したと思っていたことを、Javaでやる必要があるという意味では、Polyglotです。

public final class DriverNativeLib {
    @CEntryPoint(name = "execute_query_and_get_nodes")
    protected static int executeQueryAndGetNodes(
        IsolateThread thread, CCharPointer uri,
        CCharPointer password, CCharPointer query,
        CNodePointerPointer out
    ) {
        // Some magic to connect to Neo4j
        // more magic to retrieve nodes
        List<Node> nodes = Collections.emptyList();
        CNodePointer returnedNodes = UnmanagedMemory.calloc(
            nodes.size() * SizeOf.get(CNodePointer.class));
        int cnt = 0;
        for (Node node : nodes) {
            CNodePointer cNode = returnedNodes.addressOf(cnt++);
            cNode.setId(node.id());
            // Even more magic to retrieve does things from the node
            String firstLabel = "getLabel";
            String nameAttribute = "getName";
            cNode.setLabel(toCCharPointer(firstLabel));
            cNode.setName(toCCharPointer(nameAttribute));
        }
        out.write(returnedNodes);
        return cnt;
    }
    private static CCharPointer toCCharPointer(String string) {
        byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
        CCharPointer charPointer = UnmanagedMemory.calloc(
            (bytes.length + 1) * SizeOf.get(CCharPointer.class));
        for (int i = 0; i < bytes.length; ++i) {
            charPointer.write(i, bytes[i]);
        }
        charPointer.write(bytes.length, (byte) 0);
        return charPointer;
    }
    @CEntryPoint(name = "free_results")
    protected static void freeResults(
        IsolateThread thread, CNodePointer results,
        int numResults
    ) {
        for (int i = 0; i < numResults; ++i) {
            UnmanagedMemory.free(results.addressOf(i).getLabel());
            UnmanagedMemory.free(results.addressOf(i).getName());
        }
        UnmanagedMemory.free(results);
    }
}

Neo4jへの接続を確立した後(このサンプルには関係ありませんが、クールなのでリポジトリをチェックしてください)、 UnmanagedMemory.calloc(nodes.size() * SizeOf.get(CNodePointer.class)) でメモリの割り当てを実施しなければなりません。アンマネージドメモリとはまさにこのようなものです。そして与えられた型の配列に対してC言語で行うのとほぼ同じ方法です。返す文字列についても同様です(toCCharPointerはsparkling integersを生成します(このポインタに美しいニックネームをつけてくれた元同僚のChris Vestに感謝します)。

当然ながら、全てのメモリは最後に開放されなければならず、そこにfreeResultsの出番があります。

呼び出し方は以下のようです(スレッド分離の儀式は省略しています)。

// Prepare an output pointer
c_node *nodes;
int numResults = execute_query_and_get_nodes(
    thread, "bolt://localhost:7687", "secret",
    "MATCH (tom:Person {name: \"Tom Hanks\"})-[:ACTED_IN]->(tomHanksMovies) RETURN tom, tomHanksMovies",
    &nodes);
int i;
for (i = 0; i < numResults; i++) {
    fprintf(stdout, "(%ld:%s name:%s) \n", nodes[i].id, nodes[i].label, nodes[i].name);
}
free_results(thread, nodes, numResults);

Quintessence

GraalVMでのPolyglotプログラミング方法を3個ご紹介しました。そのうちの2個は、ホスト言語や呼び出し元の言語からGraalVMのpolyglot機能を利用し、そこからターゲット言語へと分岐させたものです。最初の例では、ホスト言語はRであり、リポジトリにはRuby、JavaScript、Pythonの例も含まれています。これらはすべてネイティブの実行ファイルにコンパイルすることもできます。サポートされている機能の範囲は異なるかもしれませんが、Javaベースのライブラリの呼び出しはほぼ標準で動作します。2番目の例では、ホスト言語はJavaで、ホストはターゲット言語に任意の複雑な型を渡しました。オブジェクトはNeo4j Graphデータベースに完全にアクセスできるサービスで、ターゲット言語はJavaScriptです。

両方のシナリオで、poly-polyglotであることに注意してください。ターゲット言語が他のサポートされている言語を呼び出すことも可能です。

ここで説明した両方のシナリオが機能し、突拍子もないことではなく、現実世界での本格的なユースケースのニーズを満たすと確信しています。最初の例では、おそらくすべての言語で利用できるとは限らず、Java上で最も完全な機能セットを持っているライブラリを使用できます。Truffleフレームワークは、コレクション、マップ、構造体のようなデータ型をホスト言語が理解して動作できるものに変換する素晴らしい仕事をしています。

2番目の例では、JVM上で実行されているソフトウェアシステムでスクリプト言語を簡単に実行できます。Oracles Databaseでは長い間PL/SQLが使われていますが、現在は組み込みGraalVMを使用して、多くの言語でユーザー定義関数を使用できるようにしています(比較のため以下のエントリを一読ください)。これは、まさにNeo4jスクリプトプロシージャで示したpolyglotのシナリオです。

Bringing Modern Programming Languages to the Oracle Database with GraalVM
https://medium.com/graalvm/bringing-modern-programming-languages-to-the-oracle-database-with-graalvm-80914d0c0167

Image for post
GraalVMのnative-imageでビルドしたNeo4j Javaドライバーの共有ライブラリを使って、CやRubyといった別の言語からNeo4jデータベースに接続できる。

3番目の例では、Javaライブラリを共有Cライブラリに変える方法、CやRubyやRustのようなFFIを使用できる言語から直接アクセス可能なエントリポイントを公開する方法を説明しています。これは前の2つの例よりも手間がかかります。

私の C の知識は、ポインタを使った手動操作ぐらいで、それ以外は錆びついていると表現するのが一番いいでしょう。それでも、少なくとも動作する機能的な概念実証を作成することができました。

私はこの実現可能性を気に入っていますが、もしこれを実現する必要性があるだけでなく、純粋なPoCを超えてこれを推進するための知識があるのであれば、まずあなたのチームに目を向けることをおすすめします。

Javaをベースにした共有ライブラリを直接使わずに済むのであれば、オプション1または2がC言語からでも動作することに驚くでしょう。いずれかのオプションを使えば、C言語で直接Cライブラリを開発し、Javaでも何でも好きなものを呼び出し、それを公開できます。

GraalVMのビルディングブロックはレゴブロックに似ていて、同じように動作します。Spring Data Neo4jスタックに動作してもらいたいのと同じように、もしくは友人のMichael Hungerがグラフモデルについて考えるのと同じように

私がSpring Data Neo4jスタックを動作させたいのと同じように、あるいは私の友人であるMichael Hungerが私たちのグラフモデルについてどのように考えているのかと同じように動作します。そしてこれらは様々な方法で構成可能であり、その合計が実際の価値を提供します。

A composable stack – Spring up your graph
https://speakerdeck.com/michaelsimons/spring-up-your-graph?slide=13
Michael Hunger
https://medium.com/@mesirii

どちらにしても、Graalとそれに関連する多くの経験は、過去3年間にわたって素晴らしいものでした。

JCrete 2017でGraalVMについて初めて聞いたときはあまり理解していませんでしたが、ツール、ドキュメント、インフラストラクチャの絶え間ない開発を目の当たりにしました。GraalVMは驚くべき技術であり、素晴らしいJVMライブラリを全く新しい範囲のエコシステムに開放してくれます。今ではオリジナルのネイティブ実行プログラムと同じくらい高速に起動するため、ただのJavaのプログラムを間違いなく超えたものになっています。Graalのようなものは、まだ完全には見えないものの、もしかしたら25年前のJVMそのものと同じくらいのインパクトがあるのではないかと推測しています。

この記事の最初に記載したリポジトリをチェックしていただければ幸いです。もし参考になるようであればお知らせください。この記事をご覧になってあなたがNeo4jの世界に入ってもらえると幸いです。Neo4jのブログもチェックしてください。

Neo4j Developer Blog
https://medium.com/neo4j

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください