Ruby
Rails

私がRubyを選ぶ3つの理由

まえがき

この記事はグロービス Advent Calendar 2017 21日目の記事です。
グロービスではRuby 愛に溢れるエンジニアを募集しております。

はじめに

私は仕事で、もしくは趣味でコードを書く時、Rubyを選ぶ事が最も多いです。
単純にRubyが好きだから、で片付けても良いですが、Advent Calendarをきっかけに「Rubyを選ぶ理由」を棚卸ししたいと思い、記事にまとめました。

Disclaimer

以下、免責事項として予めご了承下さい。
特にRuby書いたことがあるけど、合わなかった方(きっと少なく無いと思います)は「合わない理由」ばかりが書かれている可能性が高いです。

  • 個人的な思いが強いです
  • コードで例示していますが、ちょっと強引なところがあります
  • Rubyを書いたことの無い方に新発見を伝えられる程の文章になっていません

結論

プログラマー視点で絞って理由を考えながらネットを検索したら、簡単に答えに辿り付きました。

  1. すべてがオブジェクトだから
  2. 柔軟性が高いから
  3. ブロック:表現力豊かな機能を持っているから

この3つです。
・・・「Rubyとは」ですぐ見つかりました。

RubyをRubyたらしめる特徴は沢山ありますが、Matzさん仰る「Rubyをシンプルなものではなく、自然なものにしようとしている」のは間違い無く、この3点でしょう。

と、これで終わると意味が無いので、上記3つの理由について、コードの例を挙げながら深掘りしてみます。

Rubyっぽいコード

3つの理由のうち、

  1. すべてがオブジェクトだから
  2. 柔軟性が高いから

について考えます。

まずは他の言語とRubyとで書き方が特徴的に異なる例を示して考察します。
以下は、個人的にも、そして周りのエンジニア達に聞いても「Rubyっぽい」と言われるコードです。

ActiveSupport

他の言語を書いた後にRuby を書くようになった方、特にRailsに慣れている方にお馴染みの例と思います。

「今から2日後」の計算

Ruby(正確には素の状態ではなく Rails 等によって ActiveSupport の力を手に入れた Ruby)では、「今から2日後」をこのように表現することができます。

[1] pry(main)> Time.now
=> 2017-12-19 18:33:21 +0900
[2] pry(main)> 2.days.after Time.now
=> 2017-12-21 18:33:24 +0900

要点はここ、

2.days.after Time.now

2 days after (time)now です。ほぼ自然言語です。
似たようなことを他の言語で書くと、

Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, 2);
cal.getTime();

とか、

date('Y-m-d H:i:s', strtotime('2 day', time()));

とかになるかと思います。
Ruby以外は苦手なので、もっと良い書き方できるかもしれませんが、ここではニュアンスを感じて下さい。

繰り返しになりますが、Rubyはほぼ自然言語のように書けます。
自然言語のように書けるよう言語仕様が定められている、と言えます。

2日

さらに注目したいのはここ、

2.days

2に対してメソッドを呼び出しています。

Rubyっぽさの仕組み

  1. すべてがオブジェクトだから

こそ、これが可能です。
更にステップバイステップで、この書き方を実現する仕組みを見ていきます。

インスタンス

まず、2.daysによってどのようなインスタンスが作成されるのかを確認します。

[1] pry(main)> 2.days.class
=> ActiveSupport::Duration

ActiveSupport::Durationのインスタンスです。

Railsのコードのこの辺りを確認して、ActiveSupport::Durationのインスタンスを標準的な方法で作って比較してみます。

[1] pry(main)> two_days = ActiveSupport::Duration.new 2 * 60 * 60 * 24, [[:days, 2]]
=> 2 days
[2] pry(main)> two_days == 2.days
=> true

つまり、2.days は ActiveSupport::Duration.new 2 * 60 * 60 * 24, [[:days, 2]]を実行したことと等価です。(これだけで等価というのは少々乱暴ですが、ここではその論を考慮しません)

ファクトリーメソッド

コードやドキュメントを見ると、ActiveSupport::Durationにはdaysというファクトリーメソッドがありました。
そちらではもっとシンプルに記述できます。あらためて、インスタンス同士を比較してみましょう。

