LoginSignup
2

More than 5 years have passed since last update.

Introduction to V8 for R

Last updated at Posted at 2018-08-27

(※以下はV8パッケージのvignetteであるIntroduction to V8 for Rの和訳です。)

V8とV8パッケージ

V8はGoogleが開発するオープンソースのハイパフォーマンスなJavaScriptエンジンです。C++で記述されており、ECMAScript(ECMA-262)5th Editionに準拠しています。V8パッケージはこのC++ライブラリをベースにしており、Rとは完全に独立したJavaScriptエンジンを提供します。

V8では次のようにコンテキストを生成することでコードの評価などを行います。

library(V8)

# コンテキストの作成
ct <- v8()

# コードの評価
ct$eval("var foo = 123")
ct$eval("var bar = 456")
ct$eval("foo + bar")
[1] "579"

C++やJavaなどの他の言語とRとのインターフェース(RcpprJava)に比べると、V8はコンパイルが不要な上に外部の実行ファイルやランタイムに依存しないという大きな利点があります。実行エンジンの全ては6MBのパッケージ(これがさらに2MBに圧縮されています)に含まれており、主要なプラットフォームの全てで動作します。

もう少し例を見てみましょう。

# 適当なJSONの生成
cat(ct$eval("JSON.stringify({x: Math.random()})"))
{"x":0.21224139956757426}
# 適当なクロージャ
ct$eval("(function(x){return x + 1;})(123)")
[1] "124"

なお、V8それ自体は単なるJavaScriptエンジンに過ぎないということに注意してください。現状ではDOMがありませんし(つまりwindowオブジェクトもありません)、ネットワークもディスクI/Oもイベントループもありません。しかし、これらはすべてRにありますから、大した問題ではありません。この点ではV8RcpprJavaといった他言語のインターフェースに似ています。ただ対象がJavaScriptであるというだけです。

JavaScriptライブラリの読み込み

ct$sourceを使うと、ファイルやurlからライブラリを読み込めます。

ct$source(system.file("js/underscore.js", package = "V8"))
ct$source("https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.11/crossfilter.min.js")

データのやりとり

デフォルトでは、RとJavaScript間のすべてのデータ交換はJSONで行われます。JSONとRのデータ形式の間のマッピングは、jsonliteパッケージによって双方向に行われます。

ct$assign("mydata", mtcars) # JSONとして読み込ませられる
head(ct$get("mydata")) # データフレームとして読み出せる
output
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

JS()を使うと、JSONに変換せずにJavaScriptの式として代入することもできます。

ct$assign("foo", JS("function(x){return x*x}"))
ct$assign("bar", JS("foo(9)"))
ct$get("bar")
[1] 81

関数呼び出し

ct$callを使うとJavaScriptの関数を呼び出せます。このとき、オブジェクトは(引数も返り値も両方とも)自動的にRとJavaScriptの間で変換されます。

ct$call("_.filter", mtcars, JS("function(x){return x.mpg < 15}"))
output
                     mpg cyl disp  hp drat    wt  qsec vs am gear carb
Duster 360          14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
Cadillac Fleetwood  10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
Camaro Z28          13.3   8  350 245 3.73 3.840 15.41  0  0    3    4

これはC言語の代わりにJavaScriptが呼び出される.Callのようなものと考えると良いでしょう。

インタラクティブなコンソール

JavaScriptを学んだりセッションのデバッグをするにはコンソールでインタラクティブな操作をするのが簡単なやり方です。ct$console()を実行すると、V8のコンソールが開き、入力待機状態となります。

ct$assign("diamonds", ggplot2::diamonds)
ct$console()

この状態ではct$evalを実行することなく、JavaScriptをインタラクティブに実行できます。V8のコンソールに以下の入力をしてみてください。

var cf = crossfilter(diamonds)
var price = cf.dimension(function(x){return x.price})
var depth = cf.dimension(function(x){return x.depth})
price.filter([2000, 3000])
output = depth.top(10)

入力が終わったらエスケープキーを叩くかexitとタイプしてコンソールを終了してください。結果はoutputに保存しましたから、次のようにRから結果にアクセスできます。

output <- ct$get("output")
output
output
   carat  cut color clarity depth table price    x    y    z
1   0.50 Fair     E     VS2  79.0    73  2579 5.21 5.18 4.09
2   0.50 Fair     E     VS2  79.0    73  2579 5.21 5.18 4.09
3   0.90 Fair     G     SI1  72.9    54  2691 5.74 5.67 4.16
4   0.96 Fair     G     SI2  72.2    56  2438 6.01 5.81 4.28
5   1.00 Fair     G     SI2  70.2    58  2326 6.00 5.73 4.13
6   1.00 Fair     J     SI2  69.9    61  2117 6.13 5.86 4.23
7   1.00 Fair     H     SI2  69.5    55  2875 6.17 6.10 4.26
8   1.00 Fair     D     SI2  69.3    58  2974 5.96 5.87 4.10
9   0.90 Fair     F     SI2  69.0    59  2536 5.89 5.82 4.04
10  0.81 Fair     F     SI2  68.8    79  2301 5.26 5.20 3.58

警告、エラー、console.log

無効なJavaScriptコードを評価すると、SyntaxErrorが返ります。

ct$eval("var foo <- 123;")
context_eval(join(src), private$context) でエラー: SyntaxError: Unexpected token <

また、JavaScriptのランタイムエラーは自動的にRに伝えられます。

ct$eval("123 + doesnotexist")
context_eval(join(src), private$context) でエラー: ReferenceError: doesnotexist is not defined

さらにconsole.logconsole.warnconsole.errorを使えば手動でJavaScriptからRにコールバックすることもできます。これにより、JavaScriptアプリケーション内からの出力、警告、エラーの詳細をRに伝えることができます。

