Edited at
PORTDay 17

Rubyは滅びゆく言語なのか?

さて、割とキャッチーなタイトルから始めましたが、rubyの人気が落ちています。

分かりやすい指標として、TIOBEのプログラミング言語のランキングを参照しますが

2003年にはruby on railsの普及に合わせて2008年ごろには10位。

2016年に史上最高の8位を記録していますが、そこからずるずると下がって現在は17位。

ほんとにここ2,3数年でズルズルと滑り落ちるという言葉のままで進んでいます。

ruby自体、正直話題なるような新しいものも出づらい状態で、rubygemsに公開されるgemのペースも2013年から下がり始めています。

2014年には「Rubyはまだ死んでいない」という記事が出る始末です。

実際、自分の肌感覚としても、rubyという言語自体の進化はrefinementを入れたruby2.1(2013年)以降は、痺れるような機能のリリースがなくなってしまいました。

ruby2.6でついに関数合成が実装されましたが、正直待ちすぎて気疲れした挙句、やっとかという印象です。

新規の案件も、周りの案件を聞く限りgoやElixirで書き始めたりする案件を聞いたりするようになって、順位の変動もこのようにスタートアップの案件で、「rails離れ」が起き始めていることの証左だと思います。

ここ3,4年はrubyやrailsに関しては、「飽きた」という言葉を聴くようになっていて、私もこの件に関しては同意です。

実際にどうしてこうなったのかというのは表面的なところは「Rubyはまだ死んでいない」ですでに書かれていることで


  1. 他の対抗に比べて速度面で劣っている

  2. 新しい機能がこれ以上出なくなった

  3. コミュニティが消失し始めている

このあたりの理由です。実感としても分かります。

コミュニティの消失は新しいドキドキするようなオモチャ(新機能)がなかなか入らなくなった事で、新しもの好きのプログラマはScalaなどの他の言語に流れてしまいました。

ただその後も、ベンチャー向けのWebフレームワークの"定番"として新規に人は流れて来ているけど、今、入ってきている人は新しもの好きが飛びつくオモチャとしてではなくて、すでに誰かが慣らした道を、道具として使おうと捉えている人達なのでしょう。

私個人のコミュニティ周辺の感想はこんな感じですが。

ではなぜ、rubyはこのような事になってしまったのか、自分なりに考えていたことをここから言葉にしてみようと思います。


rubyの動作原理を振り返って

プログラミング言語には、私の言葉に変えると「命数」というものが存在します。

多くの人が知っている事ですが、プログラミング言語は、その登場当時のハードウェアの制約や、文法的な開発しやすさ、需要などで当時の要求に対してカリカリにチューンされる運命にあります。

結果として、新しい時代に新しい要求が出た頃には後方互換性の問題で新しい機能を簡単には追加できないで、新しく出てきた言語に新しい市場を奪われていく歴史を繰り返してきていますが。

実際、ruby自体はWeb開発という世界でperlを置き換えるように使われる様になって、現在に至った歴史があります。

では、rubyは何処から新しい要求に答えられなくなったかというともちろん一つだけで回答するのは難しのですが、自分から見て目立って大きな問題になっている所を話していきましょう。


Giant VM lockな話

rubyにはGiant VM lockという仕掛けがあって、「同時に実行される ネイティブスレッドは常にひとつになる」という制約があります。コンカレンシーに全く向いていないのを名前から宣言している様な仕組みですが、なんでこういう事になっているかというと、rubyのクラスの実装に大きな原因があったりします。

rubyのクラスというのは実際にはrubyの起動時やrequireされたタイミングでメモリ上にクラスの雛形が展開されます。

この雛形に対して、後出しで属するべきメソッドや定数などを足しこんでいくことで、クラス自体を作っていくので、モンキーパッチングなど、後出してクラスの機能を拡張する事ができるようになっているのですが、もし別のスレッドが生成された後にモンキーパッチングでメソッドが追加されたとしたら、片方のスレッドには新しいメソッドが存在して、もう片方には存在しないという事になってしまったりします。

