65
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyのブロックつらい問題を解決する暗黙のブロックパラメータ

Last updated at Posted at 2015-02-11

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では暗黙のパラメータ(プレースホルダ)を導入することでこの問題を上手く解決しています。

map.rb
(1..10).map{|_| _ * 10}
#=> [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
map.scala
1.to(10).map(_ * 10)
//=> scala.collection.immutable.IndexedSeq[Int] = Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
map.clj
(map #(* % 10) (range 1 10))
;=> (10 20 30 40 50 60 70 80 90)

やる

そこで、RubyのArray#mapArray#selectにも暗黙のブロックパラメータ(プレースホルダ)の仕組みを導入してみました。

placeholder.rb
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

それでは実際に使ってみましょう。

sample.rb
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番目のパラメータを表します。もしブロックに明示的にパラメータを記述した場合やそもそもブロックが渡されなかった場合はプレースホルダを無効化しています。

まとめ

便利

65
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?