【Ruby】何でもかんでも後置ifを使えばいいっていう訳ではない、自分は果たしてちゃんと理解して使っているのだろうか・・・

Rubyでは後置ifというif文をワンライナーで書くことができるものが存在します。

hoge = “hogehoge” If false

みたいなやつですね。

後置ifはすごい便利で、短いときには全部後置ifにしてやるざヒャッハーとなるけど、ちょっと待ってほしい。

クイズタイム

一旦、以下のコードで何が出力される考えてみよう。

class Person
  attr_accessor :name

  def say_name
    name = "三郎" if name.nil?
    p name
  end
end

taro = Person.new
taro.name = "太郎"
taro.say_name

これは何が出力されるだろうか。 実行してみると,「太郎」ではなく「三郎」が出力されます。

では、これはどうだろう。

class Person
  attr_accessor :name

  def say_name
    if name.nil?
      name = "三郎"
    end

    p name
  end
end

taro = Person.new
taro.name = "太郎"
taro.say_name


後置ifをブロックにしただけですが、出力は変わります。今度は「三郎」ではなく「nil」です。

こんな風に場合によっては、書き方によって挙動が変わってくる。

違いの要因は処理の順番

taro.name = "太郎"としているのに、なぜ三郎が出るんだ、nilって何だと思ったかもしれません。

順番に見ていきましょう。

class Person
  attr_accessor :name

  def say_name
    name = "三郎" if name.nil?
    p name
  end
end

taro = Person.new
taro.name = "太郎"
taro.say_name

まず最初のこのコードですが、say_nameが呼ばれるとname = "三郎" if name.nil?が評価されます。このnameが何を指しているかがポイントですが、ローカル変数を最初に探して、その後にメソッドなりを探しにいきます。

if name.nil?trueと判断されるけど、このnameはself.nameを指しているわけじゃなくて、ローカル変数のname = “三郎”を指している。でも評価した段階では、その変数はnil。プログラムの読み込み順序と自分たちの読み込み順序が異なっているのがわかる。

1. nameは何を指している?→nameというローカル変数があるのでそれ
2. 指しているのはname=“三郎”
3. だけど、`if name.nil?`で評価した段階ではそのnameはnil

結果として、条件はtrueとなり、三郎が出力される。

class Person
  attr_accessor :name

  def say_name
    if name.nil?
      name = "三郎"
    end

    p name
  end
end

taro = Person.new
taro.name = "太郎"
taro.say_name

これは実行するとnilが出力される。if name.nil?で指しているnameはself.nameなので、if文の中は実行されずにnilが返る。ifの中身は関係ない。

まあこんな現象が起きるのは、変数名とattr_accesserで指定している名称が同じ場合ぐらいでそうそう起きないと思う。

最初のコードも以下のように変数名を変えて書けば、直感的にわかるしね。


class Person
  attr_accessor :name

  def say_name
    aaa = "三郎" if name.nil?
    p aaa
  end
end

taro = Person.new
taro.name = "太郎"
taro.say_name


まあそもそも、attr_accessorインスタンス変数をちゃんと使ったりすれば、そんなことは起きないだろう。

class Person
  attr_accessor :name
end

taro = Person.new
taro.name = "太郎"
p taro.name

#=>"太郎"
class Person
  attr_accessor :name

  def say_name
    @name = "三郎" if @name.nil?
    p @name
    #=>"三郎"
  end
end

taro = Person.new
taro.say_name