読者です 読者をやめる 読者になる 読者になる

Ruby で暗黙のブロックパラメータを使うための gem をつくった

Ruby で暗黙のブロックパラメータを使うための gem をつくりました。

インストール

$ gem install use_arguments

定義される暗黙のブロックパラメータ

パラメータ  意味
_1, _2..._N 各引数の要素
_ _1 と同じ
_args 全引数の配列
_yield ブロック引数
_self 呼び出された Proc オブジェクト自身
_receiver ブロックを渡したメソッドのレシーバ

使い方

使い方は簡単で、ブロックの引数を定義する代わりに _1_2 といったプレースホルダを変わりに使用します。

require "use_arguments"

# Array クラスのメソッドに対して使用する
using UseArguments::Array

p [1, 2, 3].map { _1 + _1 }
# => [2, 4, 6]

# Hash クラスのメソッドに対して使用する
using UseArguments::Hash

data = {homu: 13, mami: 14, mado: 13}
p data.select { _2 < 14 }
# => {:homu=>13, :mado=>13}

Proc#use_args

対象の Proc オブジェクトのブロック内で暗黙のパラメータを使えるようにする。

require "use_arguments"

using UseArguments

p proc { _args }.use_args.call 1, 2, 3
# => [1, 2, 3]

plus = proc { _1 + _2 }.use_args
p plus.call 1, 2
# => 3

fact = proc { _1 == 1 ? 1 : _1 * _self.(_1 - 1); }.use_args
p fact.call 5
# => 120

f = proc { _yield 1, 2 }.use_args
p f.call { |a, b| a - b }
# => -1

p f.call &plus
# => 3

# 引数が配列の場合は配列を展開して受け取る
p proc { _1 + _1 }.use_args.call [1, 2]
# => 2

# lambda の場合は引数を展開しない
p lambda { _1 + _1 }.use_args.call [1, 2]
# => [1, 2, 1, 2]

Object#use_args

#use_args を呼び出すことでメソッドのブロック内で暗黙のパラメータを使えるようにする。

require "use_arguments"

using UseArguments

p [1, 2, 3].use_args.map { _1 * _1 }
# => [1, 4, 9]

p [1, 2, 3].use_args.select { _1 % 2 == 0 }
# => [2]

using UseArguments::{任意のクラス名}

指定したクラスのメソッドに渡すブロック内で暗黙のパラメータを使えるようにする。

require "use_arguments"

using UseArguments::Array
# もしくは
# using UseArguments.usable Array

p [1, 2, 3].map { _1 + _1 }
# => [2, 4, 6]

p [[1, 2], [3, 4]].map { _1 + _2 }
# => [3, 7]


using UseArguments::Hash

data = {homu: 13, mami: 14, mado: 13}
p data.select { _2 < 14 }
# => {:homu=>13, :mado=>13}

p data.map { "#{_1}:#{_2}" }
# => ["homu:13", "mami:14", "mado:13"]

[注意点] 配列を渡した場合は配列が展開されて引数を受け取る

use_args に配列を渡した場合、その配列を展開して引数を受け取ります。

p proc { _1 + _2 }.use_args.call [1, 2]
# => 3

これは次のように引数を受け取っていることを想定した挙動になっています。

p proc { |_1, _2| _1 + _2 }.call [1, 2]
# => 3

この挙動はブロックを proclambda で定義したのかで変わります。
proc の場合は引数の配列を展開し、lambda の場合は引数の配列を展開しません。

proc { [_1, _2] }.use_args.call [1, 2] # => 3
lambda { [_1, _2] }.use_args.call [1, 2] # => Error

# proc の場合は渡されなかった引数のパラメータは nil を返す
proc { [_1, _2] }.use_args.call 1 # => [1, nil]

# lambda の場合はエラー
lambda { [_1, _2] }.use_args.call 1 # => Error

# 配列を展開する
[[1, 2], [3, 4]].use_args.map &proc{ _1 + _1 }
# => [2, 6]

# 配列を展開しない
[[1, 2], [3, 4]].use_args.map &lambda{ _1 + _1 }
# => [[1, 2, 1, 2], [3, 4, 3, 4]]

メソッドに対して do 〜 end をブロックとして渡した場合は proc として扱われるので、引数の扱いを厳密にしたい場合は lambda を使用してください。

その他

use_arguments では引数以外にも _receiverメソッドを呼び出したレシーバオブジェクト)や _self (自分自身)などが定義されています。
なので、引数意外にも次のようなコードを書くことができます。

# 再帰
fact = proc { _1 == 1 ? 1 : _1 * _self.(_1 - 1); }.use_args
p fact.call 5
# => 120

p [1, 2, 3].use_args.map { _1 + _receiver.size }
# => [4, 5, 6]

このようにブロックをちょっと便利に書くこともできます。