こういう所のプログラムの整合性をどうするかというところが問題で、現状の最適解でこういう仕組みになっているのですが。

じゃぁ、動的にクラスを書き換えるなんて他の言語であまり見られない仕掛けそもそもやめればいいのではと思ったりもするのですが、Railsで日々書いている人間から言うとそれはActiveSupport消しましょうかというのと同義になるわけで、正直、考えられるものではなかったりするのです。


rubygemsの闇

問題点はこれだけでなくて、私も「method_missingに型は追加できるのか」で調べているときに、最近気づいたのですが、gemという経済圏自体も問題です。

GVL自体も関わっていますが、rubyはメモリ上にクラスの雛形を展開していますが、それを名前で管理する以上、プログラム全体で同じ名前のクラスが使えないようになっています。

これが一体何の問題になるのかというのが普通の疑問なのですが、rubygemsという巨大なライブラリに新しくgemを上げて行こうとすると新しい名前の空きがどんどんと無くなっていくという事になります。

他の人と同じ名前のクラスが使えなくても、言葉の数なんて言うのはそもそも膨大で単語も組み合わせられるので表面化していないですが、rubygemsという一つの辞書に全ての使われている辞書を統合しようとすると、流石にそうはいきません。

結果としてhttp通信するライブラリにfaradayとか名づけることになったり辞書の名前の奪い合いは発生していて、これがruby本体に何か新しい機能を追加しようとすると既にあるgemのどれかに大抵名前が衝突することになってしまうわけです。

まぁ、でもこれくらいなら良いです。

それ以上に大きいと言えるのが互換性の問題。

よく使われているライブラリの中を複数見て気づいたのですが、文法としてrefinementとかどう使っているのかなーと思って探してみたのです。

grepして探した結果がヒットした件数が0件でした…。

もうすぐruby2.6がリリースされようかというタイミングでruby2.1での追加であるrefinementが1件も使われていない訳です。

見たときまじかー、という印象ですがコード自体を実際に見てみるとそれも納得。

RUBY_VERSIONなどの単語でgrepしてみると、まーまー色々と出てくるわ出てくるわ

今回改めてgrepし直したときの結果を軽く出してみましたがこんな感じです。

./yard-0.9.5/Rakefile:  if RUBY_VERSION >= '1.9' && RUBY_PLATFORM != 'java'

./yard-0.9.5/lib/yard/core_ext/hash.rb:end if RUBY_VERSION < "1.8.7"
./yard-0.9.5/lib/yard/i18n/po_parser.rb: if RUBY_VERSION < "1.9"
./yard-0.9.5/lib/yard.rb: def self.ruby19?; @ruby19 ||= (RUBY_VERSION >= "1.9.1") end
./yard-0.9.5/lib/yard.rb: def self.ruby2?; @ruby2 ||= (RUBY_VERSION >= '2.0.0') end
./yard-0.9.5/spec/handlers/examples/class_condition_handler_001.rb.txt: if defined? RUBY_VERSION
./yard-0.9.5/spec/handlers/ruby/legacy/base_spec.rb: if RUBY_VERSION > '1.8.7'
./yard-0.9.5/spec/i18n/locale_spec.rb: if RUBY_VERSION < "1.9"
./yard-0.9.5/spec/parser/ruby/ast_node_spec.rb: vcall = RUBY_VERSION >= '1.9.3' ? 'vcall' : 'var_ref'
./yard-0.9.5/spec/parser/ruby/ruby_parser_spec.rb: if RUBY_VERSION >= '1.9.3' # ripper fix: array node encapsulates qwords
./yard-0.9.5/spec/spec_helper.rb:NAMED_OPTIONAL_ARGUMENTS = RUBY_VERSION >= '2.1.0'
./yard-0.9.5/templates/default/layout/html/footer.erb: <%= YARD::VERSION %> (ruby-<%= RUBY_VERSION %>).

一番下の検索結果だったgem一個だけでも、こんな感じ。

RUBY_VERSION で区切って、if文を使うことで未だにruby1.8への対応をガンガンに入れて来ています。

