MatzにrejectされたES6風HashリテラルをこっそりRubyで使う
ES6風Hashリテラルとは
ここで言う「ES6風Hashリテラル」というのは、ES6(ES2015)でobject property shorthandとして導入された省略記法です。
a = 42
b = "abc"
c = {a, b}
pp c #=> {:a=>42, :b=>"abc"}
最近のJavaScriptを使っている人なら見慣れた記法ですね。Rubyでもこういう風に書けると便利そう、と思う人はもちろんいるわけで、この記法を取り入れるべく過去にもbugs.ruby-lang.orgで提案がありました。が、残念ながら今のところrejectされたままです。
- Feature #11105 ES6-like hash literals https://bugs.ruby-lang.org/issues/11105
- Feature #15236 add support for hash shorthand
https://bugs.ruby-lang.org/issues/15236
似た記法として、 c = {a:, b:}
というのも提案されましたが、こちらもrejectされています。
- Feature #14579 Hash value omission
https://bugs.ruby-lang.org/issues/14579
ところが、最近のRubyでは、こういうリテラルをそのまま動かす(!)技が存在しています。もちろん、Rubyの実行ファイルにパッチを当てたりしないで、普通のCRuby処理系を使って実現します。
以下のような2つのファイルを用意します。
- lib/es6_hash.rb
def es6_like_hash(a, b)
c = {a, b}
c
end
- sample.rb
require "ruby-next/language/runtime"
require "ruby-next/language/proposed"
require_relative "lib/es6_hash"
pp es6_like_hash(42, "abc")
実行してみます。
$ ruby sample.rb
{:a=>42, :b=>"abc"}
ちゃんと動きます。すごい。
その秘密は、もちろん require "ruby-next/..."
という行にあります。これはRubyNextというライブラリを読み込むものです。
RubyNextとは
RubyNextは、Evil MartiansのVladimir Dementyevが開発しているライブラリで、一言で言うとRubyのトランスパイラです。
RubyNextはgemでインストールできます。
$ gem install ruby-next
もちろんGemfileに書き、bundle installして使うこともできます。
RubyNextはRubyのファイルをトランスパイルし、結果を別のファイルとして保存することもできるのですが、実行時にもrequireを(require_relativeも)すげ替えて、ライブラリを書き換えつつ実行することができます。
新しいリテラルを導入する(つまりスクリプトをparseしてバイトコードにコンパイルする前にトランスパイルする)ことができるのはそのためです。言い換えると、この技はライブラリにしないと使えません(上の例だと、sample.rbに直接このリテラルを書くことはできません)。
また、requireのすげ替えはあらゆるディレクトリに対して行われるわけではありません。標準ではlib
やapp
やspec
など、決まった名前のディレクトリに対してのみ適用されます。それ以外のディレクトリでも使えるようにするには、RubyNext::Language.watch_dirs
を設定します(これが分からなくて、同階層に置いてもぜんぜん動かなくてしばらくハマりました……)。
Edge features と Proposed features
さて、Rubyでもトランスパイルができるようになったとして、何をどうトランスパイルするかですが、RubyNextにはいくつかの機能があります。
- 新しいバージョン用のRubyスクリプトを動かす(例: Ruby 2.6でRuby 2.7の新機能を使う)
- まだリリースされていないバージョン用のRubyスクリプトを動かす
- まだコミットされていない(提案のみの)機能を使ったRubyスクリプトを動かす
RubyNextのドキュメントでは、2番目のものがEdge features、3番目のものがProposed featuresと呼ばれています。ここで我々が使いたいものはProposed featuresですね。
Proposed featuresを使えるようにするためのコードが、先ほどのsample.rbにあったrequire "ruby-next/language/proposed"
になります。
なお、現時点(RubyNext 0.10.0)でProposed featuresとして利用できるものは以下の2つです。
- Method reference operator (
.:
) (#13581) - Shorthand Hash notation (
data = {x, y}
) (#15236)
今回はそのうちの後者を使ってみた、というわけです。もちろん前者のメソッド参照演算子も使えます。
まとめ
RubyNextがこのようなProposed featuresを導入しているのは、新しい機能を正式に導入する前に、多くの人によって簡単に試せる機会を提供するためだそうです。これはもちろんJavaScriptのエコシステムを参考にしています。
そこで提案したいのですが、Ruby言語の開発でもこれと同じようなアプローチを採用してみてはどうでしょう?「マージか取り消しか」の二者択一ではなく、「トランスパイラのユーザーから広くフィードバックを集め、それを元に機能を受け入れる」というプロセスがあってもよいのではないでしょうか。
Ruby Nextは、Rubyを未来に向けて前進させる、そんなトランスパイラを目指しています。
(Ruby NextトランスパイラでRubyの新機能を使おう(翻訳)より)
Rubyのより良い進化を促すためにも、興味のある方はRubyNextを試してみましょう。
参考
- Ruby Next https://github.com/ruby-next/ruby-next
- Ruby NextトランスパイラでRubyの新機能を使おう(翻訳) https://techracho.bpsinc.jp/hachi8833/2020_05_20/92033
Discussion