[1] pry(main)> two_days = ActiveSupport::Duration.days 2
=> 2 days
[2] pry(main)> two_days == 2.days
=> true

同じでした。つまり

ActiveSupport::Duration.days 2

2.days

は同じインスタンスを返却しています。
では、なぜわざわざ別の書き方を提供しているのか、そこを考察します。

引数とレシーバー

2に注目して下さい。前者は、2が daysメソッドの引数になっていて、後者は2daysメソッドのレシーバーになっています。

元は引数になるものを、レシーバーにして同じ結果を得られます。
このような書き方ができるようにRubyは作られています。
その効用については一旦おいて、先にこれを実現できる仕組みを最後まで見てみましょう。

引数をレシーバーに取る仕組み

実装は単純です。実際のコードを見ると非常にシンプルに実現されています。
active_support/core_ext がIntegerを拡張する(正確にはその親のNumericを拡張することでIntegerを拡張する)箇所に記載があります。

該当のコードはたったこれだけです。

def days
  ActiveSupport::Duration.days(self)
end

引数として取りたいオブジェクトから、逆にselfを引数として呼んで貰います。
2.days というメソッドを実行すると、ActiveSupport::Duration.days(2)が返却されます。

2がプリミティブな値ではなく、Integerクラスのインスタンスであるから実現できる実装です。

  1. すべてがオブジェクトだから
  2. 柔軟性が高いから

まさにこの2つの理由です。
Rubyの柔軟性は「Rubyとは」の中で、

Rubyでは、ユーザーが自由にその一部を変更することができます。 やろうと思えば、Rubyのコアな部分でさえ、削除したり再定義したりできます。 既存の部品をその上に追加することすらできます。 Rubyはプログラマを制限しない言語なのです。

と説明されています。ActiveSupportはまさにその思想をもとに実装されています。

論を戻し、これができると何が嬉しいのか、それは先程書いた

2 days after (time)now です。ほぼ自然言語です。

を実現できることです。こう書いたらどうでしょう?

ActiveSupport::Duration.days(2).after Time.now

人であるプログラマーに、そんな正式名称を覚えて貰うのは本望ではありません。
プログラマーにはもっと覚え、考えるべきことが沢山あります。
「2日」という期間を表すインスタンスは、2.daysで取得できる、ということを知れば済むのはとても有意義なことです。

自分が書いた期間を表すコードを他の人に使って貰うときに、「2.daysって書いたら2日を表すインスタンスが返るよ」ということを、私は説明しなくとも伝えたい。

これが大きな理由です。

ブロック

残るもう一つの理由、

3. ブロック:表現力豊かな機能を持っているから

について、その表現力を確認してみます。

ですが、ここはちょっと細かい話になるので、途中で読んでいて嫌になったら、この論の結末を先に見てみて下さい。
そこに向けて書いても、説明が足りなさそうではありますが。

ブロックの基本的な使い方

Rubyの配列には、mapというメソッドがあり、ブロックを受け取り、レシーバーが含むそれぞれの要素をブロックの仮引数として渡しブロックを呼び出した上で、その結果を再度配列にする、という動きをします。

言葉で説明するよりも、コードを見るとその動きが分かり易いと思います。

[1] pry(main)> [1, 2, 3].map{|number| number * 2}
=> [2, 4, 6]
[2] pry(main)> [1, 2, 3].map{|number| number * 3}
=> [3, 6, 9]

前者は、数字を受け取ったら、それを「2倍(number * 2)する処理」、後者は「3倍(number * 3)する処理」をそれぞれブロックとして渡しました。
ブロックは「0個以上の引数を受け取り、それを使って処理をするコードの塊」といったものです。

ブロックはコードの塊ですが、それをモノとして扱いたい場合は、Procオブジェクトのインスタンスに変換することができます。

