haskellでフィボナッチ そしてPerlで書いてみた

普通のhaskellプログラミングを読み終えて、なんか次に読むものないかなと探していたら、webarchiveに面白そうなページがあることを知ったので、ダウンロードして読んでます。

3回目の講義資料に、タプルを使って高速にフィボナッチ数を計算する話があったので、なんでそうなるのかを理解するために手で書き下してみた。

普通に書くとこんな感じ(fib)。

fact :: Int -> Int
fact n
  | n == 0  = 1
  | n > 0   = fact (n-1) * n

書き下すと、計算量があっという間に増えていくことがわかる。

fib 5
=> fib 3 + fib 4
=> (fib 1 + fib 2) + (fib 2 + fib 3)
=> (fib1 + (fib 0 + fib 1)) + ((fib 0 + fib 1) + (fib 1 + fib 2))
=> (fib1 + (fib 0 + fib 1)) + ((fib 0 + fib 1) + (fib 1 + (fib 0 + fib 1)))

タプルを使った例(fastfib)

フィボナッチ数列を効率良く計算する方法を考えよう.前に見たとおり,フィボナッチ数列の各項は,その前の二つの項の和である.逆に言えば,二つの連続した項の値が与えられれば,次の項を計算するのは簡単なのだ.

fibStep :: (Int, Int) -> (Int, Int)
fibStep (u, v) = (v, u+v)

fibPair :: Int -> (Int, Int)
fibPair n
  | n == 0    = (0, 1)
  | otherwise = fibStep (fibPair (n-1))

fastFib :: Int -> Int
fastFib = fst . fibPair

コードを眺めただけではなんで速くなるのか実感がわかなかったので、書き下してみる。

fastFib 5
=> fst (fibPair 5)
=> fst (fibStep (fibPair 4))
=> fst (fibStep fibStep (fibPair 3))
=> fst (fibStep fibStep fibStep (fibPair 2))
=> fst (fibStep fibStep fibStep fibStep (fibPair 1))
=> fst (fibStep fibStep fibStep fibStep fibStep (fibPair 0))
=> fst (fibStep fibStep fibStep fibStep fibStep (0, 1))
=> fst (fibStep fibStep fibStep fibStep (1, 1))
=> fst (fibStep fibStep fibStep (1, 2))
=> fst (fibStep fibStep (2, 3))
=> fst (fibStep (3, 5))
=> fst (5, 7)
=> 5

をを、タプルにすることで、計算量が減ってます、自分で展開していくとなるほどとよくわかる。

さて、最近Higher-order Perlを読んでいる僕としては、これをperlで書いてみたくなるわけである。

 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
#!/usr/bin/perl

use strict;
use warnings;

sub fibStep {
    my $n = shift;
    return [$n->[1], $n->[0] + $n->[1]];
}

sub fibPair {
    my ($n, $fs, $fp) = @_;
    return [0,1] if ($n == 0);
    return $fs->($fp->($n-1,$fs,$fp))
}

sub fastFib {
    my $n = shift;
    my $fs = \&fibStep;
    my $fp = \&fibPair;
    my $fib_num = $fp->($n,$fs,$fp);
    return $fib_num->[0];
}

my $number = shift;
print fastFib($number)

サブルーチンのリファレンスを引数にとらないといけないのがhaskellと異なるとこかな。あとは書いてて思ったのは関数の型を書くとプログラムの流れが理解しやすいナァと。

実行すると一瞬で計算が終わる(速い!)

実際に処理の流れを追うために-dオプションを付けて実行してsを連打すると、main::fibPairがどんどん実行されて$nが0になったところで今度はmain::fibStepが次々実行されていく様がわかる。

Higher-order Perlの5章にもフィボナッチ数列のことが書いてあるのだが、まだそこまで読み進めていない。(今3章突入したところ)

ProductName Higher-Order Perl: Transforming Programs with Programs
Mark Jason Dominus
Morgan Kaufmann / 5692円 ( 2005-03-28 )


ProductName ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
青木 峰郎
ソフトバンククリエイティブ / ?円 ( 2006-06-01 )


haskellを勉強しながら、Higher-order Perlを読むと、色々新たな発見があって楽しい。

Meadowでhaskellモード

ふつうのHaskellプログラミングも、とうとう第三部のWIKIをつくろうの章に突入。

ProductName ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
青木 峰郎
ソフトバンククリエイティブ / ?円 ( 2006-06-01 )


でも、第三部は実際のコードのポイントだけ解説なので、サポートページからコードをダウンロードしてきてつきあわせながら読む感じになるので、Meadowでソースコードを開いて本を読んでる。

で、perlとかjavascriptは色がついてて見やすいのに、、、Meadowにhaskellモードってないのかなぁと思って探したらあった

これでかなりコードが読みやすくなった。

