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

Ruby で block 内からレシーバのオブジェクトを参照したかった

意図としては以下のように block 内で呼び出したレシーバを参照したい。

[1, 2, 3].map { |it|
    it + self.size # block 内でレシーバのオブジェクトを参照したい
}
# => [4, 5, 6]

しかし、block 内のスコープでメソッドやオブジェクトを参照した場合、定義したスコープから参照されるのでこれでは意図した動作は行われない。
こういうことをしたい場合は一度変数に代入してから参照する必要がある。

list = [1, 2, 3]
list.map { |it|
    it + list.size
}
# => [4, 5, 6]

しかし、こういうことをしたい場合にいちいち変数を定義するのはちょっともにょる。

#instance_eval を使う

代替え案その1。
#instance_eval をかませることで、そのブロック内から自身を参照する事ができるようになります。

[1, 2, 3].instance_eval {
    map { |it|
        it + self.size
    }
}
# => [4, 5, 6]

ただし、その分スコープが深くなってしまうのがちょっと残念。

#tap を使う

某げっ歯類氏に相談してみたところ『#instance_eval よりも #tap の方が安全じゃない?』とのことで以下のような書き方を教えてもらった。

[1, 2, 3].tap { |myself|
    break myself.map { |it|
        it + myself.size
    }
}
# => [4, 5, 6]

#tap の引数でレシーバのオブジェクトを受け取り、そこから自身を参照している。
ただし、#tap の戻り値はレシーバのオブジェクトなので、任意の戻り値を返したい場合上記のように break 経由で返す必要がある。