SEAN_BLOG

プログラミング etc.

Ruby 2.7 の新機能(Method reference operator .: 編) - チケットとコミットログを読んでみて

さて、今回はRuby 2.7 の新機能、Method reference operator .: についてまとめていきます。 Ruby 2.7 の新機能に関しては、前回のNumbered parameters に続き今回で3本目のエントリーです🎉

なんだか、連載のような感じになってきてしまいました。

最初は1つのエントリーに全てまとめるつもりだったのに。。。

Method reference operator .:

TL;DR

  • Object#method の代わりに、Method reference operator .: を利用可能
  • Dir["*/*.c"].map(&File.:basename) といった書き方が可能
  • Numbered parameters は便利

使用例

# w/ Object#method
m = 100.method(:to_s)
#=> #<Method: Integer#to_s>
m.call
#=> "100"

m = 100.method(:to_hash)
#=> NameError (undefined method `to_hash' for class `Integer')

引数で指定したメソッドの名前をオブジェクト化した、Method オブジェクトを返します。 もし、その名前のメソッドが存在しない場合は、NameError を返します。 メソッドをオブジェクト化したことにより、クロージャーとして利用することが可能になります。

これを、.: を用いて下記のように書くことができるようになりました。

# w/ .:
m = 100.:to_s
#=> #<Method: Integer#to_s>
m.call
#=> "100"

m = 100.:to_hash
#=> NameError (undefined method `to_hash' for class `Integer')

挙動は Object#method と同じです。

チケット

今回もチケットから導入の経緯、導入までの一連の流れを見ていきましょう。

bugs.ruby-lang.org

bugs.ruby-lang.org

今回の出発点は、下記のような処理をしたい時に、

  • ブロックパラメターを利用する方法
w/o Object#method
Dir["*/*.c"].map { |f| File.basename(f) }
  • Object#method を用いる方法
w/ Object#method
Dir["*/*.c"].map(&File.method(:basename))

どちらもあまりいけてないよね?というところのようです。

そこで、lambda で使われている-> を用いて、下記のような方法を用いるのはどうかと提案されています。

Dir["*/*.c"].map(&File->basename)

これを受けて、Matzさんは「アイディアは好きだけど、-> はちょっと。」と言った反応を示されておりました。

I like the idea of short hand notation for Object#method(), but I don't think -> is a good idea.

Matz.

チケットの中では.><..>&>->>=>>+>$>:>\.Object[.method]: や、 Elixir の|> を用いたらどうかという代替案が出されていました。

また、メッセージセンドの:: を地上げし、再利用するという提案もありました。 ちなみに、Java のMethod reference には :: が用いられているようです。

そんな中、Matzさん、

「候補の中で.: がベスト、ついで :::。...」とのこと。

..., .: looks best to me (followed by :::)....

Matz.

結果、.: の形に収まったようです。

その他、RubyPerl に近づいていくことを危惧する意見や、これ以上記号をRuby に導入しないで欲しいといった意見などの Method reference operator の導入しネガティブな意見も散見されました。

コミットログ

github.com

今回もテストを中心に、読み解いていこうと思います。

基本的には、Object#method と同様の挙動になるようテストが書かれています。

m = 1.:succ
assert_equal(1.method(:succ), m)
assert_equal(2, m.())

m = 1.:+
assert_equal(1.method(:+), m)
assert_equal(42, m.(41))

m = 1.:-@
assert_equal(1.method(:-@), m)
assert_equal(-1, m.())

o = Object.new
def o.foo; 42; end
m = o.method(:foo)
assert_equal(m, o.:foo)

def o.method(m); nil; end
assert_equal(m, o.:foo)
assert_nil(o.method(:foo))

通常のメソッド同様、改行にも対応してます。

assert_valid_syntax("a\n.:foo")

所感

Ruby 2.7 のMethod reference operator はなんとなく、Pattern matching とNumbered parameters の陰に身を潜めてしまっている感があります。 僕自身もPattern matching、Numbered parameters を先にまとめているので、何にも言えませんが。。。

w/o .:
Dir["*/*.c"].map { |f| File.basename(f) }

w/ Object#method
Dir["*/*.c"].map(&File.method(:basename))

w/ .:
Dir["*/*.c"].map(&File.:basename)

.: の導入により、上記のように書くことが可能になりました。 こう比べてみるとスッキリしましたね。

そういえば、Numbered parameters を用いてもかけますね。

w/ Numbered parameters
Dir["*/*.c"].map { File.basename(@1) }

少しだけ、Method reference operator .: の方が記述量少ないのか🤔 にしても、Numbered parameters も便利ですね。

これまでそもそも Object#method 知らなかったので、 これを機にガシガシ Object#method、Method reference operator .: 使っていきたいと思います。

では、今回はこの辺で。

最後まで読んで頂き、ありがとうございます🙇

参考