to_proc
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]
最高の点は、これはパフォーマンスの低下なしに構文を書き換えるだけということです。