真・全力失踪

全力で道を見失うブログ

Ruby 言語が好きだ

僕が初めて Ruby という言語に触れたのは、今の会社に入社して 3 ヶ月ほど経った 2015 年の 6 月ごろでした(当時 Ruby は Version 2.2 が最新だったかな)。それ以降現在に至るまで、特に制約がない限り日常の業務はほぼ Ruby で書いています。それくらい僕の性分にマッチしています。一方、同僚の中には Ruby が好きじゃない人も多いのですが、その背景に Rubyist 特有の Going My Way な感じ(?)に対する生理的な嫌悪感をやんわりと感じるので(感じてばっかだな)、この記事では「Rubyist は嫌いでも Ruby のことは嫌いにならないで欲しい」というささやかな願いを込めて、Ruby 言語が優れていると感じるところを紹介したいと思います。

  • 文法がエレガントである✨
  • 細かいところはかなり自由である(がゆえに状況に応じて可読性を追求できる)👕
  • その割に言語仕様は非常にシンプルである💡
  • よくやる処理が標準ライブラリ関数として大体用意されている🏪

Ruby すごい

Ruby すごいな」って改めて思った象徴的なコードを紹介します。

p [*1..15]
  .permutation(5)
  .find{ |x|
    [*1..21] - (1..5).sum([]){ |n|
      (x + x).each_cons(n).map(&:sum)
    } == []
  }

このコードは、社内の有志によって催されたプログラミングコンテスト「『ビリヤード問題』を最小の文字数で解く」の優勝コードです。Ruby2.3 で書かれています。『ビリヤード問題』とは、森博嗣さんの小説「笑わない数学者」の中で実際に出題された問題です。 bookclub.kodansha.co.jp

さて、では、もう一つ問題をだそう。五つのビリヤードの玉を真珠のネックレスのように、リングに繋げてみるとしよう。玉には、それぞれナンバが書かれている。さて、この五つの玉のうち、いくつとっても良いが、隣どうし連続したものしか取れないとしよう。一つでも、二つでも、五つでも全部でも良い。しかし、離れているものはとれない。この条件で取った玉のナンバを足し合わせて、1から21までのすべての数ができるようにしたい。さあ、どのナンバの玉を、どのように並べてネックレスを作れば良いかな?

短く書くコンテストなので、たいていのコードはめちゃくちゃ読みにくくなるのですが、上のコードはなんとなく何をやっているのか読み解けませんか?

  • 次のものを標準出力する(p)、次のものとは
    • 1 から 15 の整数を並べた配列を作り([*1..15])
    • 配列から 5 つ取り出して並べた配列を列挙し(.permutation(5))
    • その中から以下を満たすものを見つける(find)、その条件とは({})
      • 「1 から 21 の整数を並べた配列」から、([*1..21])
      • 「取り出す数 n を 1 から 5 まで変えながら((1..5))
      • 以下の処理を行ったものを繋ぎ合わせた(sum([]))結果」を取り除く(-)、その処理とは({})
        • 列挙された配列 x を 2 つ繋げた新しい配列((x + x))に対して、
        • 隣り合う n 個の合計を求めて、その合計値が並んだ配列を作る(.each_cons(n).map(&:sum))
      • その結果が、空であること(== [])

すごいポイント

  • 要素を取り出して並べて列挙するようなニッチな関数が、列挙可能なオブジェクト全てに標準で備わっているのがすごい
  • メソッドの返すオブジェクトに対してメソッドを呼ぶ、いわゆるメソッドチェインで書かれているのに全然読める!(個人差があります)

文法がエレガントである

エレガントって何? って話なのですが、ここでいうエレガントさは人間にとっての親しみやすさのような意味です。

例えば、同じ処理を 10 回繰り返したいようなとき、例えば C89 では

{
    size_t i;
    for(i = 0; i < 10; ++i) {
        /* Do Something */
    }
}
  • 何回実行したか数えるカウンター(i)を用意して
  • 繰り返し処理の中で Do Something して
  • 繰り返し処理を実行するたびにカウンター(++i)を増やして
  • 繰り返し処理の中で、実行回数が 10 に達したら繰り返しをやめる

みたいなコードを、明示的に書かなければなりません。繰り返し処理の内容だけ書いて、あとはいい感じに繰り返し処理やってほしくない?

例えば Python なら

for _ in range(10):
  # Do Something
  • 0 から 10 までの範囲オブジェクトから一つずつ値を取り出して
  • 取り出す度に Do Something する

こんな風に書けますが、これもちょっと回りくどいです。

Ruby なら

10.times do
  # Do Something
end

やりたいことをそのまま書いたようなコードです。めっちゃエレガント✨✨✨

「えーーカウンターの数値使って処理したいじゃん」ですって?

10.times do |loop_count|
  # Do Something with using loop_count
end

こういうこともできます。(使わないなら書かなくても良い、というところも Ruby らしいです)

この違いは何なのか

上の例であげた C89 や Python との違いは何なのかというと、10 という数値をただのデータとして取り扱うか、何らかの機能を持ったクラスのオブジェクトとして取り扱うかというところです。C89 にはオブジェクト(クラス)という概念はありませんが、Python にはクラスがあります。しかし Python では 10 のクラスである int はあくまでも「データ」であり、オブジェクト間でやりとりされるものでしかありません。Ruby の場合、10 は Integer というクラスのオブジェクトで、このクラスは自身の量に応じて与えられた処理を繰り返す機能を持っています。

Ruby の世界では全てがオブジェクトなのです。なんてシンプル✨✨

長くなったので続きはまた後日。