Rubyプログラマの発想

「Lisp脳」の謎に迫る – Schemeプログラマの発想という僕の好きな記事があるのですが、Rubyプログラマの発想も同じだなーと、ふと思いました。

データの加工として考える

1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

記事ではこの実装の例(手続き型)として、こんなコードが載っていますが、

(do ((x 1 (inc! x)))
    ((>= x 100) x)
  (cond ((= (modulo x 15) 0)
         (print "FizzBuzz"))
        ((= (modulo x 5) 0)
         (print "Buzz"))
        ((= (modulo x 3) 0)
         (print "Fizz"))
        (else
         (print (x->string x)))))

これをRubyに書き直すと、こんなコードになるのではないでしょうか。

(1..100).each do |x|
  if x % 15 == 0
    puts 'FizzBuzz'
  elsif x % 5 == 0
    puts 'Buzz'
  elsif x % 3 == 0
    puts 'Fizz'
  else
    puts x
  end
end

でもRubyプログラマのコードでこんなコードは、めったに見ません。大抵はSchemaプログラマの発想と同じく、

「とりあえず1から100までのリストを作れば良さそうだ」

と考えます。つまりここからがスタートです。

(1..100).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]

「mapを使ってこのリストを加工して、別のリストを返せばいいじゃん」

これは「mapを使ってこの配列を加工して、別の配列を返せばいいじゃん」って考えますね。

class Fixnum
  def fizzbuzz
    return 'FizzBuzz' if self % 15 == 0
    return 'Buzz'     if self % 5 == 0
    return 'Fizz'     if self % 3 == 0
    self
  end
end
(1..100).map(&:fizzbuzz)
#=> [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28, 29, "FizzBuzz", 31, 32, "Fizz", 34, "Buzz", "Fizz", 37, 38, "Fizz", "Buzz", 41, "Fizz", 43, 44, "FizzBuzz", 46, 47, "Fizz", 49, "Buzz", "Fizz", 52, 53, "Fizz", "Buzz", 56, "Fizz", 58, 59, "FizzBuzz", 61, 62, "Fizz", 64, "Buzz", "Fizz", 67, 68, "Fizz", "Buzz", 71, "Fizz", 73, 74, "FizzBuzz", 76, 77, "Fizz", 79, "Buzz", "Fizz", 82, 83, "Fizz", "Buzz", 86, "Fizz", 88, 89, "FizzBuzz", 91, 92, "Fizz", 94, "Buzz", "Fizz", 97, 98, "Fizz", "Buzz"]

もっといろんな処理(例えば2で割り切れる数をnilにする)を加えたければ、この後ろにmapで処理を重ねていけば良いのです。

(1..100).map(&:fizzbuzz).map(&:nanika)

データを加工する部分をメソッド化して再利用できるようにしておく、というのは割とポピュラーですよね。

Rubyプログラマの人は関数型言語の経験がなくても、結果としてSchemaプログラマのような考え方をしているのが面白いなーと思いました。