歩いたら休め

なんでこんな模様をしているのですか?

【Ruby】Effective Rubyの項目19 『ruduceを使ってコレクションを畳み込む方法を身に付けよう』のメモ

普段はPythonユーザーなのですが、仕事ではRubyを扱う機会が増えてきました。

Perlから受け継いだTMTOWTDIの思想とか、書いてて勉強になり、楽しい言語です。 関数の定義に必ずしも()が必要なく、関数が変数と同じように扱えるところとか、 「ここインスタンス変数として用意したけど、やっぱり関数で値を渡したいや」みたいに自由にプログラミングできて楽しいです。 ブロックも含めてあらゆるものに戻り値のある世界観とか、どことなくLispっぽい雰囲気も感じます (戻り値があることが前提なので、return句が不必要なのも面白いです)。

というわけで最近はEffective Rubyを読んで楽しいRubyプログラミングを勉強しています。

Effective Ruby

Effective Ruby

reduceメソッドが楽しそうだったのでメモを残しておきます。

まず驚いたのは、reduceメソッドで与える演算子をシンボルとして与えられることです。

def sum (enum)
    enum.reduce(0, :+)
end

Pythonは融通効かないので、いちいちlambda式をフルに書かないといけません。

# python3系ではreduce関数がfunctoolsの中に用意されている
from functools import reduce
def sum(enum):
    return reduce(lambda x, y: x+y, enum, 0)

ちなみにHaskellでは「関数は必ず一つの引数をとる(複数引数のあるように見える関数も、実は間で1つの引数を持つ関数を返しているだけ)」という設計のおかげでめちゃめちゃシンプルに書けるようです。 Rubyではこれと同じくらいシンプルに書けますね。

sum enum = foldl (+) 0 enum

プログラミングHaskellのfoldr, foldlの説明が秀逸だった件 - あと味 Haskell の畳み込み関数 - foldl, foldr | すぐに忘れる脳みそのためのメモ

次は、こんな処理。リストを連想配列(ハッシュ/辞書)に変換するような処理。rubyならこう書くそうです。

# reduceを使わない場合はHash::[]で変換する
Hash[array.map {|x| [x, true]}]

# reduceを使う場合は初期値に空のハッシュを与えたreduceを使う
array.reduce({}) do |hash, element|
    hash.update(element => true)
end

Pythonならリスト内包表記で書きます。Python3から辞書でもリスト内包表記が使えるようになりました。

{x: True for x in array}

その後の21歳以上のユーザーだけを集めた新しい配列を返す処理は、

users.reduce([]) do |names, user|
    names << user.name if user.age >= 21
    names
end

Pythonのリスト内包表記ならこんな感じ。

[user.name for user in users if user.age >= 21]

Pythonではlambda式がかなり制限される(引数と戻り値しか指定できない)のでreduce関数の使いどころはかなり制限されます。 代わりに簡単な処理ならリスト内包表記と、(Python3から)mapやfilter関数の戻り値がリストではなく遅延評価されるジェネレーターになることで対応している気がします。 難しい処理はちゃんと関数を定義して記述してあげよう、という方針だと思います。

Rubyではreduceがブロック記法として与えられており、中に複雑な処理を書くことができるので、 Pythonより柔軟な書き方ができそうです。 初期値をreduce([])のように指定できるので、きちんと記述してあれば、戻り値として返ってくる型を推測しやすそうです。

  • アキュムレータの初期値はかならず使おう。
  • reduceのブロックは、かならずアキュムレータを返すようにする。現在のアキュムレータを書き換えるのは問題ないが、それをブロックから返すのを忘れないようにすることだ。