[3] pry(main)> double = Proc.new{|number| number * 2}
=> #<Proc:0x007fe5e495c488@(pry):3>
[4] pry(main)> [1, 2, 3].map &double
=> [2, 4, 6]
[5] pry(main)> triple = Proc.new{|number| number * 3}
=> #<Proc:0x007f87b3d2c4a8@(pry):3>
[6] pry(main)> [1, 2, 3].map &triple
=> [3, 6, 9]

インスタンスは変数に代入することができるので、名前を付けることができます。

上記の例では「2倍にする」という処理をdoubleという変数に、「3倍にする」という処理をtripleという変数に代入し、それぞれを配列にブロックの代わりに渡しました。

なお、&演算子はProcのインスタンスをブロックに変換する働きをします。

Procのインスタンスを返すメソッド

上記の通り、ブロックはそのままだと、ただのコードの塊なので名前を持ったり、何かに代入したりすることができません。
つまり、メソッドの結果(戻り値)としてブロックを返すことはできません。

一方で、Procのインスタンスはメソッドの戻り値として返すことが可能です。変数に代入できたり、名前を付けられたりするので、モノとして扱うことができます。

それを踏まえ、doubletripleを生成するクラスを作ってみます。

class Multiplier
  class << self
    def generate multiplier
      Proc.new{|multiplicand| multiplicand * multiplier}
    end
  end
end

[2] pry(main)> double = Multiplier.generate 2
=> #<Proc:0x007f8bc4b04210@(pry):4>
[3] pry(main)> triple = Multiplier.generate 3
=> #<Proc:0x007f8bc6869f08@(pry):4>
[4] pry(main)> [1, 2, 3].map &double
=> [2, 4, 6]
[5] pry(main)> [1, 2, 3].map &triple
=> [3, 6, 9]

「2倍にする」や「3倍にする」という「処理」を動的に生成することができました。

これらを組み合わせると

纏めます。
私がRubyを選ぶ3つの理由は

  1. すべてがオブジェクトだから
  2. 柔軟性が高いから
  3. ブロック:表現力豊かな機能を持っているから

でした。
それらが示す特性を総合すると、このような書き方ができます。

まず、ブロック。
ブロック(の姿を変えたProcのインスタンス)を返すクラスを作ります。

class Multiplier
  class << self
    def generate multiplier
      Proc.new{|multiplicand| multiplicand * multiplier}
    end
  end
end

以上は再掲です。
そして、それを数字がオブジェクトであり、柔軟にメソッドを定義できる性質を利用して、Integerにメソッドを追加します。

class Integer
  def multiplier  
    Multiplier.generate self
  end
end

ここまでのコードを組み合わせると、

[1, 2, 3].map &(2.multiplier)
=> [2, 4, 6]

[1, 2, 3].map &(3.multiplier)
=> [3, 6, 9]

このように書けるようになりました。
英語の語順で考えると、これで充分1, 2, 3という数それぞれ、2倍(3倍)をすると読めると思います。

ですが、日本人の私たちにもう少し可読性を高めたい、という欲求が沸きます(沸かせて下さい)。

その欲求に応えてくれるのが(欲求を言語の制限で殺さないのが)Rubyを選ぶ最大の理由です。

さらに踏み込むと

日本人のためにコードを書くならば、日本語を一部取り入れてみましょう。

Rubyはメソッド名にマルチバイト文字(漢字等)が使えます。
それを応用すると、このような書き方ができます。

これはもちろん

  1. すべてがオブジェクトだから
  2. 柔軟性が高いから

ですね。

class Array
  alias :それぞれ :map
end

class Integer
  def 
    Multiplier.generate self
  end
end

実行例はご想像の通りで、

[1, 2, 3].それぞれ &2.
=> [2, 4, 6]
[1, 2, 3].それぞれ &3.
=> [3, 6, 9]

このようになります。
&が惜しいですが、ほぼ日本語で処理が記述できました。普通やりませんが

最後に

最後の例は極端なものではありますが、、
自分達が所有するコードの可読性をできるだけ高めたい、との欲求に柔軟に応えてくれる言語だからRubyを選んでいます。
コードの可読性を高く保つことの重要性を信じている方が私たちのチームに興味を持って頂けたら何よりです。

We love, A PROGRAMMER'S BEST FRIEND