RubyのProcクラスはとても便利なのですが、最近このクラスにcurryというメソッドがあるのを知りました。
a = -> (x, y, z) {(x||0) + (y||0) + (z||0) }
p a.curry[1][2][3]
#=> 6
例えば「1からnの連続した数字を列挙する。ただし、3で割れるならFizz、5で割れるならBuzzと数字の代わりに表示する」という動作をさせたいとします
実装1.rb
def fizzbuzz(num)
(1..num).map do |n|
case
when n % 3 == 0 then 'Fizz'
when n % 5 == 0 then 'Buzz'
else n
end
end.join(' ')
end
# 普通はこんなふうには書かないが、あえてProcを使う
def fizzbuzz_v2(num)
(1..num).map do |n|
factor = ->(y){ n % y == 0 }
if factor === 3 # factor[3] や factor.call(3)と同じ
'Fizz'
elsif factor === 5
'Buzz'
else
n
end
end.join(' ')
end
puts fizzbuzz 30
#=> "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 Fizz
puts fizzbuzz_v2 30
#=> "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 Fizz
これは Proc#curryで書くと少しだけDRYになります。
実装2.rb
def fizzbuzz(num)
# factor[y][x]で、yがxで割り切れるかの判定をtrue/falseで返す
factor = ->(y, x){ x % y == 0 }.curry
(1..num).map do |n|
case n
when factor[3] then 'Fizz' # factor[3][n] の判定
when factor[5] then 'Buzz'
else n
end
end.join(' ')
end
puts fizzbuzz 30
#=> "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 Fizz"
factorの中身がもう少し複雑になると、caseで同じような条件をいくつか判定する際に有り難みが出てきそうな気がしますね。
リファレンスを見てみると、可変長引数の場合は以下のような使い方も出来るよということです
# 引数を合算する ex) sum[1,20,300] #=> 321
sum = ->(*args){args.inject(:+)}
sum_c = sum.curry
sum_c[1][2][3]
# 最初のsum_c[1]で数値が帰ってくるので、意図した動きにならない
# 可変長引数の長さを3と固定してしまう
sum_c3 = sum.curry(3)
sum_c3[1][2] #=> <Proc:0x007f0a484f5958 (lambda)>
#引数を2つしかもらってないので、まだProcオブジェクト
sum_c3[1][2][3] #=> 6
# こういう書き方でもOK.
sum_c3[1,2][3] # caseで使うときは、この書き方を使えそう?
sum_c3[1][2,3]
sum_c3[1,2,3]
今回は->
を使いましたがlambda
proc
Proc.new
あたりでも同じように使えます。あんまり使い分けたことがない…