これなら、あえて新しい文法の仕様を諦めている個所もおおいというのは容易に想像できます。

1件でも古いrubyで動いている案件があるならば、その人たちにとっては死活問題。

対応を諦めることは出来ませんからそうもなるでしょう。

そしてrakeなどの、環境を問わずよく使われているgemであればあるほど、しがらみからは逃げられないわけです。

これでは新しい機能や文法を追加しても使えるわけはありませんし

新しい機能を追加するにも、過去のrubyとの互換性を慎重に検証しながらしか作業が進みません。

それだけでなく、より早くなるような書き方をお勧めしたり文法を追加しても互換性をとる方に判断がいってしまい簡単に採用することは出来なくなってしまうので、高速化に対する弊害にもなってしまうわけです。

rubyに一向に型が実装されないのも互換性を持ったままの追加をどうするかが問題の割と大きめの一因ですし。JRubyなど他の実装にも気を配った複雑なテストを実際回しながら開発者はメンテをしているわけで、gemの中というのは、コードを読んで読み取れる動作原理以上に(バッド)ノウハウの蓄積された複雑な実装になってきています。

大変ですね。


まとめ

正直大変だなーというの感想なのですが。

どうしてこういう現状になっているかというのを自分なりに考えたのですが、これを簡単な言葉でまとめると、まさかrubyがこんな巨大なライブラリ群と、railsのような巨大な実装で使われるとは思っていなかった、という初期設計のミスなんだと思うのですよね。

rubyの開発に関わっている様な人たちはこういう問題はとっくに把握はしていて、簡単に治せる問題に関しては解決をしつつ、既に動いているgemなどとの互換性の問題で簡単に治せなくなって残った問題が上に挙げたもの、という事だと思うのです。

じゃぁrubyはこれを何とか出来るかっていうと、「難しいからここまでこのままで来た」わけでそう考えると「ダメなんかもねー」という返事も出来るのですが。


これからへの期待

ただ、ruby使っている身分でこういうことを言うのもつまらないですし、go製のフレームワークがrailsよりも優れているとは全然思っていなくて、実際railsより生産性高いよって話を聞いたこともない訳で、会社に求められている事をひんまげて「新しい言語で書いている俺格好いい」とか思っている人達に大きな顔をさせるのも面白くないので個人的な希望を上げておきます。

ruby自体は文法的には割と稠密で、新しい文法を追加するにも空いている記号を探す所から大変になってきていますが、それ以上の問題は上に挙げたように


  1. rubyはrubygemsにロックされて設計が変えられない

  2. そして、rubygemsは古いrubyに依存して設計が変更できない

そうであれば、結局rubyの現在回っているエコシステムをどうするかって問題次第で、それってコミュニティの覚悟次第なんだと思うのですよね。

正直な問題、ruby最大のお客様であるrailsのユーザーはgemが3年以上古いrubyのサポートとかしているのは割とどうでもよかったりする訳です。

とはいえ、古いバージョンのrubyへの対応をgemが打ち切るとクリティカルに困る人たちもいるわけで、この2方向の要求を満たしていくためにrubyを支えているrubygems等にあるコード資産の管理に手を加えていく必要があると思うのですよね。

具体的なシナリオは漠然とさせておきますが、例えばの話ruby開発陣が「rubygemsやめることにしました」という大本営発表があったとしても、これにrubyのコミュニティがこれに追随してみせるかっていうのは正直半々で、perl6みたいに大きな断絶になって長い苦しみを味わってその間にコミュニティがgdgdになっていくか、時代時代の要求に踊る様に追随して、再びハッカーのオモチャになれるかはこれからの推移を見ていくしかないかなと思っています。

まぁ、でも堅実な予想としてはrailsの需要を置き換えるような強力な言語とフレームワークが登場しない限り、ランキングの上昇はしないまでも一定の地位を維持しながら、ゆっくりズルズルと落ちていくという流れになると私は思っています。

ただ、一定の覚悟と互換性を諦めればまだまだ可能性はあるように見えるので、ここは開発陣の決断次第かなと言い換えておきましょう。