Rubyのブロックつらい問題
Rubyで素直なコードを書くと、こういうことがよく起こります。
%w`foo bar baz`.map{|it| it.upcase }
#=> ["FOO", "BAR", "BAZ"]
それぞれの要素に対してupcase
を適用する、ただそれだけのためにit
を2回も記述しなければなりません。Rubyはブロックを多用する言語なのでこの様なコードを書く機会が多く、やがてあなたは辟易するはずです。
上記の例ではmap(&:upcase)
と書き直すことができますが、ブロックを使用しない方向に進んでしまうと、少しでも処理が複雑になると破綻してしまいます。詳しくはブロックなしRubyをやろうとすると関数型プログラ…うーんリストプロセッ、えーと感じ感じを参照してください。
Procを拡張しよう、ということでおもむろにlambda_driverをインストールするのも良いですが、今回はブロックそのもの(ブロックを取り扱うメソッドそのもの)を拡張する形でこれの解決を図りましょう。
参考になる例として、ClojureやScalaでは暗黙のパラメータ(プレースホルダ)を導入することでこの問題を上手く解決しています。
(1..10).map{|_| _ * 10}
#=> [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
1.to(10).map(_ * 10)
//=> scala.collection.immutable.IndexedSeq[Int] = Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
(map #(* % 10) (range 1 10))
;=> (10 20 30 40 50 60 70 80 90)
やる
そこで、RubyのArray#map
、Array#select
にも暗黙のブロックパラメータ(プレースホルダ)の仕組みを導入してみました。
module PlaceHolder
refine Array do
prepend (
Module.new do
lambda {|*args, &block|
if block && block.parameters.size == 0
super() do |(*args)|
Object.new.instance_eval do
define_singleton_method(:_) { args[0] }
args.size.times do |i|
define_singleton_method("_#{i + 1}") { args[i] }
end
instance_eval &block
end
end
else
super()
end
}.tap {|it|
%i[ map select ].each do |method|
define_method method, it
end
}
end
)
end
end
それでは実際に使ってみましょう。
using PlaceHolder
## map
p [1,2,3].map{_ * 10}
#=> [10, 20, 30]
p [[1,20],[2,30]].map{_1 + _2 + 100}
#=> [121, 132]
p [1,2,3].map
#=> #<Enumerator: [1, 2, 3]:map>
p [1,2,3].map{|m| m + 10}
#=> [11, 12, 13]
## select
p [1,2,3].select{ _ % 2 == 1 }
#=> [1, 3]
p [[1,20],[2,30]].select{ (_1 + _2) % 2 == 0 }
#=> [[2, 30]]
いいですね。_
は常に1つ目のパラメータ、_n
はn番目のパラメータを表します。もしブロックに明示的にパラメータを記述した場合やそもそもブロックが渡されなかった場合はプレースホルダを無効化しています。
まとめ
便利