コンテンツへスキップ
GitHubリポジトリ フォーラム RSS-ニュースフィード

to_proc

Ary Borenzweig

Rubyのto_proc

Rubyのブロックは強力です。数値の配列を文字列に変換するのは簡単です。

[1, 2, 3].map { |n| n.to_s } #=> ["1", "2", "3"]

もちろん、このショートカットでも実行できます。

[1, 2, 3].map &:to_s #=> ["1", "2", "3"]

&演算子は、オブジェクトをブロックとして渡すのに適したProcに変換します。to_procメソッドを実装することで、任意のクラスがこの演算子に対応できます。Symbolにはto_procメソッドがあります。

これは非常に良いのですが、メソッドに引数を渡したい場合はどうでしょうか。例えば

[10, 20, 30].map { |n| n.modulo(3) } #=> [1, 2, 0]

&:modulo(3)のような記述で動作させることはできますか? 少なくとも簡単にではありません容易ではありません

それだけでなく、RubyはSymbolをProcに変換する必要があるため、通常のブロックを使用する場合よりもわずかなパフォーマンスの低下があります。

最後に、RubyのSymbol#to_procの実装にはProcのキャッシュがあるので、同じシンボルを使用するたびに作成されるわけではありませんが、それでも通常のブロックよりもわずかに遅いです。

Crystalのto_proc?

最初は、Crystalにこれと同じ構文を持たせることを考えましたが、少し技巧的です。&:to_sを実行する場合、&の引数はSymbolであるため、ソースコードを書き換えてブロックを受け取ることができます。

# This:
[1, 2, 3].map &:to_s

# is rewritten to this:
[1, 2, 3].map { |x| x.to_s }

他の引数については、別の方法(例えば、関数型をブロックに変換するなど)を使用します。

幸いなことに、wajがより良い提案をしました。&.to_sのように記述したらどうでしょうか?

[1, 2, 3].map &.to_s

これはRubyとは異なる新しい構文です。Rubyでこれを実行すると…

irb(main):001:0> [1, 2, 3].map &.to_s
SyntaxError: (irb):1: syntax error, unexpected '.'
[1, 2, 3].map &.to_s
               ^

これは、&の後にドットを置くことはRubyでは意味がないことを意味し、この構文を新しい意味を与えるために使用できることも意味します。そのため、Crystalではこの構文を使用することにしました。

この小さな変更で、メソッドに引数を非常に簡単に渡すことができます。

[10, 20, 30].map &.modulo(3) #=> [1, 2, 0] ... but only in Crystal ;-)

それだけでなく、次のように書くこともできます。

[1, 20, 300].map &.to_s.size #=> 1, 2, 3

あるいは、これです。

[[1, -2], [-3, -4]].map(&.map(&.abs)) #=> [[1, 2], [3, 4]]

そしてもちろん、これです。

[1, 2, 3, 4].map &.**(2) #=> [1, 4, 9, 16]

最高の点は、これはパフォーマンスの低下なしに構文を書き換えるだけということです。