あと本を最後まで読んでみての感想だが、ふつうのHaskellプログラミングは一部、二部と三部の具体事例の間にはかなり開きがあるような気がした。二部と三部の間に数学的な問題を事例として扱う部もあったほうがいいような。

というわけで、「連載: Haskellプログラミング」なんかを読んで解くといいかもしれん。

モナドで躓く

ふつうのHaskellプログラミングも、とうとう11章へ。中盤も佳境をむかえております。

ProductName ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
青木 峰郎
ソフトバンククリエイティブ / ?円 ( 2006-06-01 )


モナドが~~

わかりません、さっぱり。他の言語での例があったので少し読んでみた。

檜山正幸のキマイラ飼育記 - 世界で一番か二番くらいにやさしい「モナド入門」

振り返れば我々は; 関数から副作用を取り除き(汚れ作業はCountupMainに押しつけ)、代わりに戻り値に副作用の意図を詰め込み、それによって失われた関数結合の自由さを関数の拡張により取り戻しました。

javascriptもperlのコードもなんとなくわかったような気にはなったんだけど、ふつうのHaskellプログラミングに戻ると混乱してしまうヨ。

週末にでも幾つかコードを書いてみてよく考えることにしよう。

http://del.icio.us/kzfm/haskell

HaskellでBioinformatics

本日普通のHaskellプログラミングの7章まで読み終えた。そろそろ、具体的な例題解いてみたいなぁと思ったが、3部13章のWIkiをつくってみようの章まではあと5つもある。今のとこ、ここまで読んで、実用Perlプログラミングみたいに他の言語との違いみたいなページを割いてくれるとよかったりと思った。それ以外は、unixのコマンドをtailとかheadとかをhaskellで書こうみたいな感じにすすむので理解はしやすいヨ。

ProductName ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
青木 峰郎
ソフトバンククリエイティブ / ?円 ( 2006-06-01 )


実際に、どんな感じの問題に適用すんのがいいのかなぁとか、考えたときに、ゲノムみたいな大きい配列操作すんのに適してるも。もしかしてbioinformaticsでなんかあるかなと探したらあった。

論文あったのでアブスト眺めたけど、DPってhaskell向きなのかな?まだ、いまいちわかってなさげ。

Rによる統計的プログラミング でRと比較しているのだから統計的な(というより数学的な)記述にむいているのは感覚的にわかるんだけどなぁ。

ということはchemoinformaticsでの利用例あるだろ、とか思うんだが見当たらない。

perlで高階関数とカリー化

先週は電車通勤+歩きで仕事場を行き来したので、電車の中で、英語漬けで英語のトレーニングしてみたり、普通のHaskellプログラミング読んだりと、電車通勤はなかなか有意義なことに気付かされた。特に普通のHaskellプログラミングは第一部を読み終えたことで、高階関数変数の束縛というものがわかったような気がした?

、、、、したのかどうかまだよくわからんので、自分の理解力を認識するために少し書いてみた。

まず、言葉の定義を。

  • 変数の束縛
  • (例えば)関数という値があり、それが変数名を束縛している
  • 高階関数
  • 関数を返したり引数にとったりする関数
  • 変数が関数に束縛されているから、関数を変数として扱うことが出来る?
  • それとも関数を変数として扱うことことが出来れば高階関数はつくれるのか?

perlでも変数を束縛することが出来れば高階関数など扱えるのかなと。クロージャ使えってことか?と思って調べて見ると面白そうなのが。

(って全部一度は見たことあるじゃん。でもワカンネとか言って放り出した気が、、、、)

ぱるも日記ではカリー関数を作る例が示されていたが、関数を返す高階関数の例ですな。カリー化に関しては、これなど面白かった。

torus solutions!は僕がblosxomをいじくるときにかなり参考にさせてもらったサイトです。

XML を Perl の高階関数で。 : torus solutions!

XML::LibXML で XML を作るときに、 いちいち createElement とか appendChild とか書くのに飽きてきたので、 高階関数版を作ってみた。
これも、関数を返すといってよいのでしょう。

関数を引数にとる高階関数をperlで書けるのだろうか?というのが気になるところだが、関数のリファレンスを引数にとる関数を書けばいいのかなと思ってこれまた調べてみた。

ここまでの関数を返す関数とか関数を引数にとる関数を考えると、perlでの(関数による)変数の束縛ってのは、関数のリファレンスを変数として扱うことでなされると考えてよいのかな。

ProductName Higher-Order Perl: Transforming Programs with Programs
Mark Jason Dominus
Morgan Kaufmann / 5692円 ( 2005-03-28 )


Higher-order Perlも気になる

ふつうのHaskellプログラミング

rubyもpythonも中途半端ななのに、とうとう耐え切れずに手を出しちゃいました。

Haskell

ProductName ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
青木 峰郎
ソフトバンククリエイティブ / ?円 ( 2006-06-01 )