Java, JavaScript, Pythonなど、多くの言語でUnicodeを用いたUCS(Universal Character Set)方式を採用している理由は、推測ではありますが、
- Unicodeで十分と思った
- UCS方式の問題にあまり遭遇したことがなかった
- CSI方式は実装が複雑すぎて現実的ではないと思った
とかではないでしょうか。これらの言語の設計者はみな欧米人で、だいたいASCIIかISO-8859でテキストが表現できてしまうので、マルチバイト文字の闇を覗き込む機会はほとんどなかったんですよね。
一方、私を含めて日本人(またはアジア人)たちは古くからマルチバイトの文字コード(Shift-JISとかEUC-JPとか)を複数扱うプログラミングの経験が豊富でしたし、文字コード相互変換に伴う問題や苦痛をよーく承知していました。
それを踏まえて、RubyがUCS方式を採用していない理由はふたつあります。
- UCS正規化することによって失われる情報があるから
- UCS正規化によって発生する変換コストはムダだから
Unicodeに収録されている文字数はどんどん増加しています。そのため、今では昔のようにUnicodeに収録されていない文字を持つ文字コードはほとんどなくなりましたが(過去にはGB18030のようなものもありました)、しかし、Shift-JIS系の文字コードをUnicodeに変換する時のバッ
Java, JavaScript, Pythonなど、多くの言語でUnicodeを用いたUCS(Universal Character Set)方式を採用している理由は、推測ではありますが、
- Unicodeで十分と思った
- UCS方式の問題にあまり遭遇したことがなかった
- CSI方式は実装が複雑すぎて現実的ではないと思った
とかではないでしょうか。これらの言語の設計者はみな欧米人で、だいたいASCIIかISO-8859でテキストが表現できてしまうので、マルチバイト文字の闇を覗き込む機会はほとんどなかったんですよね。
一方、私を含めて日本人(またはアジア人)たちは古くからマルチバイトの文字コード(Shift-JISとかEUC-JPとか)を複数扱うプログラミングの経験が豊富でしたし、文字コード相互変換に伴う問題や苦痛をよーく承知していました。
それを踏まえて、RubyがUCS方式を採用していない理由はふたつあります。
- UCS正規化することによって失われる情報があるから
- UCS正規化によって発生する変換コストはムダだから
Unicodeに収録されている文字数はどんどん増加しています。そのため、今では昔のようにUnicodeに収録されていない文字を持つ文字コードはほとんどなくなりましたが(過去にはGB18030のようなものもありました)、しかし、Shift-JIS系の文字コードをUnicodeに変換する時のバックスラッシュと円マークの扱いのように外部からの指定なしには原理的に区別が不可能なものもあります。Unicodeに変換すればすべてOKというのはUnicode論者の楽観主義にすぎません。まあ、それはいつものことですし(2バイトで表現できる65536文字で世界中の文字が表現できるとドヤ顔された時にはキレそうになりました)、問題になるケースが以前に比べて劇的に減っているのは素晴らしいことですが。
また、Unicodeでないデータを大量に処理するケースで、ファイルから生データを読み込み、Unicodeに変換し、文字列処理し、またUnicodeから元の文字コードに変換し直し、ファイルに書き込むという文字コード変換のコストは純粋にムダです。さらに、元の文字コードの推測がはずれたり、部分的に壊れたデータを変換すると大規模な「文字化け」が発生する危険性もあります。
これらのデメリットは、RubyでCSI(Character Set Indepentent)方式を採用させる動機になりました。幸い、過去の経験から、当初は不可能に近いと思われていたCSI方式の効率良い実装方法もわかってきましたし、可能な以上、やらない手はないというのがRubyの方針でした。
以前からShift-JISやEUC-JPの大量のテキストデータを抱えていた日本人と、過去のテキストは全部ASCIIで、これからはもしかしてUnicodeのテキストも少しは扱うかもしれないという姿勢の欧米人とはスタート地点がだいぶ異なるんですよね。
この方針を決定してから20年以上経ちました。当初はまた新しい文字コードが登場したよ、やれやれ、という感じだったUnicodeも、ようやく「真のユニバーサル文字コード」になりつつあるようです。まだShift-JISのテキストデータとか結構残ってますが。
こうなると、CSI方式の必要性は下がってしまって残念と言えば残念なのですが、CSI方式はUCS方式のスーパーセットなので、なんの問題もありません。ただ、この様子ではCSI方式を採用するプログラミング言語が今後登場することはなさそうで、Rubyのアレは将来ロストテクノロジーになってしまいそうなことだけは心から残念です。
Ruby が遅いというのが先入観なのかどうなのかは、話しているコンテキストに左右されると思います。
Ruby や Python の様なスクリプト言語を取り上げるのであれば、遅いと感じる点は大きく2つに分けられます。
- 単性能が遅い
- ロードが遅い
他の回答者の方が例に出されたGo言語はコンパイル型言語です。プログラムのソースコードはコンパイルされ、ほぼほぼ OS が実行可能なバイナリの状態で実行モジュールが作られます。それに比べて Ruby や Python 等のスクリプト言語はソースコードのままディスクに保存され、インタプリタにより解釈され実行されます。(一部のモジュールは言語拡張という形式になっておりテキストファイルではなくほぼほぼC言語で書かれている事もあります)
大よそは以下の手順です。
- ファイルの読み込み
- 句解析
- 構文解析
- 必要であれば最適化処理
- 評価
Go言語であればこれらの手順はコンパイルの時点で実施され、実行時には行われません。import によるパッケージの取り込みはコンパイル時点で行われ実行モジュールに静的に埋め込まれます。それに比べ Ruby や Python といったスクリプト言語は require/import を実行したタイミングで上記の手順が都度実施されます。require/import の数が多いとロードの遅延も起きますしディスクのパフォーマンスにも依存します。
尚この時点でプ
Ruby が遅いというのが先入観なのかどうなのかは、話しているコンテキストに左右されると思います。
Ruby や Python の様なスクリプト言語を取り上げるのであれば、遅いと感じる点は大きく2つに分けられます。
- 単性能が遅い
- ロードが遅い
他の回答者の方が例に出されたGo言語はコンパイル型言語です。プログラムのソースコードはコンパイルされ、ほぼほぼ OS が実行可能なバイナリの状態で実行モジュールが作られます。それに比べて Ruby や Python 等のスクリプト言語はソースコードのままディスクに保存され、インタプリタにより解釈され実行されます。(一部のモジュールは言語拡張という形式になっておりテキストファイルではなくほぼほぼC言語で書かれている事もあります)
大よそは以下の手順です。
- ファイルの読み込み
- 句解析
- 構文解析
- 必要であれば最適化処理
- 評価
Go言語であればこれらの手順はコンパイルの時点で実施され、実行時には行われません。import によるパッケージの取り込みはコンパイル時点で行われ実行モジュールに静的に埋め込まれます。それに比べ Ruby や Python といったスクリプト言語は require/import を実行したタイミングで上記の手順が都度実施されます。require/import の数が多いとロードの遅延も起きますしディスクのパフォーマンスにも依存します。
尚この時点でプログラムのソースコードとして曖昧な物をはじいてしまうプログラミング言語と曖昧なまま評価を持ち越すプログラミング言語があります。
これらの手順でプログラムがロードされると後は「評価」のみになり言わば単性能になるのですが、この曖昧を残している事で評価の処理速度が遅くなる場合があります。これは各言語の特性でもあります。
スクリプト言語の単性能はC言語で書かれた実際のコードとスクリプト言語の間にどれだけ変換処理が必要になるかに依存してきます。足し算を実行するのであれば、C言語が実際に数値として足し算を行うまでに幾つか必要な手順が実行されます。Ruby であれば VALUE オブジェクトを受けとり型をチェックし、VALUE が保持する数値を得る必要があります。また計算結果から数値型の VALUE を作り結果を格納する必要があります。
これらの手順が例えばループの中で行われるならば、それだけぶん変換処理が必要になる訳です。もちろん各スクリプト言語では日々色々な最適化が行われており、こういった処理を極限まで減らす試みが行われています。例えば nodejs は内部で v8 という JavaScript の処理系を使っていますが v8 は JavaScript をコンパイルし、アセンブラレベルまで落とす事で他のコンパイル型言語と変わらないパフォーマンスを得ています。
もちろんプログラムは足し算ばかりではありません。ディスクやネットワークにアクセスしたり、ユーザからの入力を待ち受けたりします。そしてこれらの処理は何れのスクリプト言語においても大よそC言語で書かれています。
これらの単性能を比較すると実はGo言語と大して変わらないはずなのです。またコンテキスト次第では Ruby は遅くないのです。
またプログラミング言語のパフォーマンスを比較したがる多くの人は、普段は実行されない様なフィボナッチ数列の計算をしかも1秒程度だけ実行した様なベンチマークを使ってプログラミング言語を比較しようとします。
実際のプログラムがどの様の処理を実行しているかを考えるとこういったベンチマークには殆ど意味がないのが分かると思います。
スクリプト言語が日々行っている最適化は、とても稼働の掛かるパフォーマンス計測の積み重ねの末に得られる改良なので、今すぐ特定の言語が劇的にパフォーマンスが良くなる事は無いと個人的には思っています。それよりも言語レベルで非同期 I/O に対応したりマルチコアに対応させる方が昨今の状況では重要課題と感じています。
ネットワークやディスクの I/O に関して言えば非同期 I/O が使えるかどうかでパフォーマンスがかなり左右されます。例えば先に述べた nodejs は libuv という非同期 I/O を扱うライブラリを使う事で多くのパフォーマンス課題を解決しています。もちろん非同期 I/O をサポートするには、その言語のユーザに新しい DSL を覚えて貰う必要があります。nodejs であれば 関数のコールバックを使う事で解決していますが、これが直観的ではないと言う人もいます。Go言語に関して言えば goroutine という処理を並行に行える為の仕組みが、初めから言語レベルで備わっていますが、これは単に後発の強さなのかもしれません。
Ruby も最近、笹田さんが非同期の仕組みを考えておられるらしいので、いずれ他のコンパイル型言語と同じ様な I/O パフォーマンスが高くなる仕組みが導入されるのではないかと個人的に期待しています。
※この文章は笹田さんにプレッシャーを与える為の物ではありません
手軽にプログラミングできる軽量プログラミング言語(lightweight language)においては、プログラミングのしやすさだけではなく、プログラムの読みやすさ、管理のしやすさ、ライブラリの豊富さ、サポートしている環境の多彩さ、処理速度の速さ、言語仕様の安定性(バージョン間の互換性)、といったいろいろな要素が普及度合いに大きな影響を与えます。
以前は長らくPerlが軽量プログラミング言語の王者として不動の地位を占めていましたが、オブジェクト指向への対応が不十分だっために次第にユーザが離れてしまいました。またUnicodeへの対応が不十分でライブラリ間の文字列処理の互換性がメチャクチャになったことも大きな理由でしょう。
それでもPerl6にうまく移行できれば問題なかったのですが、Perl6の言語仕様があまりに野心的過ぎてPerl5とは全く別の言語になってしまいました。複雑な仕様のマイナー言語をわざわざ学習したがるプログラマーはそれほど多くはありません。もしPerl5のプログラムをそのままPerl6でも動かすことができれば今もPerlは健在だったかも知れません。
で、90年代からPerlをバリバリ使っていた私は2000年代になってから「次に何を使うべきか」を当然考えたわけですが、Rubyは言語仕様が全く安定しなかったので選択肢には入りませんでした。バージョンがちょっと上がる度に互換性が
手軽にプログラミングできる軽量プログラミング言語(lightweight language)においては、プログラミングのしやすさだけではなく、プログラムの読みやすさ、管理のしやすさ、ライブラリの豊富さ、サポートしている環境の多彩さ、処理速度の速さ、言語仕様の安定性(バージョン間の互換性)、といったいろいろな要素が普及度合いに大きな影響を与えます。
以前は長らくPerlが軽量プログラミング言語の王者として不動の地位を占めていましたが、オブジェクト指向への対応が不十分だっために次第にユーザが離れてしまいました。またUnicodeへの対応が不十分でライブラリ間の文字列処理の互換性がメチャクチャになったことも大きな理由でしょう。
それでもPerl6にうまく移行できれば問題なかったのですが、Perl6の言語仕様があまりに野心的過ぎてPerl5とは全く別の言語になってしまいました。複雑な仕様のマイナー言語をわざわざ学習したがるプログラマーはそれほど多くはありません。もしPerl5のプログラムをそのままPerl6でも動かすことができれば今もPerlは健在だったかも知れません。
で、90年代からPerlをバリバリ使っていた私は2000年代になってから「次に何を使うべきか」を当然考えたわけですが、Rubyは言語仕様が全く安定しなかったので選択肢には入りませんでした。バージョンがちょっと上がる度に互換性がなくなるような言語は仕事では危なくて使えません。その点はPythonの方がだいぶ安定していました。この部分は言語の普及速度に大きな影響を与えたと思います。
なのでRubyは正直言いますと「日本でだけ知られているマイナーな言語」で終わると予想していました。
ではなぜRubyが世界的に有名になったかと言うと、それはもちろん「Ruby on Rails」が登場したためです。サーバサイドMVCモデル全盛期において、重量級のJavaを使うかヘボいPHPを使うかという選択肢しかなかった状況でのRoRの登場は非常に劇的なものでした。世界中に熱狂を巻き起こしたと言っても言い過ぎではないでしょう。
そして時代は移り変わり、RESTとSPAの普及によりサーバサイドMVCの全盛期は終わりを告げました。今はマイクロサービスの時代です。RoRももちろんそれに対応していますが主役を張れるほど素晴らしいというわけではありません。今はRoRはもう時代遅れのフレームワークだと多くの人に思われています。
つまり、「プログラミング言語のRubyの地位が下がった」という見方は正確ではなく、「Ruby on Railsの地位が下がった」が正しい見方と言えます。プログラミング言語としてのRubyの地位はもともとそれほど高くはなかった、という話です。
もちろんそれはRubyが言語として優れていないことを全く意味しません。それは全く関係ない話です。PHPが普及している理由がPHPが優れた言語だからではないのと同じ話です。
Pipeline operatorはまだホットな状態で(2019–06–15現在)、今後も変更が加わる可能性が高いのですが、現状において質問の意味を推測すると、pipeline operator (|>) において、当初
- 1 |>+ 2 #=> 3 (1.+(2) と同じ意味に解釈されるから)
となる仕様だったのを禁止したのはなぜかということですが、単に読みやすさに貢献しないからです。それだけでなく積極的に読みにくさに貢献してますね。
Pipeline operatorは以下の指摘があり、もっともだと思う点もあるのでなんらかの対処を考えています。
- ElixirやElm、F#などの言語では a |> b は b(a) の意味であり、同じ演算子で意味が異なるのは良くない。
- 今後、Rubyで上記の意味の演算子を追加したくなった時に既に |> 演算子が使われているのは困る
- a = 1 |> b が a = (1 |> b) ではなく (a=1) |> b と解釈されるのは嬉しくない。
- 必要性が高くない
(1)については、Rubyにおいてはレシーバーはメソッドの第1引数と考えることができるので、 a.b(1) は他言語における b(a,1) のようなものなので a |> b(1) が a.b(1) と解釈されることにあまり抵抗はなかったのですが、Rubyユーザーのメンタルモデルが意外に柔軟性にかけていたの
Pipeline operatorはまだホットな状態で(2019–06–15現在)、今後も変更が加わる可能性が高いのですが、現状において質問の意味を推測すると、pipeline operator (|>) において、当初
- 1 |>+ 2 #=> 3 (1.+(2) と同じ意味に解釈されるから)
となる仕様だったのを禁止したのはなぜかということですが、単に読みやすさに貢献しないからです。それだけでなく積極的に読みにくさに貢献してますね。
Pipeline operatorは以下の指摘があり、もっともだと思う点もあるのでなんらかの対処を考えています。
- ElixirやElm、F#などの言語では a |> b は b(a) の意味であり、同じ演算子で意味が異なるのは良くない。
- 今後、Rubyで上記の意味の演算子を追加したくなった時に既に |> 演算子が使われているのは困る
- a = 1 |> b が a = (1 |> b) ではなく (a=1) |> b と解釈されるのは嬉しくない。
- 必要性が高くない
(1)については、Rubyにおいてはレシーバーはメソッドの第1引数と考えることができるので、 a.b(1) は他言語における b(a,1) のようなものなので a |> b(1) が a.b(1) と解釈されることにあまり抵抗はなかったのですが、Rubyユーザーのメンタルモデルが意外に柔軟性にかけていたのは驚きでした。pipeline operatorという名前にしたのも原因かもしれません。chaining operatorとでも呼べばよかったかもしれません。
(2) についてはまあ将来の話なのでおいときましょう。(4)もまあ重要ではありません。(3)は確かにその通りだと思うので、優先順位の調整について検討しましょう。
今後については、
- まあ、これだけ反対されたのだからあきらめる
- 名前(pipeline)と演算子(|>)のどちらかあるいは両方を変えて残す
- 現状のまま強行
のいずれかになると思いますが、まだ、決心できていません。まあ、こうやってユーザーの話を聞くのは良いことです。気がついていなかったこともわかるし。
追記[2019–08–29]
本日付けでRubyからpipeline operatorは削除されました。ユーザーが期待するものとの解離が私が当初想定した以上に大きかったからです。また別の名前、別の演算子が思いつけば再チャレンジということもあるかもしれません。
いろいろと足りていませんが、過去廃れていったプログラミング言語のいろいろに思いを馳せると、廃れていくプログラミング言語でまず不足するのは初心者、という印象があります。つまり人の流入がなくなっていくとプログラミング言語は蛸壺になりやすそうな気がする。
いま、たとえば、RubyKaigiというカンファレンスを行うと、各国から参加者があってありがたい反面、ロシア以外のCIS諸国やブラジル以外のラテンアメリカ諸国、それとアフリカ諸国などからの参加者は残念ながらほとんどいません。それは地理的に遠いからとか、日本に入国するビザが厳しくてとか、色々理由はあるかとは思います。でも単にリーチしてないんじゃないかという思いもある。
べつに地理的な問題だけでもないと思っていて、いまRubyをとりまくデモグラフィックはまだそれ以外の一般的な人口分布とは異なると思います。さまざまな箇所で意図せずだれかを排除していることがある結果ではないでしょうか。
流入を増やすと参加者の質が変わっていくという面はあろうかと思います。でもそれは自然なことで、参加者固定のままでゆるやかに衰退していくよりはずっといい。これからのことを考えるとRubyに足りていないのは、初心者だと思います。
ほとんどどのような言語に対しても「(あるタスクには)十分に速い」と「(理論的な限界値に比べて)遅い」の両方が同時に成立するので、ある言語が一般的に遅いかどうかはあまり意味のある質問ではありません。重要なのは「私のやりたいことに対して、Rubyの実行速度的なデメリットが、その他のメリットを上回るか」です。理論的な限界値だけを追求する人は、当然「Rubyのその他のメリット」を評価していないわけなので、Rubyを使わない方がいいんじゃないかな、と思います。
プラクティカルな判断をする多くの人は、たとえば「私が作りたいWebアプリケーションの規模と当初想定されるトラフィックにたいして、RubyとRailsの性能は充分である。むしろRuby on Railsによる素早い開発を期待する」と感じるでしょう。そこで「いや、私のアプリは急速に成長して大量のトラフィックをさばくようになるから、それを見越して最初からGoとマイクロサービスを採用」という判断をする人もいて良いと思います。その判断がいつも賢明かどうかは自信がありませんが。
他の回答者の方達は楽しさを上げておられるので僕はコーディングを楽にしている側面を書いてみたいと思います。
Ruby の言語仕様は「コードを仕上げていく際に手数を少なくする」工夫が盛り込まれています。ソースの完成系を一発で書けるなんて人は殆どいません。例えばこんな例を挙げてみます。
- ファイルを読み取り
- 各行の先頭に行番号を付け
- 行を連結し
- 表示する
Python でこれを作り上げていくケースを考えてみます。まずファイルを読み込むコードを書きますよね。
- with open('foo') as f:
- print(f.readlines())
この後は行番号を付けるでしょうね。リストの操作なのでリスト内包ですね。
- with open('foo') as f:
- print([f.readlines()])
行番号のための配列インデックスがいるので enumerate を使います。
- with open('foo') as f:
- print([i for i, x in enumerate(f.readlines())])
出力したい書式にします。
- with open('foo') as f:
- print(["%5d %s" % (i+1, x) for i, x in enumerate(f.readlines())])
最後に行を結合します。
- with open('foo') as f:
- p
他の回答者の方達は楽しさを上げておられるので僕はコーディングを楽にしている側面を書いてみたいと思います。
Ruby の言語仕様は「コードを仕上げていく際に手数を少なくする」工夫が盛り込まれています。ソースの完成系を一発で書けるなんて人は殆どいません。例えばこんな例を挙げてみます。
- ファイルを読み取り
- 各行の先頭に行番号を付け
- 行を連結し
- 表示する
Python でこれを作り上げていくケースを考えてみます。まずファイルを読み込むコードを書きますよね。
- with open('foo') as f:
- print(f.readlines())
この後は行番号を付けるでしょうね。リストの操作なのでリスト内包ですね。
- with open('foo') as f:
- print([f.readlines()])
行番号のための配列インデックスがいるので enumerate を使います。
- with open('foo') as f:
- print([i for i, x in enumerate(f.readlines())])
出力したい書式にします。
- with open('foo') as f:
- print(["%5d %s" % (i+1, x) for i, x in enumerate(f.readlines())])
最後に行を結合します。
- with open('foo') as f:
- print("".join(["%5d %s" % (i+1, x) for i, x in enumerate(f.readlines())]))
おおよそこんな手順だと思います。このコードを書くのにテキストエディタでカーソルをいっぱい動かしましたよね?
では Ruby のケースを見てみましょう。まずはファイル読み込み。
- puts File.readlines('foo')
行番号が欲しいので each_with_index でしょうか。
- puts File.readlines('foo').each_with_index
結果を更新したいので map を使いますよね。
- puts File.readlines('foo').each_with_index.map
出力したい書式にします。
- puts File.readlines('foo').each_with_index.map {|x,n| "%5d %s" % [n, x]}
行を結合します。
- puts File.readlines('foo').each_with_index.map {|x,n| "%5d %s" % [n, x]}.join
どうです?カーソルはどう動きましたか?
殆ど右方向だけだったと思います。
慣れない間はマニュアルを引いたりコードを書き直したりしてカーソルが右に行ったり左に行ったりしますが、慣れてくるとカーソルは殆ど左に戻る事がなくなってきます。
Python だと慣れていてもカーソルは行ったりきたりします。
もちろん全てのコードを 1 line で書くのは良い事ではないのですが、編集しやすさというのもプログラミング言語の好き嫌いを決める一つの特色だと思います。
これはあくまで1例ですが、Ruby にはこういった工夫(意図してこう作られたかは知りません)が沢山あると思っています。
すでに笹田さんがブロックパラメーターのシャドウイングについて述べてくださいました。
Rubyに実際に行われたローカル変数スコープの改善という意味では、それで合っているのですが、もうひとつ、あまり広く知られることなく結局不採用になったローカル変数スコープの改善案について語っておきます。
Rubyのローカル変数はメソッド定義、クラス定義、ブロックによってスコープが構成されます。さらにブロックには外側のスコープのローカル変数を参照できるという性質があります。
これは通常大変便利な性質なのですが、以下のようなケースではちょっと不便な思いをします。
- array.each do |x|
- if x.condition?
- obj = x
- end
- end
- method(obj) # NameError!
ローカル変数objはブロックの中で初登場したのでそのスコープはブロック内のみです。6行目ではブロックの外側(有効範囲外)で変数をアクセスしているので、ローカル変数が見つからずエラーになります。これを避けるにはブロックが始まる前に obj = nil のような代入をしておいて、ローカル変数のスコープがブロックの外側であることを明示しなくてはいけません。が、これは処理の本質とは無関係で無駄です。
この件について悩んでいたある日、玉造温泉につかっている時にひらめきました。ブロックの外側でブロックローカ
すでに笹田さんがブロックパラメーターのシャドウイングについて述べてくださいました。
Rubyに実際に行われたローカル変数スコープの改善という意味では、それで合っているのですが、もうひとつ、あまり広く知られることなく結局不採用になったローカル変数スコープの改善案について語っておきます。
Rubyのローカル変数はメソッド定義、クラス定義、ブロックによってスコープが構成されます。さらにブロックには外側のスコープのローカル変数を参照できるという性質があります。
これは通常大変便利な性質なのですが、以下のようなケースではちょっと不便な思いをします。
- array.each do |x|
- if x.condition?
- obj = x
- end
- end
- method(obj) # NameError!
ローカル変数objはブロックの中で初登場したのでそのスコープはブロック内のみです。6行目ではブロックの外側(有効範囲外)で変数をアクセスしているので、ローカル変数が見つからずエラーになります。これを避けるにはブロックが始まる前に obj = nil のような代入をしておいて、ローカル変数のスコープがブロックの外側であることを明示しなくてはいけません。が、これは処理の本質とは無関係で無駄です。
この件について悩んでいたある日、玉造温泉につかっている時にひらめきました。ブロックの外側でブロックローカル変数が使われた時(上記サンプルの6行目のように)は、スコープが自動的に拡張されるというルールを導入すれば、無駄な代入を避けることができるのではないか、と。それからしばらく、このアイディアにlocal variable scope propagationという名前をつけて温めていました。
結構よいアイディアではないかと思いましたが、実装が若干面倒だったのと、タイプミスにより偶然変数名がかぶってしまった時のダメージが大きいことから最終的に不採用となりました。
はっきりと覚えてはいませんが、このインタビューはこのアイディアを思いついてから間がなかったのではないかと思います。せっかくのアイディアでしたが、結局は採用されることなく、Rubyにおけるローカル変数スコープの改善は、ブロックパラメーターのシャドウイングにとどまったということです。
あとから考えるとJavaScriptのhoistingにちょっと似てますね。向こうのほうがだいぶシンプルですが。
僕は Ruby コミッタではないですが式として呼び出す場合(右項)にカッコを強制したいです。
- def sum(*args)
- args.sum
- end
こういうコードがあった場合
- a = sum(1, 2); puts a # 3
- a = sum 1, 2; puts a # 3
これらの結果は 3 です。
- a = sum(1), 2; puts a # 1
これの結果は 1 です。(※1)
- puts sum 1, 2 # 3
- puts sum(1, 2) # 3
これらの結果は 3 です。
- puts sum(1), 2 # 1 2
これは 1 と 2 が表示されます。(※1 と一貫性が無いように見えます)
- def take1(x)
- p x
- end
これは引数を1つとる関数です。
- take1 sum(1) # 1
- take1 sum(1), 2 # wrong number of arguments (given 2, expected 1) (ArgumentError)
(若干のモヤモヤ感)
- def take2(*args)
- p args
- end
これは引数を複数取る関数です。
- take2 sum(1) # [1]
- take2 sum(1), 2 # [1, 2]
- take2 sum 1, 2 # [3]
んー。では sum を以下の様に変更してみます。
- def sum(x, y)
- x + y
- end
そ
僕は Ruby コミッタではないですが式として呼び出す場合(右項)にカッコを強制したいです。
- def sum(*args)
- args.sum
- end
こういうコードがあった場合
- a = sum(1, 2); puts a # 3
- a = sum 1, 2; puts a # 3
これらの結果は 3 です。
- a = sum(1), 2; puts a # 1
これの結果は 1 です。(※1)
- puts sum 1, 2 # 3
- puts sum(1, 2) # 3
これらの結果は 3 です。
- puts sum(1), 2 # 1 2
これは 1 と 2 が表示されます。(※1 と一貫性が無いように見えます)
- def take1(x)
- p x
- end
これは引数を1つとる関数です。
- take1 sum(1) # 1
- take1 sum(1), 2 # wrong number of arguments (given 2, expected 1) (ArgumentError)
(若干のモヤモヤ感)
- def take2(*args)
- p args
- end
これは引数を複数取る関数です。
- take2 sum(1) # [1]
- take2 sum(1), 2 # [1, 2]
- take2 sum 1, 2 # [3]
んー。では sum を以下の様に変更してみます。
- def sum(x, y)
- x + y
- end
そうすると
- take2 sum 1, 2 # [3]
- take2 sum(1), 2 # wrong number of arguments (given 2, expected 1) (ArgumentError)
となります。つまり sum/take1/take2 が複数引数を取る事をちゃんと理解していないと問題が起きうる訳です。
Ruby は DSL を作るのが得意な言語でカッコが無い事で Human Readable な記法を生み出しているのは理解していますが、例えばカッコを強制させるための仕組みがあってもいいんじゃないかなーと思います。
もしくは右項にある関数呼び出しの場合はカッコを明示的に書かせるというのもありかもしれません。
RubyのendはEiffelから受け継ぎました。さらに遡るとAlgolに至ると思います。これは中括弧よりも古い、由緒正しい記法です。
「ネストが深くなるととても見難い」とのことですが、C, C++, Java, JavaScriptのようにブロック構造の終端に中括弧を使う言語でも、スタイルとしては1行1中括弧を採用しているはずで、1行に「end」の3文字とある場合とで視認性に大差があるとは思えませんから、恐らくは慣れの問題だと思われます。別件としてネストがあまり深くなるスタイルは、そもそも推奨されないということもあります。
Pythonのようにインデントでブロックを表現する方法であれば、ネストが閉じる時のendや中括弧の連鎖がなくなるので、望ましいと感じる人も多いのですが、あれはあれで、
- 終端がひとめでわからない
- テンプレートに向かない
- 式と文の区別が明確すぎてつらい(Python限定)
- オートインデントが効かない
など嬉しくない点もあります。
「end」記法には良い点もあって、多くの言語がブロック終端に中括弧を使うため、Rubyの文法はそれらの言語とは異なることがひとめで認識されて、視覚情報をヒントにしたコンテキストスイッチが容易になります。私のようにRubyとCを頻繁に行き来する人、あるいはバックエンドをRuby、フロントエンドをJavaScriptで開発する人にとって、現在触っている言語を
RubyのendはEiffelから受け継ぎました。さらに遡るとAlgolに至ると思います。これは中括弧よりも古い、由緒正しい記法です。
「ネストが深くなるととても見難い」とのことですが、C, C++, Java, JavaScriptのようにブロック構造の終端に中括弧を使う言語でも、スタイルとしては1行1中括弧を採用しているはずで、1行に「end」の3文字とある場合とで視認性に大差があるとは思えませんから、恐らくは慣れの問題だと思われます。別件としてネストがあまり深くなるスタイルは、そもそも推奨されないということもあります。
Pythonのようにインデントでブロックを表現する方法であれば、ネストが閉じる時のendや中括弧の連鎖がなくなるので、望ましいと感じる人も多いのですが、あれはあれで、
- 終端がひとめでわからない
- テンプレートに向かない
- 式と文の区別が明確すぎてつらい(Python限定)
- オートインデントが効かない
など嬉しくない点もあります。
「end」記法には良い点もあって、多くの言語がブロック終端に中括弧を使うため、Rubyの文法はそれらの言語とは異なることがひとめで認識されて、視覚情報をヒントにしたコンテキストスイッチが容易になります。私のようにRubyとCを頻繁に行き来する人、あるいはバックエンドをRuby、フロントエンドをJavaScriptで開発する人にとって、現在触っている言語を簡単に認識できるのは脳の混乱を避けるのに有効です。
最大の理由はセキュリティです。
カレントディレクトリに既存のライブラリと同名のファイルがあり、そちらが優先されると第三者により予想されないオーバーライドが発生する可能性があり、それが悪意あるものであれば深刻なセキュリティ問題が発生します。
そうでなくても、意図しない状態で違うライブラリを呼んでしまう事故(たとえば'test'のようなライブラリを呼んでいて、手元に'test.rb'があるとか)が発生したことが報告されており、それを避けることも考慮されました。
Rubyは遅いについては先入観だけとも言い切れない面はたしかにあり、rubyで書かれていたプログラムをgolangで書き直して高速化しましたというような例が存在するのも事実です。これは問題であるとは認識しており、高速化の試みは色々と行われているところです。
一方で、そのようなレベルで酷使するほどRubyが使えている例は稀、という話もあります。いまだとRubyを使っている用途としてはWebアプリケーションバックエンドが主流かと思いますが、この領域ですと、処理の時間の大部分はIOが占めている場合がほとんどです。Rubyは遅いというほど実際のアプリケーションパフォーマンスにインパクトが出ることは少ないかもしれません。
いずれにせよ、Rubyを速くする仕事をしている人間としては、イメージだけで語るのは不毛と感じます。速い遅いは定量的な評価が可能な領域です。自分なら、マーケティングをしたいということでなければ、Rubyは遅いという意見に対する反論としては、まずその遅いRubyとやらを屏風から出してください将軍様というような言い方をするかもしれませんね。
もともとはRubyを褒めてくださる人が言い出したことですが、私としては(Rubyを)設計する人が心に留めるべき原則というつもりで捉えていました。しかし、Rubyのユーザーは多様で背景もそれぞれ異なるため、コミュニティが大きくなるにつれ、それぞれ異なる事柄に「驚く」ことが増えてきました。また、Rubyに対してなんらかの提案を述べるときにも、この「驚き最小の法則」を理由にする例が目立ってきました。しかし、そのような提案においては「驚き最小の法則」は結局は「私が過去慣れ親しんだ◯◯言語と違う」ということしか意味しておらず、提案の有効性を強化するためにまったく役立たず、かえって議論を阻害してしまうことが増えてきました。この結果、少なくとも議論においては個々人の背景や嗜好に強く依存してしまう「驚き最小の法則」という言葉は使わないようにしよう、と申し合わせたわけです。
元々は「言語が作りたかった」というのがRubyを作った動機なので、別にスクリプト言語である必要はなかったのですが、結局以下のような理由でスクリプト言語になりました。
- せっかく作った言語は自分で使いたかったので、プログラマとして普段使いしていた言語であるC、またはシェル(あとたまにPerl)を置き換えるような言語が欲しかった
- Cのようなコンパイル型言語は複数CPU/OS対応などバックエンドの移植性が大変なのでインタプリタ型言語が良さそうだった。
- もちろん、Cに変換してからCコンパイラを使うというアプローチもありえたが、それは大学の卒論でやったので違うアプローチを取りたかった
というわけで、Rubyがスクリプト言語だったのは意外と消去法で決まっています。今思えば正解だったわけですが、未来を予測してそうしたわけではないということは面白いですね。
ブロックパラメータのシャドウィングでしょうか。
「i=10;1.times{|i|}; p i」このようなプログラムで、ブロックパラメータの i は何を意味するか、ということなんですが、このように変わっていきました。
- Ruby 1.8 までは、外側の i を指していました。そのため、1.times{|i| の部分で、ブロックの外側の変数 i に、i=0 と代入され、ブロックを抜けた後の p i で 0 を出力しました。
- Ruby 1.9 からは、shadowing といって、ブロックの中のローカル変数になるようになりました。そのため、外側の変数とは別の変数 i に i=0 と代入され、ブロックから抜けたら、i はそのままなので、p i は 10 を出力しました。ただし、外側と同名の変数を新たに使うよ、ということを明示するため、ruby -w で起動すると、"warning: shadowing outer local variable - i" という警告がでていました。
- Ruby 2.6 から、警告が消えました。
ついでに、ブロックパラメータではないけれど、ブロック内で同名の新しい変数を明示的に宣言するための記法 `|; i|` が Ruby 1.9 からサポートされました。x=10; 1.times{|;x|x=0}; p x というプログラムで、Ruby 1.9 からは 10 が出力さ
ブロックパラメータのシャドウィングでしょうか。
「i=10;1.times{|i|}; p i」このようなプログラムで、ブロックパラメータの i は何を意味するか、ということなんですが、このように変わっていきました。
- Ruby 1.8 までは、外側の i を指していました。そのため、1.times{|i| の部分で、ブロックの外側の変数 i に、i=0 と代入され、ブロックを抜けた後の p i で 0 を出力しました。
- Ruby 1.9 からは、shadowing といって、ブロックの中のローカル変数になるようになりました。そのため、外側の変数とは別の変数 i に i=0 と代入され、ブロックから抜けたら、i はそのままなので、p i は 10 を出力しました。ただし、外側と同名の変数を新たに使うよ、ということを明示するため、ruby -w で起動すると、"warning: shadowing outer local variable - i" という警告がでていました。
- Ruby 2.6 から、警告が消えました。
ついでに、ブロックパラメータではないけれど、ブロック内で同名の新しい変数を明示的に宣言するための記法 `|; i|` が Ruby 1.9 からサポートされました。x=10; 1.times{|;x|x=0}; p x というプログラムで、Ruby 1.9 からは 10 が出力されます。
Ruby is designed to make programmers happy.
Rubyはプログラマーを幸せにすることを目標に設計されています。
プログラミング言語を評価するときに「スピード」とか「効率」みたいな工場の機械みたいな形容詞よりも、「ハッピー」とか「ラブ」といったまるで恋愛のような表現がよく出てくるのはRubyぐらいじゃないでしょうか。
そういう、単なる道具という域を超えてアピールするものがあるところがユニークなのだと思います。
また、RubyにはMINASWANというモットーがあります。
「Matz(Rubyの作者)はいい人なのでみんないい人」みたいな感じでしょうか。
殺伐としたエンジニアの世界で、優しく、ホッとする感じがありますね。
Ruby is designed to make programmers happy.
Rubyはプログラマーを幸せにすることを目標に設計されています。
プログラミング言語を評価するときに「スピード」とか「効率」みたいな工場の機械みたいな形容詞よりも、「ハッピー」とか「ラブ」といったまるで恋愛のような表現がよく出てくるのはRubyぐらいじゃないでしょうか。
そういう、単なる道具という域を超えてアピールするものがあるところがユニークなのだと思います。
また、RubyにはMINASWANというモットーがあります。
「Matz(Rubyの作者)はいい人なのでみんないい人」みたいな感じでしょうか。
殺伐としたエンジニアの世界で、優しく、ホッとする感じがありますね。
配列の範囲を越えてアクセスした場合、例外を出す陣営と、nilのような「空」を表現する値を返す陣営があります。前者の陣営は配列とは限られた範囲の値の並びであると認識し、後者の陣営は配列は仮想的に無限の領域であると認識しているようです。
認識についての違いは上記のとおりですが、実用としては前者は範囲内のアクセスが強制されるのでより「正しさ」が保証される傾向があり、後者は例外捕捉や事前の範囲チェックの必要性が下がりより「楽」な傾向があります。どちらを選ぶのかは言語のデザインポリシーによるのでしょうが、Rubyの場合は「楽」を優先したということになります。
だいたい以下のような理由です。
- クラスやメソッドなどソフトウェアを構成する部品にあたる存在がオブジェクトであり、プログラムから操作可能である
- 動的型言語なので、型の整合性を気にせず実行時に自由に変更できる
- evalなどをはじめとするメタプログラミング用メソッドが充実している
Rubyを越えるレベルでメタプログラミングを行う、Lispのようなプログラムコードそのものをデータとして処理できる言語もありますが、Rubyはそこまでは踏み込まないと決めています。
そこまでしちゃうと言語文法の意味を変えてしまうので(マクロなど)、実行中のソフトウェアの仕様に踏み込まないと、プログラムの意味を読み取れなくなってしまうからです。コンパイラにはどうということはないのですが、人間には厳しいだろうと見積もっています。
いやあ、まさにそれが今までRubyにマクロがなかった理由でもあるのです。
他言語(特にLisp)においてマクロの主要な用途は以下のようなものだと思います。
- コンパイル時計算
- 制御構造の拡張
しかし、Rubyの場合、メタプログラミングとブロックの組み合わせでこれらのほとんどは実現できてしまいます。また、マクロを多用して構文を自由に拡張できると、容易に他人には読めないプログラムを量産することに繋がります。あと、Rubyのようなオブジェクト指向言語は基本的な振る舞い(どのメソッドが呼ばれるか、とか)が決定するのは実行時で、コンパイル時に決定されるマクロとは相性が悪いのも無視できません。
では、それでもなおRuby3以降でマクロの導入をほのめかしたのはなぜかというと、
- JuliaやRustのようにマクロの名前に特別なルールを導入することで、マクロによる混乱を避けることができる
- C++におけるMPIのような並列化などを上手に隠蔽できる
- コンパイル時処理によって高速化できる
- これまでマジックコメントでアドホックに実現してきたものを統一的にカバーできる
などの可能性に期待してのことです。しかし、現時点で構想しているのは、なんらかの方法でASTを操作することを許すことだけで、それが伝統的なマクロになるのか、あるいはコードに対するなんらかのアトリビュートであるのか、さっぱり決まっていません。
現時点では、まつもとが過去に
いやあ、まさにそれが今までRubyにマクロがなかった理由でもあるのです。
他言語(特にLisp)においてマクロの主要な用途は以下のようなものだと思います。
- コンパイル時計算
- 制御構造の拡張
しかし、Rubyの場合、メタプログラミングとブロックの組み合わせでこれらのほとんどは実現できてしまいます。また、マクロを多用して構文を自由に拡張できると、容易に他人には読めないプログラムを量産することに繋がります。あと、Rubyのようなオブジェクト指向言語は基本的な振る舞い(どのメソッドが呼ばれるか、とか)が決定するのは実行時で、コンパイル時に決定されるマクロとは相性が悪いのも無視できません。
では、それでもなおRuby3以降でマクロの導入をほのめかしたのはなぜかというと、
- JuliaやRustのようにマクロの名前に特別なルールを導入することで、マクロによる混乱を避けることができる
- C++におけるMPIのような並列化などを上手に隠蔽できる
- コンパイル時処理によって高速化できる
- これまでマジックコメントでアドホックに実現してきたものを統一的にカバーできる
などの可能性に期待してのことです。しかし、現時点で構想しているのは、なんらかの方法でASTを操作することを許すことだけで、それが伝統的なマクロになるのか、あるいはコードに対するなんらかのアトリビュートであるのか、さっぱり決まっていません。
現時点では、まつもとが過去にもよくやってきた「キーノートで唐突に発表される思いつき」レベルです。なお、まつもとの思いつきの勝率は現在7割弱です。1/3は単なる思いつきで終わるということですね。さて、マクロはどちらに転ぶのか。楽しみですね。
結論から言うと、Rubyが静的型付けだろうと動的型付けだろうと出来る最適化はそれほど変わらないです。
詳細を述べると、キーワードとしては脱最適化と抽象実行が挙げられると思います。
Rubyが最適化がしにくいのは動的型付けという理由ではなく、もっと邪悪な言語仕様、すなわちeval, binding, メソッドの再定義などがあるためです。その証拠に、動的型付け言語でもLuaやJavascriptでは静的型付けと遜色ない最適化をおこなう処理系が存在します。
ぶっちゃけていうと、evalやメソッドの再定義を含むプログラムに最適化は無理です(極めて限定された条件下なら可能かもしれません)。そこでどうするかと言えば、evalとかメソッドの再定義とかで破綻するまではガンガンに最適化して、もし破綻したら初めからコンパイルし直すという戦略です。これを一般的に脱最適化(deoptimization)と呼び、Rubyなどの言語で最適化をおこなう際には必須なものです。色々な論文が出ているはずです(私は英語が苦手でよく知らない)。
次にRubyが動的型付けだからコンパイル時に型が分からないだろうと思うと思いますが、決してそんなことはありません。例えば抽象実行というテクニックを使うと*ある程度*静的に型がわかります。詳しくは、ruby-type-profilerで検索して貰えるといろいろ分かると思います。
実は私もRu
結論から言うと、Rubyが静的型付けだろうと動的型付けだろうと出来る最適化はそれほど変わらないです。
詳細を述べると、キーワードとしては脱最適化と抽象実行が挙げられると思います。
Rubyが最適化がしにくいのは動的型付けという理由ではなく、もっと邪悪な言語仕様、すなわちeval, binding, メソッドの再定義などがあるためです。その証拠に、動的型付け言語でもLuaやJavascriptでは静的型付けと遜色ない最適化をおこなう処理系が存在します。
ぶっちゃけていうと、evalやメソッドの再定義を含むプログラムに最適化は無理です(極めて限定された条件下なら可能かもしれません)。そこでどうするかと言えば、evalとかメソッドの再定義とかで破綻するまではガンガンに最適化して、もし破綻したら初めからコンパイルし直すという戦略です。これを一般的に脱最適化(deoptimization)と呼び、Rubyなどの言語で最適化をおこなう際には必須なものです。色々な論文が出ているはずです(私は英語が苦手でよく知らない)。
次にRubyが動的型付けだからコンパイル時に型が分からないだろうと思うと思いますが、決してそんなことはありません。例えば抽象実行というテクニックを使うと*ある程度*静的に型がわかります。詳しくは、ruby-type-profilerで検索して貰えるといろいろ分かると思います。
実は私もRubyに静的型付けみたいな最適化をおこなう処理系を作っています。miura1729/mruby-meta-circular を見てもらえるとうれしいですが、たいしたドキュメントもないし何も得られるものは無いでしょう。ただ、RubyがCより速いとか普通に起こります。
Rubyの開発を始めた1993年の時点で、
- yaccというコンパイラ・コンパイラが存在していた
- プログラミング言語の実装例が豊富 (Perl, Python, OCaml, Lisp)
- OSのシステムコールなどへのアクセスが容易
- 私が慣れ親しんでいて経験値が高かった
などの理由でCを選びました。
2019年の今であれば異なる選択肢(GoとかRustとか)もありえるかもしれませんが、それらの言語に対する私の経験値が低いままなので、やっぱりCを選びそうです。
Ruby の開発者が日本人ということで、コミュニティが大きく日本語の情報も豊富です。また、Ruby on Rails は日本の企業でよく使われているため、生徒の就職先を探すのが容易で、講師の採用もやりやすいです。
また、Ruby on Rails は、機能が豊富で、その機能を覚えていくことで、それなりの Web アプリが開発できるようになるので、教えやすいということもあると思います。
一方で、フロントエンドで React や Vue や Flutter が人気になってきています。それらを使う場合、バックエンドでは、GraphQL や Rest が実装できればいいので、Ruby on Rails の多機能さって、それほど必要なくなっていくと思われます。プログラミングスクールで学ぶ場合には、すぐに就職したい場合には、Ruby on Rails はいいのですが、そうでなければ、何を学ぶかもう少し考えた方がいいです。
まずは短い回答から:
Rubyに非互換な変更を加える時には、それによるメリットが明らかにデメリットを上回る時(だけ)です。そして、非互換によるデメリットはひどく大きいので現実的にはほぼ不可能です。
では、背景を含めてもう少し詳しく説明しましょう。
もちろん、最初から上記の変更ポリシーを採用していたわけではなくて、Rubyのユーザーがまだそれほど多くなかった頃には、言語仕様も安定しておらず、比較的頻繁に非互換な変更が行われていました。ユーザーも承知したもので、「はいはい、ここを直すのね」という感じで気軽に対応していたように思います。広く知られる前のRubyは、ほとんどのユーザーは言語そのものに興味があるタイプでしたし、言語が変化(進化)することに寛容だったと思います。
しかし、Rubyが世界中で広く使われるようになって、Rubyが好きだからという理由でRubyを選んだのではない人が増えてくると、非互換な変更によるダメージが大きくなってきます。たとえば、Ruby1.9においては文字列とエンコーディングの扱いにおいてかなり非互換な変更を行いました。これは大規模な書換えを必要とする非互換ではありませんでしたが、それでもRubyコミュニティ全体がRuby1.8から1.9に移行するのに5年以上の月日が必要でした。10年以上かかっているPython3よりはマシかもしれませんが。
この経験は、非互換な変更に
まずは短い回答から:
Rubyに非互換な変更を加える時には、それによるメリットが明らかにデメリットを上回る時(だけ)です。そして、非互換によるデメリットはひどく大きいので現実的にはほぼ不可能です。
では、背景を含めてもう少し詳しく説明しましょう。
もちろん、最初から上記の変更ポリシーを採用していたわけではなくて、Rubyのユーザーがまだそれほど多くなかった頃には、言語仕様も安定しておらず、比較的頻繁に非互換な変更が行われていました。ユーザーも承知したもので、「はいはい、ここを直すのね」という感じで気軽に対応していたように思います。広く知られる前のRubyは、ほとんどのユーザーは言語そのものに興味があるタイプでしたし、言語が変化(進化)することに寛容だったと思います。
しかし、Rubyが世界中で広く使われるようになって、Rubyが好きだからという理由でRubyを選んだのではない人が増えてくると、非互換な変更によるダメージが大きくなってきます。たとえば、Ruby1.9においては文字列とエンコーディングの扱いにおいてかなり非互換な変更を行いました。これは大規模な書換えを必要とする非互換ではありませんでしたが、それでもRubyコミュニティ全体がRuby1.8から1.9に移行するのに5年以上の月日が必要でした。10年以上かかっているPython3よりはマシかもしれませんが。
この経験は、非互換な変更に対する私の考え方に大きく影響を与えました。ですから、上記のようなポリシーとなるわけです。もちろん、現在のRubyの仕様には「失敗したな」とか「別のデザインのほうが良かったな」と思うところがないわけではないですが、それを直して得られるメリットは言語設計者の自己満足であり、ユーザーにとってほとんどプラスになるものではありません。それくらいだったら、過去の失敗を引きずったほうがマシです。
これが現時点の状態ですが、未来においてこの状況は若干変化するのではないかと思っています。Rubyプログラムを静的解析する技術は次第に向上しており、たとえばある種の非互換性については、静的解析ツールによって検出・自動変換できるようになる未来はそう遠くないと思います。そうなれば、「Rubyの新しいバージョンで実行する前に、このツールで自動変換してください」と言えば済むようになり、もっと気軽に非互換な変更を行うことができるようになる未来が来る可能性はあります。それとは別に学習コストもあるので、別の言語になってしまうような変更は論外でしょうが。
この時、自動変換は100%確実に変更できる必要があります。Python2からPython3へ対応するツール(2to3)が100%確実に変換できていれば現在の不幸は起きていなかったでしょう。とすると、将来許容される非互換性というのは、ツールで確実に検出・自動変換できるという条件を満たすものになるでしょう。
さて、質問にあった frozen string literals ですが、これは現状でもマジックコメントをつければ有効にすることができるのですが、これをデフォルト化するかどうかはRuby3のホットなトピックのひとつでした。
しかし、これに対してRuby3においても frozen string literals をデフォルトとすることはないという判断をしました。理由としては
- いくつかのベンチマーク結果によれば frozen string literalsをデフォルト化してもパフォーマンス改善は大きくない
- frozen string literals のデフォルト化はかなり大きな非互換であり、ほとんどすべてのRubyプログラムがなんらかの対応を必要とする
- frozen string literals 対応はデータフロー解析が必要であり、簡単に検出・変換ができない。あるファイルで作られた文字列がまったく別のライブラリでエラーを起こすことがありえる
ということで、これは将来においてもたちの悪い非互換性変更であると判断しました。ツールの進化により別の未来が見えるようになるかもしれませんが。
spaCyはわりと新しいツールですよね。
それまでは、pythonではNLTKが有名でした。
NLTKに当るツールはRubyにもいくつかあります。treatがそのひとつです。
spaCyはこれらのツールの出来ることを拡張した新世代のものです。自然言語処理や機械学習の分野ではPythonに一日の長がありRubyが最新のものにキャッチアップするのは難しいと思います。
先日、言語処理学会の年次大会が名古屋でありました。私も参加してきたのですが、殆どの方がPythonやC++で実装されていました。まずはこういった人たちにRubyを使ってもらわないと差は開く一方ですね。
そんな中Rubyを使っている人達も一部にはいました!
クックパッド、LeagalForceといった自然言語処理を必要としている企業(2社ともRubyKaigiのスポンサーもしています)です。ただし、自然言語処理のところはPythonで書かれているそうです。それらの企業でもRubyで自然言語処理ができたらということには興味はあるようです。
それと、名古屋大学の佐藤理史先生の研究室ではなんとRubyで自然言語処理をされているそうです。佐藤先生とも少し話をしましたがやはりRubyは手に馴染んでいてRubyで出来るということにとても価値を感じているようでした。
質問への回答としては、自然言語処理に使う人が少ないからということになりそうですが、今後も
spaCyはわりと新しいツールですよね。
それまでは、pythonではNLTKが有名でした。
NLTKに当るツールはRubyにもいくつかあります。treatがそのひとつです。
spaCyはこれらのツールの出来ることを拡張した新世代のものです。自然言語処理や機械学習の分野ではPythonに一日の長がありRubyが最新のものにキャッチアップするのは難しいと思います。
先日、言語処理学会の年次大会が名古屋でありました。私も参加してきたのですが、殆どの方がPythonやC++で実装されていました。まずはこういった人たちにRubyを使ってもらわないと差は開く一方ですね。
そんな中Rubyを使っている人達も一部にはいました!
クックパッド、LeagalForceといった自然言語処理を必要としている企業(2社ともRubyKaigiのスポンサーもしています)です。ただし、自然言語処理のところはPythonで書かれているそうです。それらの企業でもRubyで自然言語処理ができたらということには興味はあるようです。
それと、名古屋大学の佐藤理史先生の研究室ではなんとRubyで自然言語処理をされているそうです。佐藤先生とも少し話をしましたがやはりRubyは手に馴染んでいてRubyで出来るということにとても価値を感じているようでした。
質問への回答としては、自然言語処理に使う人が少ないからということになりそうですが、今後もそうとは限りません。特に日本語の自然言語処理においては。
ruby の人気は Ruby on Rails に依拠してるところがあったからです。
Rails は画期的なウェブフレームワークでしたが、フロントエンドの開発状況が大きく変わったことにうまく追従できませんでした。この点は Laravel が非常に良くできています。
また、ウェブアプリケーションよりもスマホのネイティブアプリケーションが重視されるようにもなりました。
このような事情で Rails に支えられてたぶんの ruby 人気が下がってるというだけだと思われます。
ruby はよい言語ですし 3 ではかなりの高速化が予定されてるので、まだまだ扱いやすい「プログラマを幸せにする言語」として残っていくでしょう。
個人的に、Ruby on Rails を使ったWebサービスを作れば良いです。
なぜなら、「Rubyで働く」と同義で、求められていることだからです。
「これからRubyエンジニアとして働き始めたい」という質問だと受け取って回答します。
「仕事でRubyを使う」ということは基本的に下記の通りです。
- 最終目標は「Webサービス」を作ること。
- これを達成するための技術として「Ruby on Rails」を使うこと。
最終目標は「Webサービス」を作ること。
基本的に、Rubyを採用している現場で作成しているものの大半がWebサービスの開発です。
一部スマホゲームもありますが、組み込み系だったりOS開発だったりは、ほぼありえません。
これを達成するための技術として「Ruby on Rails」を使うこと。
RubyだけでWebサービスを構築している現場はありません。ほぼ必ずRailsとの組み合わせです。
そのため、極端な話ですがRubyに詳しくなくてもRailsだけにかなり詳しいなら、即戦力になれます。
(Railsに詳しいのにRubyに詳しくない、というケースは普通はありえませんが・・・)
「Rubyエンジニアとして、あなたを雇う」という観点で雇う側が考えるのは、下記のような観点かと思います。
- どれくらいの技術レベルなのか?
- 即戦力となるかどうか?
- 学習意欲があるか?
- 個人でどこまで実現できるか?
- 現場のことをどこまでわか
個人的に、Ruby on Rails を使ったWebサービスを作れば良いです。
なぜなら、「Rubyで働く」と同義で、求められていることだからです。
「これからRubyエンジニアとして働き始めたい」という質問だと受け取って回答します。
「仕事でRubyを使う」ということは基本的に下記の通りです。
- 最終目標は「Webサービス」を作ること。
- これを達成するための技術として「Ruby on Rails」を使うこと。
最終目標は「Webサービス」を作ること。
基本的に、Rubyを採用している現場で作成しているものの大半がWebサービスの開発です。
一部スマホゲームもありますが、組み込み系だったりOS開発だったりは、ほぼありえません。
これを達成するための技術として「Ruby on Rails」を使うこと。
RubyだけでWebサービスを構築している現場はありません。ほぼ必ずRailsとの組み合わせです。
そのため、極端な話ですがRubyに詳しくなくてもRailsだけにかなり詳しいなら、即戦力になれます。
(Railsに詳しいのにRubyに詳しくない、というケースは普通はありえませんが・・・)
「Rubyエンジニアとして、あなたを雇う」という観点で雇う側が考えるのは、下記のような観点かと思います。
- どれくらいの技術レベルなのか?
- 即戦力となるかどうか?
- 学習意欲があるか?
- 個人でどこまで実現できるか?
- 現場のことをどこまでわかっているか?
これらの求められているスキル上げたり、他人に証明するためには
「個人的に、Ruby on Rails を使ったWebサービスを作れば良いです。」
- 基本正規表現 (BRE) — sed, grepのデフォルト。
- 拡張正規表現 (ERE) — egrepのデフォルト。
- perl正規表現 (PRE) — perlで採用されている。BREやEREよりも大幅に高機能。perlの後発の多くの言語、たとえばpython, rubyなどでもサポートされている。PCREというライブラリを使えばperl以外のシステムにも導入できる。
大まかにはこの3種類を区別する必要があります。具体的な特徴についてはウィキペディアでも見てください。
言語やエディタによって、これらの一部だけがサポートされていたり独自仕様が追加されていたりします。それはそれぞれのマニュアルを確認するしかありません。