RubyでもErlangの[H|T]したいよ!
「プログラミングErlang」(Joe Armstrong著/榊原一矢訳)
という本でちょっとErlangの世界を覗いているよ
- 作者: Joe Armstrong,榊原一矢
- 出版社/メーカー: オーム社
- 発売日: 2008/02/23
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 284回
- この商品を含むブログ (97件) を見る
Erlangのような関数型言語はリスト処理に優れていて
便利な構文がいろいろとあるんだね
例えばリストの先頭に別の要素を結合したものを
| を使って簡単に作れるんだ
1> Langs = [haskell, erlang, lisp]. [haskell,erlang,lisp] 2> NewLangs = [ruby | Langs]. [ruby,haskell,erlang,lisp]
[ruby | Langs] のところが
リストへの要素の追加になってるよ
ちなみにErlangでは変数は大文字で始まって
アトム*1は引用符なしで書けるんだ
また式の終りには英語のようにピリオドを付けるよ
一方でリストから先頭要素を分離したものを作るには
次のようにするよ
3> [H|T] = Newlangs. [ruby,haskell,erlang,lisp] 4> H. ruby 5> T. [haskell,erlang,lisp] 6>
変数Hにリストの先頭要素がバインドされて
変数Tにリストの残りがバインドされる
この何がうれしいかって言うと
これでリストの再帰的な処理が
すごく簡単に書けるんだよ
試しにリストの要素を足し合わせる
sum関数を定義してみるね
sum([], N) -> N; sum([H|T], N) -> sum(T, H+N). sum([1,2,3,4,5], 0). => 15
リストが空のときは第2引数Nを返す
そうでないときは先頭要素HをNに足して
残りのリストTでsum関数を再帰する
簡潔でカッコイイよねー
リストの要素に関数を適用する
map関数も書いてみるよ
map([], _) -> []; map([H|T], F) -> [F(H)|map(T,F)]. map([1,2,3,4,5], fun(X)->X*X end). => [1,4,9,16,25]
リストが空のときは空リストを返す
そうでないときは先頭要素Hに関数Fを適用し
一方で残りのリストTでmap関数を再帰し
これらを結合してできるリストを返す
つまりここでは[H|T]を使って
リストの分離と結合をしてる
素敵だねー
ちなみにfun(X)->X*X end は
Rubyのlambdaにそっくりだよね
Rubyでやってみる
で
これを見てRubyでも | で
リストの結合や分離ができたら
カッコイイと思ったんだ
じゃあ少しやってみるね
Object#| を定義するといろいろと問題がありそうなので
ここではFixnum String Symbolに対象を絞って実装するよ
[String, Symbol].each do |klass| klass.module_eval do def |(other) [self] + other end end end class Fixnum alias :__OR__ :| def |(other) case other when Array; [self] + other else __OR__(other) end end end list = [2,3,4] 'a' | list # => ["a", 2, 3, 4] :a | list # => [:a, 2, 3, 4] 1 | list # => [1, 2, 3, 4] 1 | 3 # => 3
これでリストの結合ができた
どうかな?
次にリストの分離だけれど
Array#| を再定義して引数にIntegerが渡されたら
先頭要素を分離するというのを考えたんだけど
なんかスマートじゃないんだ
で
ここでハタと気が付いたんだけど
Rubyには既にカッコイイ分離方法があったんだよ
list = [1, 2, 3, 4, 5] a, *b = list a # => 1 b # => [2, 3, 4, 5]
Rubyの多重代入はリストの分離に使えるんだ
さて
Rubyでも再帰を使ってsumとmapを定義してみよう
Rubyはオブジェクト指向だから
Arrayのメソッドとしてこれらを定義するよ
まずは先の定義を使わない例を示すよ
class Array alias :head :first def tail drop 1 end def sum(acc=0) return acc if empty? tail.sum(head+acc) end def mappy(&blk) return [] if empty? [blk[head]] + tail.mappy(&blk) end end [*1..10].sum # => 55 [*1..5].mappy { |i| i * i } # => [1, 4, 9, 16, 25] %w(ruby erlang haskell lisp).mappy { |n| n.capitalize } # => ["Ruby", "Erlang", "Haskell", "Lisp"]
ここではリストの先頭を返すheadメソッドと
残りを返すtailメソッドを別途定義しているよ
次に
先に定義した | と多重代入を使った
ヴァージョンを示すよ
class Array def sum(acc=0) return acc if empty? head, *tail = self tail.sum(head+acc) end def mappy(&blk) return [] if empty? head, *tail = self blk[head] | tail.mappy(&blk) end end [*1..10].sum # => 55 [*1..5].mappy { |i| i * i } # => [1, 4, 9, 16, 25] %w(ruby erlang haskell lisp).mappy { |n| n.capitalize } # => ["Ruby", "Erlang", "Haskell", "Lisp"]
できたよ!
って
変数名にheadとtailを使ったからか
なんか見た目に違いがなくて
あんまり面白くなかったね..
でもRubyの多重代入が
リストのheadとtailの分離に使えることに
気づけたから僕自身は良しとするよ