読者です 読者をやめる 読者になる 読者になる

【Ruby Advent Calendar 2015 1日目】C++er が Ruby を書いてみて驚いた10のこと

Ruby Advent Calendar 2015 とは

Ruby について何か書くアドベントカレンダーになります。
現在は Qiita で開催しています。

まだ参加者は募集中なので気になる方はぜひ参加してみてください。

本題

さて、わたしは元々 C++ 界隈の人間なのですが、最近は Ruby ばかり書いています。
そこで、今回は C++er が Ruby を書いてみて驚いたことをちょっと紹介してみようかと思います。
ちなみにわたしは Rails は触ったことはありません。

if や while, class, def などの制御構造はすべて式

Ruby でや if や while, class などの制御構造はすべて式になります。
ですので、代入式の右辺などに記述する事ができます。

flag = true
result = if flag
    "OK"
else
    "NG"
end
p result
# => "OK"

式であるのでメソッドの引数内でも class なんかを定義することができます。

ブロックがある

Ruby の特徴的な構文にブロックというものがあります。
これはメソッドの引数として渡すことができ、そのメソッドに対する操作を行うような処理を記述する事ができます。

ary = [1, 2, 3, 4, 5]

# 各要素を2倍にする
ary.map { |it|
    it + it
}
# => [2, 4, 6, 8, 10]

# 偶数を選ぶ
p ary.select { |it|
    it % 2 == 0
}
# => [2, 4]

このように配列を操作するような場合などに利用することができます。
C++ でも最近はラムダ式が導入されて似たような記述をすることができますが、Ruby では言語レベルでこういう機能が導入されています。

return を省略できる

Ruby では return を省略して書くことができます。
return が省略された場合はその構文内の最後の式が戻り値として返されます。

def plus a, b
    # return を省略して書くことができる
    a + b
    # return a + b
end


def check flag
    # if を戻り値として返す
    if flag
        "OK"
    else
        "NG"
    end
end

メソッド呼び出し時に () を書かなくてもよい

Ruby ではメソッドの呼び出し時に () を省略することができます。

def plus a, b
    a + b
end

# 他の言語であれば () をつけてメソッドを呼び出すが
puts(plus(1, 2))

# Ruby では () を省略して記述する事ができる
puts plus 1, 2

これにより Rubyメソッドをより DSL っぽく記述する事ができます。

オープンクラスを拡張できる

Ruby では既存のクラスのメソッドなどあとから再定義する事ができます。
これにより組み込みクラスの挙動などを変更することもできます。

# 組み込みクラスである String に #twice メソッドを追加する
class String
    def twice
        self + sel
    end
end
p "homu".twice
# => "homuhomu"

演算子メソッド呼び出しのシンタックスシュガー

Ruby では演算子メソッド呼び出しのシンタックスシュガーとなります。

# 1 + 2 は次のようなメソッド呼び出しのシンタックスシュガー
1.+(2) # => 3

# [] みたいなのも同じ
[1, 2, 3].[](1) # => 2

また、演算子メソッドであるため、任意の演算子を再定義したい場合はメソッドとして定義することができます。

class X
    def initialize
        @name = "homu"
    end

    def + other
        @name + other
    end
end

p X.new + "mami"
# => "homumami"

ただし ||&& などの一部の演算子は言語の組み込み演算子になるため、メソッドとして扱うことはできません。

nil と false 以外は true

Ruby では nilfalse 以外はすべて true になります。
なので C++ とは違い数値の 0true となります。

if 0
    p "OK"
else
    p "NG"
end
# => "OK"

ちなみに "homu" =~ /^h/ という比較式は 0 が返ってくるので false と勘違いするが true である(これは戻り値が『マッチした位置』を返すため 0 が返ってくる

クラスは型ではなくてオブジェクト

C++ ではクラスは型として扱われますが、Ruby ではクラスはオブジェクトとして扱われます。
class が以下のように Class クラスのインスタンスオブジェクトとして定義されるとイメージしやすいと思います。

# クラスを Class クラスのインスタンスオブジェクトとして定義する
X = Class.new {
    attr_accessor :name

    def + other
        name + other
    end
}

x = X.new
x.name = "homu"
p x + "mami"
# => "homumami"

このように Ruby ではクラスは型ではなくて Class クラスのオブジェクトとして使われます。

private メソッドが自クラス以外からも呼びだせる

C++private は基本的には自クラス内でしか呼び出すことはできないんですが、Rubyprivate メソッドは割と簡単に外部から呼び出すことができます。

class X
    private
    def name
        "homu"
    end
end

x = X.new

# レシーバ付きでメソッドを呼び出すことができない
# x.name
# Error: private method `name' called for #<X:0x00000001d76c10>

# 逆に言えばレシーバを付けなければ呼び出すことができる
p x.send(:name)
# => "homu"
p x.instance_eval { name }
# => "homu"

Rubyprivate メソッドはレシーバを付けて呼び出せなくなるだけなので、それ以外の手段(#send#instance_eval など)を利用すれば private メソッドも呼び出すことができます(ちなみに self.name という呼び出し方はレシーバを付けてるためエラーになります。

ほとんどがメソッドで構成されてる

Ruby では関数は存在せずにほとんどがメソッドとして扱われます。
例えば、次のようにトップレベルにメソッドを定義した場合もグローバル関数ではなくて main オブジェクトの private メソッドとして扱われます。

# main オブジェクトの private メソッドとして定義される
def plus a, b
    a + b
end

# #send をかまして private メソッドを呼び出す
p self.send :plus, 1, 2
# => 3

また、次のようなコードを見てみると

require "json"

class X
    include Math

    attr_accessor :name
    
    private
    def add other
        name << other
    end
end

requireinclude, attr_accessor, private などは言語キーワードのように見えますが、Ruby ではこれらはすべてメソッドとして定義されています。
このように Ruby ではたいていの構文はメソッドを扱うような感じで記述していきます。

まとめ

と、言う感じでざっくりとまとめてみましたー。
やっぱり静的型付け言語の C++ と比べると Ruby は恐ろしく緩い、というか C++ とは違った意味でやりたい放題という感じがひしひしと伝わってきます。
今だと Rubyメタプログラミングスクリプト感を最大限に活かしてるような感じがして闇っぽくて結構好きです。
また、Ruby には RubyGems があり、外部ライブラリを管理するのも簡単ですし充実してるので、やりたい事がサクッと実現する事ができますしねー。
そんな感じでRuby Advent Calendar 2015、1日目の記事でしたー。

おまけ

以前、Ruby で便利ライブラリをつくったので気になる方は見てみてくださいー。