5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyAdvent Calendar 2023

Day 18

Opalとは(2023年版)

Posted at

Ruby Advent Calendar 2023 18日目の記事です。

みなさんOpalを知っていますか?
OpalはRubyスクリプトをブラウザ等のJavaScriptランタイムで動かすための仕組みです。

Qiitaでも2016年2018年にOpalのアドベントカレンダーが立てられていましたが、最近はOpalのタグもやや寂しい状況です1

ブラウザでRubyを動かすといえばruby.wasmがすっかりお馴染みになった感もありますが、Opalもまだまだ開発が続けられ、近日中にv2.0のリリースが予定されていますので、今改めてOpalの紹介をしておきます。

Opalの仕組み

Opalはざっくり言うと、以下の3つが合わさったものと言えます。

  • コードをRuby→JavaScriptに変換するトランスパイラ
  • クラス・メソッドのシステムなど、Rubyランタイムのコア部分の実装
  • Rubyの組み込みクラス・標準ライブラリの実装

3つ目の各種クラス(Array, Stringなど)も基本的にRubyで実装されていて、Opalの利用者側が書いたコードと合わせてコンパイルされます。
また、1つ目のトランスパイラはRubyで実装されているのですが、これ自体もJavaScriptへ変換することで、TryRubyのプレイグラウンド2のようにブラウザ上で入力されたRubyコードをその場で動かすといったことも可能になります。

Ruby⇔JavaScriptの連携

Opalでは``または%x{}の中にJavaScriptコードを書くことができます3
さらにその中に#{}でRubyコードを埋め込むことができ、Ruby⇔JavaScriptの連携が容易です。

# backtick_javascript: true
`#{(1..3).to_a}.map(x => x * 2)`.each_with_index do |x, index|
  puts `Math.pow(x, index)`
end

こんな風にRubyとJavaScriptをごちゃまぜに書いてもちゃんと動きます。
この例ではRange#to_aの戻り値にArray.prototype.mapを呼び出し、その結果に対してArray#each_with_indexを呼び出しています。
つまり、RubyのArrayがそのままJavaScriptのArrayとして使えたり、その逆ができています。
他にも、以下のクラスはRuby⇔JavaScript間で連携できるようになっています。

Ruby JavaScript
Array Array
Boolean Boolean
Exception Error
Hash Map
Number Number
Proc Function
Regexp RegExp
String String
Time Date

RubyとOpalの非互換

Opalは、主にJavaScript上の制限のためにいくつか本家であるCRubyと仕様が異なっている面があります。

たとえば、上の表でRuby列に見慣れないクラスがあるのに気づいたでしょうか?OpalではCRubyにないクラスが一部存在しています。

Booleanクラス

true, falseがそれぞれTrueClass, FalseClassのインスタンスであることは変わりませんが、両クラスともBooleanの子クラスという形になっています。

Numberクラス

最も大きな非互換の1つがInteger, Floatの区別が存在せず、Numberクラスのみになっていることです。
そのため、整数演算でも場合によっては浮動小数点誤差が発生することがあります。
それが問題になる場合はBigDecimalか、JavaScriptのBigIntを使います。

文字列の破壊的操作

もう一つの大きな非互換が、文字列の破壊的操作ができないことです。これはJavaScriptの文字列がimmutableであることから来る制限です。
str << otherができないので、str += otherに置き換える必要が出てきます。

これは文字列だけの話なので、配列のarr << otherは問題ありません。

SymbolがString

RubyのSymbolは現在のOpalではサポートされておらず、SymbolクラスはStringのエイリアスになっています。
シンボルリテラルを書いても文字列としてコンパイルされるので、:foo == "foo"がtrueになります。

ruby.wasm vs Opal

WASMによってCRubyが直接ブラウザ上で動くようになった今、Opalを使うメリットは何でしょうか?

  • JavaScriptとの親和性
    • 上にも書いたように、OpalはJavaScriptの配列をそのままRubyの配列として扱えるなど、直感的な操作が可能です
    • ruby.wasmでは、JavaScriptのオブジェクトをRuby側に持ってくると常にJS::Objectなので、変換が必要になってきます

一方、ruby.wasmの利点もあります。

  • CRubyとの互換性
    • ruby.wasmは基本的にCRubyをそのままWASMに移植したものなので、ほとんど同じ動きをすると言えます。
    • Opalは先述した非互換以外に、標準クラスの各メソッドも仕様を元にRuby+JavaScriptで再実装したものであるため、細かいエッジケースでCRubyと異なる挙動をする箇所が少なからずあります。
  1. 今回のアドベントカレンダーではDXOpalでスイカゲームみたいなのを作ったがありましたね

  2. このプレイグラウンドはもともとOpalによるものがデフォルトでしたが、現在はruby.wasmがデフォルトでOpalと選択できるようになっています

  3. v2.0以降はマジックコメント# backtick_javascript: trueが必要になります(無ければ通常のRubyと同じKernel#`の呼び出しになる)。

5
1
0

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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?