「プログラミングErlang」(Joe Armstrong著/榊原一矢訳)という本でちょっとErlangの世界を覗いているよ。

プログラミングErlang by Joe Armstrong

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の分離に使えることに気づけたから僕自身は良しとするよ。

(追記:2010-8-16)RubyのArray#sumをErlangのsumに合わせて末尾再帰版に修正しました

  1. Rubyのシンボルのようなもの


blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS