こんにちは。フォースタートアップス株式会社、エンジニアの石田です。 前回の記事では「主にバックエンドのエンジニアをしています。」と自己紹介しましたが、今はバックエンドとフロントエンド両方を担当しています。
特に直近数ヶ月はフロントエンド(React/Next + TypeScript)ばかりやっています。ですが、私が一番好きな言語はRubyですので、今回も前回と同じくRubyの話をしたいと思います。
今回の内容はRubyの基礎知識+豆知識的な内容になります。後半になるにつれて認知度が低く、マニアック度が高くなる気がします。お付き合いください。
ゲッター/セッターメソッド
Rubyで頻繁に使うattr_reader
, attr_writer
, attr_accessor
の正体について説明します。
class Hoge attr_reader :foo # ゲッターを定義 attr_writer :bar # セッターを定義 attr_accessor :baz # ゲッター/セッターを定義 def initialize(foo, bar, baz) @foo, @bar, @baz = foo, bar, baz end end
これはよく見るRubyのコードですね。ゲッター・セッターを定義しています。Rubyにおいてこの「ゲッター・セッター」とは何でしょうか?実はこれはただのメソッドです。
上のコードは下のコードと同じ意味です(注意: 文字通り「意味的に」同じという意味です。Rubyインタプリタがどのようにこれを実行するかは別です。後述)。
class Hoge def initialize(foo, bar, baz) @foo, @bar, @baz = foo, bar, baz end # ゲッターを定義 def foo @foo end # セッターを定義 def bar=(val) @bar = val end # ゲッター/セッターを定義 def baz @baz end def baz=(val) @baz = val end end hoge = Hoge.new(1, 2, 3) hoge.bar=(100) # 普通のメソッド実行 hoge.bar= 100 # カッコが省略できる hoge.bar = 100 # 特別ルールでメソッド名の「=」の前にスペースが入ってもOK
Rubyのインスタンス変数は全て必ずprivateスコープです。外部からアクセスするためにはpublicスコープのメソッドをつかう必要があります。
attr_〇〇
を使うとこれらのメソッドが定義されます。特に注目なのがセッターメソッドです。実際にはメソッドの実行なので、bar=
という名前のメソッドを実行しているだけですが、hoge.bar = 100
というふうにあたかもhoge.bar
に値を代入しているように書くことができます。この辺がRubyマジックですね。
注意
attr_〇〇
を使うのとゲッター/セッターメソッドを自力で書くのは意味的には同じですが、実行速度は違います。(少なくともCRubyでは)attr_〇〇
はCレベルの専用処理が動くのでattr_〇〇
を使ったほうが実行速度は速くなります。特に理由がなければattr_〇〇
の方を使いましょう。
privateメソッドは「外部からアクセスできないメソッド」という意味ではない(?)
以下のコードがあったとします。これを実行するとNoMethodError
が発生します。privateなので当たり前ですね。
class Hoge private def say_foo puts 'foo' end end hoge = Hoge.new hoge.say_foo # private method `say_foo' called for #<Hoge:0x00007f86bb887f40> (NoMethodError)
では以下のコードの場合はどうなるでしょうか?
class Hoge def public_say_foo self.say_foo end private def say_foo puts 'foo' end end hoge = Hoge.new hoge.public_say_foo # どうなるでしょう?
この実行結果は実はRubyのバージョンによって変わります。
# Ruby 2.7より前 hoge.public_say_foo # private method `say_foo' called for #<Hoge:0x00007f86bb887f40> (NoMethodError) # Ruby 2.7以降 hoge.public_say_foo #=> foo
なぜRuby 2.7より前のバージョンではNoMethodError
が発生したのでしょうか?それは以下のような理屈です。
- もともとprivateメソッドは「レシーバを明示的に指定して実行できないメソッド」という意味だった
- レシーバの
self
は省略できる - privateメソッドはレシーバを省略しなければならない = レシーバは必ず
self
上記コードはレシーバself
を明示的に指定しているので、Ruby 2.7より前ではNoMethodError
が発生しました。
Ruby 2.7ではこれにルールが追加されました。
なのでRuby 2.7以降ではエラーにならなかったんですね。
レシーバself
を明示的に指定しない場合はバージョンに関わらずエラーにはなりません。
class Hoge def public_say_foo say_foo end private def say_foo puts 'foo' end end hoge = Hoge.new hoge.public_say_foo # どのバージョンでも foo が返る
子クラスからのprivateの実行
さて、次は以下のコードです。
class SuperHoge private def say_foo puts 'foo' end end class Hoge < SuperHoge def public_say_foo say_foo end end hoge = Hoge.new hoge.public_say_foo # どうなるでしょう?
これは"foo"
が返ります。
hoge.public_say_foo #=> foo
先述の通りprivateメソッドはレシーバを省略すれば実行できます。そのクラスに対象メソッドが定義されていなかった場合、Rubyは自動的に先祖クラスを順番に探しに行きます。この場合SuperHoge
クラスにsay_foo
が存在しているので無事実行できました。
「あれ?」と思った方へ
ここまで読んで、一部の方(とくにJavaでオブジェクト指向を学んだ方)はこう思うかもしれません。「子孫クラスで先祖クラスのprivateメソッドを実行できるならprotectedスコープって何?」と。これについては次の章で話しますが、Rubyのprotected
とJavaのprotected
は全く別の概念です。
とりあえず今は「子孫クラスで先祖クラスのprivateメソッドを実行できる」とおぼえて、例えばテンプレートメソッドパターン(リンクは弊社ブログ)でprotected
キーワードが使われているのを見つけたら、そっとprivate
に置き換えてあげてください。
protectedメソッドは「自身と子孫のクラスからしかアクセスできないメソッド」という意味ではない
先述の通り、Rubyのprotected
とJavaのprotected
は全く別の概念です。
Rubyのprotectedメソッドは「自身と子孫のクラスからしかアクセスできないメソッド」という意味ではありません。
Ruryのprotected
は公式ドキュメントでは以下のように説明されています。
protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せ ます。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#limit
これだけ読んでもよく意味がわかりませんね。簡単な例を用意しました。
protectedを使ったサンプルコード
サンプルの要件は以下です。
FullName
という値オブジェクトクラスが存在しているFullName
クラスのインスタンスは2つの値を持っている- first_name
- family_name
FullName
クラスのインスタンスの比較は「持っている値がすべて同じ場合」はtrueにしたいFullName
クラスが持っている値は外部から隠蔽したい(この要件が存在する理由は不明ですが、とにかくそういう要件があると思ってください)。
この要件を満たすFullName
クラスを作っていきます。
まず単純に要件1と要件2を満たすクラスを作ってみます。
class FullName def initialize(first_name, family_name) @first_name = first_name @family_name = family_name end end foo = FullName.new('foo', 'bar') bar = FullName.new('foo', 'bar') puts foo == bar #=> false <- これがtrueになるようにしたい
このままでは要件3を満たしません。要件3を満たすために==
メソッドをoverrideしましょう。
class FullName def initialize(first_name, family_name) @first_name = first_name @family_name = family_name end def ==(other) self.first_name == other.first_name && \ self.family_name == other.family_name end def first_name @first_name end def family_name @family_name end end foo = FullName.new('foo', 'bar') bar = FullName.new('foo', 'bar') p foo == bar #=> true <- 要件3を満たす foo.first_name #=> "foo" <- 要件4違反
これで要件3を満たすようになりました。しかし要件4に違反しています。どうすればいいでしょうか?#first_name
, #family_name
をprivateにすることはできません。other.first_name
, other.family_name
がNoMethodError
になってしまうからです。
ここでprotected
の出番です。
class FullName def initialize(first_name, family_name) @first_name = first_name @family_name = family_name end def ==(other) self.first_name == other.first_name && \ self.family_name == other.family_name end protected def first_name @first_name end def family_name @family_name end end foo = FullName.new('foo', 'bar') bar = FullName.new('foo', 'bar') p foo == bar #=> true <- 要件3を満たす foo.first_name #=> NoMethodError <- 要件4を満たす
これですべての要件を満たすFullName
クラスができました!
問題は「そもそもこんな要件が実際に存在するのか(とくに要件4)?」ということです。想像の通りprotected
を使わなければいけない場面は相当少ないと思われます。私は実務でも私的なプロジェクトでもprotectedを使ったことが一度もありません。
Rubyの親であるMatz氏もprotected
を実装したことを後悔している様子です。
注意
このFullName
は値オブジェクトのクラスとしては完全ではありません。HashのキーとしてFullName
のインスタンスが使えないからです。Hashのキーの同一性は==
メソッドではなくeql?
メソッドを使って比較します。詳しくは述べませんが、Hashのキーとしても使える値オブジェクトを実装したい場合、上記のコードは参考にしないでください。
Bool/Boleanクラスは存在しない
どの言語でもありそうなBool/Bolean型・Bool/BoleanクラスというものはRubyには存在しません。確かめてみましょう。
p true.class #=> TrueClass p false.class #=> FalseClass
TrueClass
とFalseClass
クラスは全く別のクラスです。共通の親クラスも(すべてのクラスの共通の先祖クラスであるObject
/BaseObject
クラスを除いて)ありません。
ちなみに、TrueClass
/FalseClass
はシングルトンパターンで実装されているのでインスタンスはtrue
/false
ただひとつです。
Procとlambdaの違い
RubyにはProc
とlambda
というかなり似ているけどちょっと違う2つの実装があります。
下のコードを見てみましょう。
proc = Proc.new { |x| x + 1 } lamda = lambda { |x| x + 1 } p proc.call(1) #=> 2 p lamda.call(1) #=> 2 p proc.class #=> Proc p lamda.class #=> Proc
はい、全く同じに見えますね。しかし、細かく見ると違いがあります。
ちなみにProc.new
とlambda
にはそれぞれエイリアス(のようなもの)があります。
書き換えると以下になります。
proc = proc { |x| x + 1 } lamda = -> (x) { x + 1 }
Procとlambdaはどう違うか
returnの挙動が違う
def say_foo1 proc = Proc.new { return 10 } proc.call puts 'foo1' end def say_foo2 lambda = lambda { return 10 } lambda.call puts 'foo2' end puts say_foo1 puts '-----' puts say_foo2
このコードを実行すると以下の出力になります。
10 ----- foo2 nil
どういうことかというと、Procの場合はreturnした時点で#say_foo1
から抜けてしまうんですね。それに対してlambdaはreturnしてもブロックから抜けるだけです(このブロックというものの概念の説明も難しいのですが、余計ややこしくなるので今回は説明しません)。
引数の扱いが違う
以下のコードを見てください。
proc = Proc.new { |a, b| [a, b] } p proc.call(1) #=> [1, nil] p proc.call(1, 2, 3) #=> [1, 2] # 足りないところをnilで埋められる # 余ったところは切り捨てられる lambda = lambda { |a, b| [a, b] } p lambda.call(1) # ArgumentError p lambda.call(1, 2, 3) # ArgumentError
上記のようにProcとlambdaでは引数の扱いが違います。
Procとlambdaの識別方法
Procとlambdaは#lambda?
をつかって識別できます。
prop = Proc.new { |x| x + 1 } lamda = lambda { |x| x + 1 } p prop.lambda? #=> false p lamda.lambda? #=> true
(Procとlambdaはすくなくともruby 1.8のころからあったのに)#lambda? が実装されたのはruby 2.1です。それまではProcなのかlambdaなのか区別するのは困難でした。
Procとlambdaの違いについてまとめ
正直、Procとlambdaの違いは「Rubyの闇」「Rubyの技術的負債」のように感じます。
とりあえずProcは使わずにlambdaのみを使うようにすればトラブルは少なそうです。
ちなみにブロックというものはProcともlambdaとも挙動が違うのですが、それについて話すと長くなりすぎますし、ますます混乱するので割愛します。1つだけ言うとブロックはProcやlambdaと違い、なにかのインスタンスではないので変数に入れたり、引数で渡すことはできないものです。
なぜdef self.hoge
と書くとクラスメソッドになるのか
Rubyではクラスメソッドを定義するときに以下のように書くのが普通です。
class Foo def self.what_is_your_name? puts "my name is Foo" end end Foo.what_is_your_name? #=> my name is Foo
でもなんでdef self.what_is_your_name?
と書くとwhat_is_your_name?
はクラスメソッドになるのでしょうか?それを理解するためには段階を踏む必要があります。
Step1. インスタンス固有のメソッドを定義できる
クラスメソッドからは一旦離れて、以下のコードを見てください。
foo = "foo" def foo.add_ban self + "!!!" end p foo.add_ban #=> "foo!!!" bar = "bar" p bar.add_ban # NoMethodError
実はRubyではインスタンスに固有のメソッドを定義することができます。これを一般的に「fooインスタンスの特異クラスのインスタンスメソッド」と呼びます。単に「fooの特異クラスメソッド」や「fooの特異メソッド」と呼ばれることもあります。(公式の呼び方はなさそう?)
Rubyの「.
」には少なくとも3つの意味があります。
上記のdef foo.add_ban
は2の意味ですね。1の意味では無いので注意です。
Step2. 全てのクラスはインスタンスである
Rubyでは「全てのクラスはインスタンス」です。言っている意味がわからないかもしれませんが、とりあえず以下のコードを見てください。
class Hoge end hoge = Hoge.new p hoge.class #=> Hoge p Hoge.class #=> Class p String.class #=> Class
hoge
のクラスはHoge
です。これは当たり前ですね。実はHoge
のクラスはClass
という名前のクラスです(言い換えるとHoge
はClass
クラスのインスタンス)。Hoge
に限らすString
など、全てのクラスはClass
クラスのインスタンスです。
なのでHoge
クラスは以下のように定義することもできます。
Hoge = Class.new # こうやってクラス定義できる hoge = Hoge.new p hoge.class #=> Hoge
これは余談になりますが、Rubyでクラス名の頭が大文字なのはクラスが定数だからです。Rubyでは変数の頭の文字によってそれがどんな変数なのかを識別します。
- 先頭が
$
: グローバル変数 - 先頭が
@
: インスタンス変数 - 先頭が
@@
: クラス変数(こいつのことは忘れてください) - 先頭が大文字(全部大文字である必要はない): 定数
- 先頭が小文字: ローカル変数
Step3. クラスの特異メソッドを定義できる
「クラスはインスタンス」ということは、Step1のように「クラスに特異メソッドが定義できる」ということです。これがクラスメソッドの正体です。
class Foo p self #=> Foo def self.what_is_your_name? # `def Foo.what_is_your_name?` と同じ意味 puts "my name is #{self}" end end Foo.what_is_your_name? #=> “my name is Foo”
つまり、Rubyには「クラスメソッド」というものは実は実在せず、全てはインスタンスメソッドです。「クラスに所属するインスタンスメソッド」か「特異クラスに所属するインスタンスメソッド」かの違いしかありません。
変数名やメソッド名はASCII文字じゃなくてもいいんだよ
注意: 下記はソースコードファイルをUTF-8で保存して動作確認しています。それ以外の文字コードを使った場合は動作確認していません。
変数名やメソッド名に使える文字にはルールがありますが、「ASCIIコード文字に限る」のようなルールは存在しません。日本語だろうがUnicode記号だろうが使うことができます。
日本語 = "Japanese" p 日本語 #=> "Japanese"
Q これなんの役にたつの?
A 特になんの役にもたちません。ただしユビキタス言語を定義するときにどうしても英語にできないものがあったときはつかえるかも?(例は思いつきませんが。)
「なんの役にもたちません」と言いましたが、やろうと思えば以下のような事もできます。
def √(x) Math.sqrt(x) end p √ 2 #=> 1.4142135623730951 def i(num) Complex(0, num) end e = Math::E π = Math::PI # オイラーの公式 p e ** (i π) + 1 == 0 #=> true
世の中にはこういうコードが「美しい」と思う人が一定数いるようで、こういうのがたくさん定義されたgem(例:unicode_math)が存在します。
おわりに
「基礎知識」と言いながらRubyの深淵を撫でるような内容になってしまいました。ボリュームも多いですね。しかし、これでも省略した内容が複数あります。
- ブロックとはなにか
def
には戻り値がある- なぜ
private
と書くとその下のメソッドがprivateになるのか - 評価戦略
これらはまた別に機会があれば話せたらよいな、と思います。
お付き合いいただきありがとうございました。