Ruby と関数型プログラミングについてのメモ(1)
jp.quora.com
まつもとゆきひろさんは、Ruby を「関数型言語と呼ぶのにはだいぶ抵抗があります」といっておられる。これでもう結論は出たようなものだが、それでも、Ruby にはだいぶ関数型言語の考え方が取り入れられているのも事実だ。それをちょっとだけ考えてみる。
Array, Enumerable, Enumerator
配列a = [1, 2, 3]
の各要素を 2倍した新しい配列new_a
が欲しい。これを「手続き型」プログラミングを使ってやるとどうなるか。
a = [1, 2, 3] new_a = [] for i in a new_a << i * 2 end new_a #=>[2, 4, 6]
例えば、こんな感じ?
でも、たぶん Ruby初心者でも、あんまりこんな風には書かないよね。Array#map
を使って、こう書くと思う。
a = [1, 2, 3] new_a = a.map { |i| i * 2 } #=>[2, 4, 6]
このmap
が既に関数型プログラミングの考え方を使っている。この場合、ブロックが「関数」の役割を果たしている。そもそも Ruby でブロックを使うこと自体、関数型プログラミングっぽいのだ。ここでのブロックは、簡単に「無名関数」を使っていることになる。
Enumerable
やEnumerator
は繰り返しを抽象化している。例えば、アルファベットの小文字だけで出来ている文字列を、すべて大文字にしたいとする。もちろん、String#upcase
を使えば一発だが、それに頼らず、仮にこんなふうに自力でやってみる。
str = "ruby" e = str.each_byte e.map { |b| (b - 32).chr }.join #=>"RUBY"
このe
のクラスがEnumerator
で、each_byte
により(繰り返しのように)1文字ずつ小文字を切り出す。Enumerator
はEnumerable
を継承しているのでEnumerable#map
が使えるのである。
Enumerable
やEnumerator
を駆使したプログラミングはとても Ruby らしく、関数型プログラミングっぽい。
ブロックを敢て使わない 関数型Rubyプログラミング
yuroyoro.hatenablog.com
ブロックは関数型プログラミングをお手軽に実現する、すばらしい発明である。それだけで、可読性の高い、しかも強力なプログラミングが可能になる。
しかし、ここからは趣味の領域になるが、敢て(あまり)ブロックを使わず、もっと関数型プログラミングを推し進めることもできる。Proc
やMethod
オブジェクトを関数のように使ったプログラミングである。
例えば配列a = [1, 2, 3]
の各要素を 2倍して、さらに各々 10を足した配列が得たいとする。もちろん、
a = [1, 2, 3] new_a = a.map { |i| i * 2 + 10 } #=>[12, 14, 16]
でいいのだが、あえて演算をバラしてみる。
a = [1, 2, 3] new_a = a.map { |i| i * 2 }.map { |i| i + 10 } #=>[12, 14, 16]
しかしこれを、Proc
(あるいはlambda
)を関数のように使って、ブロックの代わりにしてみる。
a = [1, 2, 3] double = ->(i) { i * 2 } add = ->(i) { i + 10 } new_a = a.map(&double).map(&add) #=>[12, 14, 16]
map
を重ねる代わりに、Proc#>>
を使って「関数合成」してもよい。
new_a = a.map(&double >> add) #=>[12, 14, 16]
関数合成の順番を入れ替えれば、10を足してから 2倍ということになる。
new_a = a.map(&add >> double) #=>[22, 24, 26]
Methodオブジェクト
じつはメソッドも、Proc
のように「関数化」できる。これはかなり趣味の領域に入ってくるが。
似たことを、Object#method
を使ってやってみる。
a = [1, 2, 3] def double(i) = i * 2 def add(i) = i + 10 new_a = a.map(&method(:double) >> method(:add)) #=>[12, 14, 16]
一行def の文法を使った。見てのとおり、かなり趣味的である。
method
メソッドの名前が長ければ、適当にそのエイリアスを定義してもよい。
Object.alias_method(:■, :method) new_a = a.map(&■(:double) >> ■(:add)) #=>[12, 14, 16]
こうなるともうよくわからない笑。