ct$eval('console.log("this is a message")')
this is a message
ct$eval('console.warn("Heads up!")')
Warning: Heads up!
ct$eval('console.error("Oh no! An error!")')
context_eval(join(src), private$context) でエラー: Oh no! An error!

console.errorの使用例としては、外部リソースの読み込み確認が挙げられます。

ct$source("https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.11/crossfilter.min.js")
[1] "true"
ct$eval('var cf = crossfilter || console.error("failed to load crossfilter!")')

Global Namespace

Nodeやブラウザからの場合と違って、新しいコンテキストの名前空間にはごくわずかなものしか置かれていません。デフォルトでは、global(自分自身への参照)、consoleconsole.logやその他のため)、printconsole.logのエイリアスですが、これを必要とするJavaScriptライブラリがあります)だけが含まれています。

ct <- v8(typed_arrays = FALSE)
ct$get(JS("Object.keys(global)"))
[1] "console" "print"   "global" 

型付き配列(typed array)を有効にすると、いくつか追加でグローバルオブジェクトが読み込まれます。

ct <- v8(typed_arrays = TRUE)
ct$get(JS("Object.keys(global)"))
 [1] "console"      "print"        "global"       "ArrayBuffer" 
 [5] "Int8Array"    "Uint8Array"   "Int16Array"   "Uint16Array" 
 [9] "Int32Array"   "Uint32Array"  "Float32Array" "Float64Array"
[13] "DataView"    

名前が設定されていない状態でも、コンテキストは常にグローバルスコープを持ちます。コンテキストをglobal =
NULL
とともに初期化した場合でも、thisキーワードを評価すればグローバルスコープに到達できます。

ct2 <- v8(global = NULL, console = FALSE)
ct2$get(JS("Object.keys(this).length"))
[1] 10
ct2$assign("cars", cars)
ct2$eval("var foo = 123")
ct2$eval("function test(x){x + 1}")
ct2$get(JS("Object.keys(this).length"))
[1] 13
ct2$get(JS("Object.keys(this)"))
 [1] "ArrayBuffer"  "Int8Array"    "Uint8Array"   "Int16Array"  
 [5] "Uint16Array"  "Int32Array"   "Uint32Array"  "Float32Array"
 [9] "Float64Array" "DataView"     "cars"         "foo"         
[13] "test"        

独自のグローバルオブジェクトを使いたければ次のようにします。

ct2$eval("var __global__ = this")
ct2$eval("(function(){var bar = [1, 2, 3, 4]; __global__.bar = bar;})()")
ct2$get("bar")
[1] 1 2 3 4

シンタックスバリデーション

V8はJavaScriptのシンタックスのバリデーションを行うこともできます。実際に評価をすることなしに、です。

ct$validate("function foo(x){2*x}")
[1] TRUE
ct$validate("foo = function(x){2*x}")
[1] TRUE

これは、テンプレート化されたJavaScriptを通じてグラフィックスを生成するようなRパッケージで有用です。

また、JavaScriptはグローバルスコープで無名関数を定義できないことに注意してください。

ct$validate("function(x){2*x}")
[1] FALSE

無名関数の構文が正しいかどうか確認するためには()で囲むか、!を頭に付けます。

ct$validate("(function(x){2*x})")
[1] TRUE
ct$validate("!function(x){2*x}")
[1] TRUE

Rへのコールバック

最近追加された機能ですが、console.rAPIを利用することでJavaScript内からRとやりとりすることもできます。簡単な例を示しましょう。まずコンソールを開きます(これは以下の説明を簡単にするためであり、ct$evalを通じてコードを評価しても同様の結果が得られるはずです)。

ctx <- v8()
ctx$console()

console.r.getconsole.r.assignを通じてJavaScriptからRオブジェクトの読み書きが可能です。この関数は最後の引数にtoJSONfromJSONに渡す引数をリストとして指定することもできます。

R内のデータセットであるirisをJavaScriptに読み込む例を示します。

var iris = console.r.get("iris")
var iris_col = console.r.get("iris", {dataframe: "col"})

次に、RのオブジェクトにJavaScriptから書き込む例を示します。

console.r.assign("iris2", iris)
console.r.assign("iris3", iris, {simplifyVector: false})

また、console.r.callを使うと、Rの関数をJavaScriptから呼び出せます。最初の引数にはRの関数名を文字列で指定しますが、無名関数を指定することもできます。2つ目の引数にはリストとして引数を指定します。do.call()と似たようなものと考えると良いでしょう。リストは名前付き、名前なし、どちらもサポートされています。実行結果はJSONとしてJavaScriptに返されます。

// rnormで乱数を生成する例
var out = console.r.call('rnorm', {n: 2,mean:10, sd:5})
var out = console.r.call('rnorm', [2, 10, 5])

// 無名関数を呼び出す例
var out = console.r.call('function(x){x^2}', {x:12})

似たようなものにconsole.r.eval関数があります。この関数には引数は1つしかなく、文字列として渡されたコードを評価し、その結果を単にコンソールに出力します。

// sessionInfo()の結果が表示される
console.r.eval('sessionInfo()')

V8はオブジェクトの自動的な変換を行うだけでなく、R、C++、JavaScript間で例外をスタックの上下に伝搬するということもします。つまり、JavaScriptから呼び出したRのエラーをJavaScriptの例外としてキャッチできますし、その逆もできるわけです。もし例外がキャッチされなければ、これはRセッションのトップレベルにエラーとして表出します。

// Rのエラーを発生させる例
console.r.call('stop("ouch!")')

// RのエラーをJavaScriptで補足する例
try{
  console.r.call('stop("ouch!")')
} catch (e) {
  console.log("Uhoh R had an error: " + e)
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2