26 April, 2010

[Coq] reverse (reverse xs) = xs

 先日のProofCafeでみんなで解いていた reverse (reverse xs) = xs の証明について、どんな風に解くかを解説というか自分の解答を晒すというか。

 教科書でこの問題を演習課題として解く場合は、予め必要な補題が順々に証明課題として与えられる事が多いが、現実の問題ではそういうことはまず無い。(このあたり、現実の問題と試験問題の違いにも通ずる話だ。)

 まず、ProofCafe01から必要な定義をコピーしよう。
Inductive list (A: Type) : Type :=
| nil : list A
| cons : A -> list A -> list A.

Implicit Arguments nil [A].
Implicit Arguments cons [A].

Fixpoint append {A : Type} (xs ys: list A) : list A :=
match xs with
| nil => ys
| cons x xs => cons x (append xs ys)
end.
Infix "++" := append (at level 60).

Theorem append_assoc : forall (A:Type) (xs ys zs:list A),
(xs ++ ys) ++ zs = xs ++ (ys ++ zs).
Proof.
(* あなたの証明を書いてね *)
Qed.

定理append_assocの証明は難しく無いと思うが、判らない人はProofCafeのページを参照して欲しい。

 次いで、関数reverseを定義しよう。
 関数型言語で関数を末尾再帰で書くのに慣れている人は、ついうっかり
Fixpoint rev' {A:Set} (xs ys:list A) :=
match xs with
| nil => ys
| cons x xs' => rev' xs' (cons x ys)
end.
Definition rev {A:Set} (xs:list A) := rev' xs nil.

と書いてしまうだろう。私も実は当日そう書いてしまい嵌った。

 ここは実行効率を考えず、ひとまず素直に、
Fixpoint reverse {A:Set} (xs:list A) :=
match xs with
| nil => nil
| cons x xs' => (reverse xs') ++ (cons x nil)
end.

と定義して証明する方が簡単だ。関数を実際に動かしてみる場合は、
Eval compute in reverse (cons 1 (cons 2 (cons 3 nil))).

とか入力してみれば良い。

 ここから、どうやって証明していったか、考えた過程を示そう。
 まずはいきなり、定理を証明しようとしてみた。
Theorem reverse_reverse : forall (A:Set) (xs:list A),
reverse (reverse xs) = xs.
Proof.
induction xs; simpl.
reflexivity.

最初にxsについての帰納法を試み、xs=nilのケースは簡単に証明出来た。次のゴールの
reverse (reverse xs ++ (cons a nil) = cons a xs
については、リストを ++ で繋いでreverseする、
Hypothesis reverse_append : forall (A:Set) (xs ys:list A),
reverse (xs ++ ys) = (reverse ys) ++ (reverse xs).

という定理があれば都合が良さそうだと何となく思いつく。(なんとなく美しげな定理だし。)

 そこで、とりあえず上記のHypothesisを先に定義して、改めてreverse_reverseを証明する。
Theorem reverse_reverse : forall (A:Set) (xs:list A),
reverse (reverse xs) = xs.
Proof.
induction xs; simpl.
reflexivity.
rewrite (reverse_append A (reverse xs) (cons a nil)).
rewrite IHxs.


 この時点でゴールが
reverse (cons a nil) ++ xs = cons a xs
となる。そこで再度
Hypothesis append_cons_nil : forall (A:Set) (a:A) (xs:list A),
(cons a nil) ++ xs = cons a xs.
Hypothesis reverse_cons : forall (A:Set) (a:A),
reverse (cons a nil) = cons a nil.

を追加してから、改めてreverse_reverseを証明する。
Theorem reverse_reverse : forall (A:Set) (xs:list A),
reverse (reverse xs) = xs.
Proof.
induction xs; simpl.
reflexivity.
rewrite (reverse_append A (reverse xs) (cons a nil)).
rewrite IHxs.
rewrite (reverse_cons A a).
rewrite (append_cons_nil A a xs).
reflexivity.
Qed.


 証明が出来たので、Hypothesisで誤摩化していた部分をLemma, Theoremに書き換えて証明する。
Lemma append_cons_nil : forall (A:Set) (a:A) (xs:list A),
(cons a nil) ++ xs = cons a xs.
Lemma reverse_cons : forall (A:Set) (a:A),
reverse (cons a nil) = cons a nil.

については難しく無いので証明してみると良いだろう。(intros, simpl, reflexivityで証明出来る。)

Theorem reverse_append : forall (A:Set) (xs ys:list A),
reverse (xs ++ ys) = (reverse ys) ++ (reverse xs).

については、解説しよう。

Proof.
intros A xs ys.
induction xs; simpl.
assert (append_nil: forall (zs:list A), zs = zs ++ nil).
induction zs; simpl.
reflexivity.
rewrite <- IHzs. reflexivity.
apply (append_nil (reverse ys)).
rewrite IHxs.
rewrite (append_assoc A (reverse ys) (reverse xs) (cons a nil)).
reflexivity.
Qed.

証明の途中でzs = zs ++ nilが使いたくなり、わざわざ補題にするまでもないと思って、途中でassertで証明している。
 この証明したreverse_appendを使って、reverse_reverseを証明すれば完成。

 全体を改めて示すとこのようになる。
Inductive list (A: Type) : Type :=
| nil : list A
| cons : A -> list A -> list A.

Implicit Arguments nil [A].
Implicit Arguments cons [A].

Fixpoint append {A : Type} (xs ys: list A) : list A :=
match xs with
| nil => ys
| cons x xs => cons x (append xs ys)
end.
Infix "++" := append (at level 60).

Theorem append_assoc : forall (A: Type) (xs ys zs : list A),
(xs ++ ys) ++ zs = xs ++ (ys ++ zs).
Proof.
intros.
induction xs; simpl.
reflexivity.
rewrite IHxs. reflexivity.
Qed.

Fixpoint reverse {A:Set} (xs:list A) :=
match xs with
| nil => nil
| cons x xs' => (reverse xs') ++ (cons x nil)
end.

Lemma append_cons_nil : forall (A:Set) (a:A) (xs:list A),
(cons a nil) ++ xs = cons a xs.
Proof.
intros. simpl. reflexivity.
Qed.
Lemma reverse_cons : forall (A:Set) (a:A),
reverse (cons a nil) = cons a nil.
Proof.
intros. simpl. reflexivity.
Qed.

Theorem reverse_append : forall (A:Set) (xs ys:list A),
reverse (xs ++ ys) = (reverse ys) ++ (reverse xs).
Proof.
intros A xs ys.
induction xs; simpl.
assert (append_nil: forall (zs:list A), zs = zs ++ nil).
induction zs; simpl.
reflexivity.
rewrite <- IHzs. reflexivity.
apply (append_nil (reverse ys)).
rewrite IHxs.
rewrite (append_assoc A (reverse ys) (reverse xs) (cons a nil)).
reflexivity.
Qed.

Theorem reverse_reverse : forall (A:Set) (xs:list A),
reverse (reverse xs) = xs.
Proof.
induction xs; simpl.
reflexivity.
rewrite (reverse_append A (reverse xs) (cons a nil)).
rewrite IHxs.
rewrite (reverse_cons A a).
rewrite (append_cons_nil A a xs).
reflexivity.
Qed.

[Coq] Proof Cafe #01

Proof Cafe (栄)に参加しました。

 当日使われた資料はyoshihiro503の日記を参照の事。

 yoshihiro503さんによるCoq最速文法マスターという感じで、僅か90分の入門講座で、(Haskellなど関数型言語の知識があるとはいえ)Coqに初めての人が
Theorem append_length : forall (A: Type) (xs ys: list A),
length (xs ++ ys) = length xs + length ys.

を証明出来る様になるってのは、やはり説明の手際が良いよなぁ。次に入門用PPTを修正する時は是非とも参考にしよう。

 Proof Cafe自体は2時間だったんですが、その後、懇親会を実施して頂き、なんか身に余る様な歓待をして頂きました。皆さんどうもありがとうございました。Coqに限らず関数型言語とかIT業界の話とか色々楽しく話をしました。

 Proof Cafeは今後も毎月名古屋で開催されるとか、Proof CafeでもCPDTを読もうとしている、ようです。Formal Methods Forumの方でも頑張ってCPDTを読んでいきたいです。

 

18 April, 2010

[Coq] Install on NetWalker

 買ってあったがしばらく放置していたNetWalkerにCoq, CoqIDEをインストールしました。まぁUbuntuなんで、sudo apt-get install coq coqideでインストール出来て当然だが、なんかCoqのバージョンが古いみたい。
 ともあれ通勤途中にCoqで遊べる様になった。

17 April, 2010

[Coq][FM] Formal Methods Forum #4 on 4/29

 形式仕様に関する勉強会のATND - 第4回FormalMethods勉強会を4/29に行います。
 Coqに関してはCerti􏰀ed Programming with Dependent Typesという教科書を今回から読み進める予定です。この本は、関数型言語のプログラマ向けに書かれた割と実践的な教科書です。
 Coq以外の内容についてはATNDのページからFormal Methods Forumのページを辿って下さい。

[Haskell] Haskellers Meeting 2010 Spring

Haskellers Meeting 2010 Springを聞きにいきました。

 和田先生の話は、まぁ割とどうでも良い昔話だった。
 メインはSimon Peyton JonesさんのSTMの話。基本的には"Beautiful Code"に載っている話で、ジョークなども比較的聞き取りやすかった。ScalaにもSTM早く欲しいなぁ。
 山本さんのHaskellでWebサーバの話は前に聞いたことのある話だった。
 山下さんの擬データの話が、実は一番興味深かった。紹介された論文「擬データを用いた対話的関数プログラミングに関する研究」(石井裕一郎)はWeb上で見つからなかったが、「擬データと関数による並行プロセス群の記述」は検索するとCiNii上で読める様だ。擬データは興味深いのだけど、Haskell以外の言語では意味が無いかなぁ。

----
追記:
山下さんの発表資料はここから入手可能。

03 April, 2010

[Coq][FM] Formal Methods Forum Meeting #3

一見、キャンセル待ち状況になっている第3回FormalMethods勉強会ですが、まぁ椅子を追加して対応可能だと思うので、とりあえず名前をATNDに書いて下さい。

あと、直前に案内メールとかが流れますのでFormalMethods勉強会のGoogle groupにも登録して頂くと良いと思います。

28 March, 2010

[Coq] Coq Course Materials at Nagoya Univ.

 名古屋大学の2009年度後期のGarrigue先生のCoqの講義の教材が公開されているので、Coqの勉強として解いてみた。
 全部は自力では解けず、答えを参照しつつ解いた部分もある。自習用には良い教材だと思った。
 解答とかcoqdocでHTML化したんだけど...宿題は公開するとまずいよね、やはり。2009年度後期はまだ終わってないし。
 とりあえずFormal Methods Forumの勉強会用に使えるかな?

28 February, 2010

[Joke] How do you trap a programmer in the shower?

redditのHow do you trap a programmer in the shower? (list.cs.brown.edu)経由。

[plt-scheme] OT: How do you trap a programmer in the shower?を翻訳してみました。

オチが判らなかった箇所が幾つかあって、それはつまり誤訳してる可能性が高いので、間違っていたら指摘して頂けると嬉しいです。

----

ある生徒が教室で1つ目のジョークを言い、この類にはもっと色々考えられる様な気がした。これは最初の試みなので批判や追加を歓迎する。代名詞の性別についてはご容赦を。男性と女性を入れ替えたりすべきかどうか自信が無いし、彼/彼女と書くのは変だし、複数形で書くと複数人でシャワーを浴びている様なニュアンスがでてしまって本意じゃないし。:-)

Todd

Schemeプログラマをシャワー室に閉じ込めるには?
シャンプーを一瓶渡せ。(Hand him a bottle of shampoo.)

Visual Basicプログラマをシャワー室に閉じ込めるには?
カーテンをオープンするウィジェットを隠せ。

BASICプログラマをシャワー室に閉じ込めるには?
彼のCommodore-64をタイルに固定する為にシリコン接着剤を使え。

アセンブラプログラマをシャワー室に閉じ込めるには?
えーと、まず彼はシャワーをビルドしないと。

Javaプログラマをシャワー室に閉じ込めるには?
どこかにexitShower()メソッドがあると彼を説得し、ドキュメント全体を精査し終わるまで笑ってやれ。

Pythonプログラマをシャワー室に閉じ込めるには?
Guidoがそこにいることを望んでいると彼に伝えろ。

Cプログラマをシャワー室に閉じ込めるには?
そんなことをするな。彼は風呂桶をオーバーフローさせて君の家のコントロールを奪うだろう。

----

以下はredditで追加された物を幾つかピックアップして紹介。

C#プログラマをシャワー室に閉じ込めるには?
> インテリセンスをオフにしろ。

PHPプログラマをシャワー室に閉じ込めるには?
1. その頃、PHPプログラマは芝生に裸で立ち、庭用ホースと驚く程沢山のアタッチメントを手にして、どうして他の全ての人がシャワーの方が良いと言っているのか理解出来ない。
2. PHPプログラマをシャワーの外に出そうとしても出来ない。全ての現実の仕事はシャワーの中でだけ起き、外でのことは単にアカデミックでエリートぶった連中のすることだと主張するだろう。
3. 必要ない。ドキュメントをチェックしないと呼ぶべきメソッドがopen_door(), door_open(), openDoor(), doorOpen() のどれなのか思い出せないから。

LISPプログラマをシャワー室に閉じ込めるには?
1. 冗談を。LISPプログラマはシャワーを浴びない。(don't _take_ showers : 副作用が無いという話?)
2. ")"キーを奪え。

Rubyプログラマをシャワー室に閉じ込めるには?
彼が中に入ったらCurtain#openメソッドにモンキーパッチを当てろ。追加で一つ引数を取る様にして、その引数が何なのかドキュメントに書いておかない。
> 彼は単に引数が不要になるモンキーパッチを当てて戻すだけだが、そのパッチは一回以上使うとsegfaultする様な代物だ。

Prologプログラマをシャワー室に閉じ込めるには?
1. Yes (ってオチが良く解らん)
2. inShower(Programmer) :- inShower(Programmer).

Haskellプログラマをシャワー室に閉じ込めるには?
1. < 圏論に関する馬鹿馬鹿しい程に長い小論を挿入せよ >
2. 何もする必要が無い。シャワーの水で洗って純粋になったならば、シャワーから出るには出力が必要だと気付くから。
3. 必要ない。彼は怠惰(lazy)なので外に出ない。

JavaScriptプログラマをシャワー室に閉じ込めるには?
1. 気にする必要は無い。同時に2つのシャワーは動作しないので、それを修正しようと時間を費やすから。
2. IE6のロゴをシャワーカーテンに張ってデバッグする様に頼め。

Erlangプログラマをシャワー室に閉じ込めるには?
出来ない。彼は自分自身をクローンして、別々のシャワーで全てのクローンに身体の別々の部位を洗わせる。もし閉じ込められたら単にそれを殺すだけだ。

C++プログラマをシャワー室に閉じ込めるには?
C++プログラマはシャワーをvoidポインタにキャストして、ドアの存在を消す。
> 「これは正しいやり方ではないがしかし...」という注釈を付けるのを忘れてるよ。

Perlプログラマをシャワー室に閉じ込めるには?
1. 彼はどうやったら自分のシャワーが動くのか思い出せない。
2. 彼の脱出用の針金の余りで、閉じたシャワーカーテンとシャワーの向きを固定しているダクトテープとをくっ付けておく。

Objective-Cプログラマをシャワー室に閉じ込めるには?
[[NSShower standardShower] addPerson:[NSObjectiveCProgrammer programmerWithRSI]];
SEL removeSelector = @selector(removePerson:);
Method removeMethod = class_getInstanceMethod([NSShower class], removeSelector);
removeMethod->method_imp = voidMethod;

14 February, 2010

[Coq][FM] Formal Methods Forum #1

 2010/02/08に第1回FormalMethods勉強会というのがありました。
 形式手法って、企業内の勉強会とか、あるいは有料の研修コースはあるのだけど (i.e. ビジネスになる、ということなんだろう)、無料のIT勉強会はほとんどありませんでした。
 「形式手法の勉強会欲しいよね〜」という話から勉強会を始めることになり、まず第1回は各自の持ちネタを持ち寄る感じで開催されました。
 私はCoqとWhy (INRIAで作っているプログラムの検証用ツール) の話をしました。発表資料をSlideshareに上げたので興味のある方はどうぞ。

 Coqに限らず、形式仕様記述 (B, Zとか)、あるいは各種モデル検査 (SPIN, Alloyとか)、Lightweight Formal Method (VDMとか) なんでもありの勉強会なので興味のある方はどうぞ。
 開催場所は新宿の豆蔵オフィスを利用させて頂く事が多いのではないかと思うが、Coqという意味では一度名古屋遠征したいなぁ。

Formal Methods Forum : 勉強会のGoogle group
fm-forum @ ウィキ : 勉強会のWiki

11 February, 2010

[Scala] Articles about Scala 2.8 on ITpro

 Scala 2.8に関する紹介記事をITproに書きました。ほぼ年末年始休暇を費やして書き、2010年の1,2月に分けて掲載。

第15回 Scala 2.8の新機能 (1)
第16回 Scala 2.8の新機能 (2) --- コレクションライブラリの再実装

 記事を書く機会を与えてくださった羽生田さんに感謝を。

31 January, 2010

[Scala] Hindley-Milner Type Inference

Hindley-Milnerの型推論を理解したいと思い、とりあえずScala by Exampleに載っているコードにデバッグプリントを大量に挿入して動かしてみました。
動かした結果を、将来の自分の為にメモしたものがHM.pdf、
動かしたソースはHM.scalaにあります。

アルゴリズム自体はHindley Milner Type Inference Algorithm (PS file)とか簡潔に書かれている。が、こうやってデバッグプリントを挟んで動かしてみないとなかなか判らない...。

17 December, 2009

[Scala] Talk on scala-be's "Step by Step Scala" on 12/22

 東京のScala勉強会であるscale-beの、12/22開催のStep by Step Scala [vol.06]@scala-be (ATNDへのリンク)で講師をすることになりました。
 今回は主として14章の「表明と単体テスト」のScalaCheckの使い方を中心に話をしようと思っています。ScalaCheckは「満たすべき性質を記述する」ことでテストケースを自動生成してくれる面白い単体テストツールですが、従来のxUnit系テストツールとはちょっと使い方が違うというのもあって、使い方を学ぶには良いのではないかと思います。
 参加者希望者は上記のATNDのリンクから申し込みをしていただければと思います。
 今回は日程が12/22と多くの忘年会と重なる日で、割と参加者は少なそうでちょっと残念。

29 November, 2009

[Scala] Scala Style Guide

Scala Style Guide (PDF)

Scalaのコーディングスタイルガイド。

[scala] Proposed Style Guideで議論されていて、Oderskyもコメントしてる。これが叩き台になって正式版が出たら訳すといいかなぁ。

23 November, 2009

[Scala][Book] Pro Scala: Monadic Design Patterns for the Web

APressからPro Scala: Monadic Design Patterns for the Webという本が出る様です。
今までの職業的プログラマの視野に入っていなかったが、実は実用的にも重要である概念の、
- Monadic design patterns
- Zippers and data type differentiation
- Delimited continuations
を考察するとのこと。

「モナド=デザインパターン」というのはあちこちで書かれていることだし、モナドに限らず圏論的視点で計算を考えようという話は檜山さんのブログで繰り返し出て来るテーマ(11/28開催のセミナーのコパスタを食べる余会はまだ空席があるようですよ)。
Delimited continuationということはScala 2.8対応だし、限定継続を一般向けに紹介した本は今までにあるのかなぁ?なんかそれだけでも価値があるよね。

なんか非常に楽しみな本ですね。

[Event] Recent Days

今週は、Step by Step Scala [vol.04]@scala-beで話をしたり、Haskellナイトを見に行ったり、Scala Hack-a-thon #1に参加したり、でした。

Step-by-Step Scalaは、(少なくとも何人かの方には)Swarmについて興味を持ってもらえた様なので満足。この先の仕事の状況がまだ確定しないのだけど、また機会があればScalaの講師を出来ればなぁ、とか。

Haskellナイトは、本の話がちょっと多過ぎたかなぁとは思うのだけど、出版合わせのイベントだから仕方がないのかなぁ。もうちょっとHaskellの話を聞きたかったかも。

Scala Hackathonは存在を知った時には既に満員になっていて参加を諦めてたんですが、当日朝見たらキャンセルが大量に出たみたいで慌てて家を出た次第。yuroyoroさんの作成したテキストが良く出来ていて、みんな黙々とそれを読んでいたのかな?かつ、質問はtwitterで主として行われていたみたい。懇親会も楽しく参加させて頂きました。

25 October, 2009

[Scala] Talk on scala-be's "Step by Step Scala" in November

東京のScala勉強会にscale-beというのがありまして、隔週の水曜or木曜の夜に新宿の豆蔵で開催しています。
内容はOderskyの「Scalaスケーラブルプログラミング」の章立てに沿って講師がScalaを解説するというもので、読書会の様にテキストに厳格に沿う訳ではありません。
参加者は、

  • 出来れば該当の本を持参して下さい。本の内容を逐一全部話す訳ではないですし。
  • 出来れば最近のScala実行系(Scala 2.7系)がインストールされたノートPCを持参して下さい。簡単な課題とか、実際に自分でインタプリタ上で動作確認したり、などがあるので。同様に、Scala APIドキュメント(JavaDoc)などをインストールしておくと良いです。(なお、会場はコンセントは利用可能ですが、インターネット接続は提供されません。)

あと、勉強会の後に懇親会も行っています。

で、11/4(Wed), 11/19(Thr)の2回は私が講師を担当する事になりました。それぞれ6,7章と8,9章の範囲について話す予定です。11/19の回にはテキストの内容からちょっと離れて最近のScalaの話題という事でSwarmの話をちょっとだけ紹介しようと思ってます。

なお、参加申し込みはATND上で行われ、ATNDへのリンクを含む開催案内は定期的にscala-be上で告知されます。
では、11月に勉強会会場でお会いしましょう。

---
追記:11/21

2回の発表で使用した資料はPDF形式でscala-be Google groupのファイル置き場で公開してます。

21 October, 2009

[Agda] Install Agda on Mac OS

Mac OS 10.5 の上にAgdaをインストールしました。基本的にはAgda Wikiを参考にしたのだけど、そのままではうまく動きませんでした。

  • Xcodeをインストール。DVDからなり、ADCのサイトなりから。最新版は10.6用なので古いのをインストール。
  • Mac Portsのインストール。/opt/local/binにPATHを通す。Mac Portsの準備が済んでいる人もsudo port -v selfupdateする。
  • GHCはportsからインストールすると古くて駄目なので、GHC download pageよりバイナリパッケージを導入。
  • sudo port install hs-cabalして、sudo cabal updateして、~/.cabal/binにPATHを通す。
  • sudo port install darcs
  • sudo cabal install happyして、sudo cabal install alex
  • sudo cabal install Agda-executable
  • cd ~/.cabal/binして、./agda-mode setup
  • emacsは薦められるままにAquamacsを導入する。
  • 下記を~/.emacsに追加。

    (setq load-path (cons "~/.cabal/share/Agda-2.2.4/emacs-mode/" load-path))
    (autoload 'agda2-mode "agda2-mode" "Major mode for Agda2 files" t)
    (unless (assoc "\\.agda" auto-mode-alist)
    (setq auto-mode-alist
    (nconc '(("\\.agda" . agda2-mode)
    ("\\.alfa" . agda2-mode)) auto-mode-alist)))

  • Aquamacsを起動し、~/test.agdaというファイルを作成して、

    module test where

    data Bool : Set where
      true : Bool
      false : Bool

    と書く(true, falseの前のインデント必須。単語や:の前後に空白があるのも確認。)
    C-c C-lで入力内容が処理され、文字が色分けされたら、agda2-modeが動いている。


---
追記: ~/.emacsについて念のため全部掲載。haskell-modeの設定も重要らしいので。

(setq load-path (cons "‾/.cabal/share/Agda-2.2.4/emacs-mode/" load-path))
(autoload 'agda2-mode "agda2-mode" "Major mode for Agda2 files" t)
(unless (assoc "¥¥.agda" auto-mode-alist)
(setq auto-mode-alist
(nconc '(("¥¥.agda" . agda2-mode)
("¥¥.alfa" . agda2-mode)) auto-mode-alist)))
(setq load-path (cons "‾/lib/elisp/haskell" load-path))
(setq auto-mode-alist
(append auto-mode-alist
'(("促促.[hg]s$" . haskell-mode)
("促促.hi$" . haskell-mode)
("促促.l[hg]s$" . literate-haskell-mode))))
(autoload 'haskell-mode "haskell-mode"
"Major mode for editing Haskell scripts." t)
(autoload 'literate-haskell-mode "haskell-mode"
"Major mode for editing literate Haskell scripts." t)
(add-hook 'haskell-mode-hook 'turn-on-haskell-decl-scan)
(add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)
(add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
(add-hook 'haskell-mode-hook 'turn-on-haskell-ghci)

(setq haskell-literate-default 'latex)
(setq haskell-doc-idle-delay 0)


(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))

16 October, 2009

[Agda] Agda Lectures at CVS

産業技術総合研究所の研修コース「 Agda による仕様記述」に参加しました。

以下は私の参加した10月の回の話。もしかしたら11月は10月の参加者の反応を見て内容が変更されるかもしれません。

一言で言うと
Coq, Agda のような定理証明系とか依存型の関数型言語とかに興味のある人が、概要を知るには良い二日半のコース。参加費無料なのもポイントが高い。お薦め。(開催が大阪なんで関西周辺以外の人にはちょっと厳しいが)

もう少し詳しく説明すると
「Agdaの持つ依存型の機能を使って、仕様の制約を型として表現すれば、機械的に検証出来るよね」というところに主眼を置いているみたいです。なので、定理証明の部分は従であり、主眼は仕様を書ける様になろう、ということのようです。
とはいえ、仕様の制約をAgdaで書く以上、書いた関数が仕様を満たしていることを示す必要がある訳ですが...。

が、まぁ当たり前と言えば当たり前なんですが、二日半ではAgdaを使いこなせるようにはならなかった...。まぁJavaだってHaskellだって二日半では使える様にはならない訳で、当然というか仕方が無い。なので、「Agdaがどんなものなのかの概説を聞く」ぐらいに考えていたほうが良いです。
とはいえ、Agdaに関する日本語の資料はほとんど無い現状で、テキストの説明を聞き、判らないところを教わりつつのハンズオン実習がある訳で、価値は大きい。

参加者は結局8名。堅苦しさの無い普通のIT系勉強会のようなフレンドリーな雰囲気で、質問しまくりみたいな。逆に言うと講義内容がまだきっちりと固まっている訳では無い感じです。
にわとり小屋でのプログラミング日記のCoqな今井さんとお近づきになれたのも良かったです。数人で2日目の夜に懇親会したのですが、最初からちゃんと計画して全員(講師の方にも声をかけて)でやれば良かったと反省。
#話に出た紫色の本は、"Handbook of Practical Logic and Automated Reasoning" (John Harrison, Cambridge Univ. Press, 2009) です>今井さん。

とにかく私は楽しかったです。代休消化で関西旅行した価値がありました。参加させて頂き、ありがとうございました>CVSの方々。

参加の前提について
募集要項には「プログラムを書いた経験(言語不問)、またはシステムやソフトウェアの設計に従事したことがあること。」「Emacs で文書を作成編集した経験があること。」と書いてあるんですが...。
うーむ、HaskellとかOCamlとかの類の型付き関数型言語の初歩的な知識があったほうがいいかも。モナドとか別に知らなくてもいいけど、簡単な再帰とかパターンマッチとか使って List の map とか length とかぐらい書けたほうが良いかもしれないです。そうでないと初日の演習問題でいきなり困るかも。あとはまぁ、ペアノの自然数( 3 = succ(succ(succ(zero))) みたいな話 ) も知ってた方が良いかなぁ。
ただ、参加者の関数型言語への習熟度を見て調整していたかも知れないので、11月はどうなるかは判らないです。

25 July, 2009

[Scala] sbt : simple-build-tool (1)

sbt (simple-build-tool) は Scala で書かれたビルドツールです。
これもscalaで書かれたDSL的なツールであり、プロジェクトのビルドの設定などをscalaで記述する事が出来たりします。

試しに使ってみて、使い方が判ったら追記していこうと思います。

★インストール

Setupのページに従い作業します。
私は MacOS ユーザなので Unix の指示に従い作業。

まずsbt-launcher-0.5.1.jar をダウンロードして指示通り~/binに置き、~/bin/sbt ファイルを作り chmod したりします。

% ls ~/bin
sbt sbt-launcher-0.5.1.jar
% cat ~/bin/sbt
java -Xmx256M -jar `dirname $0`/sbt-launcher-0.5.1.jar "$@"
%


★動作確認
空の (*.scala の無い) 作業ディレクトリに hw.scala を作ります。(main メソッドを探して自動で判断する都合上、無関係なソースがあると巧く動作しません。最初それで失敗しました。)

% ls
hw.scala
% cat hw.scala
object Hi { def main(args: Array[String]) { println("Hi!") } }
%


とりあえず下記の様に動きました。


[apple-3:~/work/scala/hw] miyamoto% ~/bin/sbt
Project does not exist, create new project? (y/N/s) : s
:: loading settings :: url = jar:file:/Users/miyamoto/bin/sbt-launcher-0.5.1.jar!/org/apache/ivy/core/settings/ivysettings.xml
:: retrieving :: sbt#boot
confs: [default]
2 artifacts copied, 0 already retrieved (9831kB/149ms)
:: retrieving :: sbt#boot
confs: [default]
3 artifacts copied, 0 already retrieved (3171kB/26ms)
[info] Building project scratch 1.0 using sbt.DefaultProject
[info] with sbt 0.5.1 and Scala 2.7.5
[info] No actions specified, interactive session started. Execute 'help' for more information.
> run
[info]
[info] == compile ==
[info] Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Compiling main sources...
[info] Compilation successful.
[info] Post-analysis: 2 classes.
[info] == compile ==
[info]
[info] == run ==
[info] Running Hi ...
Hi!
[info] == run ==
[success] Successful.
[info]
[info] Total time: 2 s
> quit
[info]
[info] Total session time: 14 s
%


この結果として下記の様にプロジェクトが生成されます。このあたりは Maven とかと同様な感じ。各プロジェクト毎にscala-compiler.jarを持ったりするのは、なんか富豪的だなぁ。

% ls -RCF
hw.scala project/ target/

./project:
boot/ build.properties

./project/boot:
scala-2.7.5/

./project/boot/scala-2.7.5:
lib/ sbt-0.5.1/ update.log

./project/boot/scala-2.7.5/lib:
scala-compiler.jar scala-library.jar

./project/boot/scala-2.7.5/sbt-0.5.1:
ivy-2.0.0.jar jsch-0.1.31.jar sbt_2.7.5-0.5.1.jar

./target:
analysis/ classes/

./target/analysis:
applications external hashes
dependencies generated_files tests

./target/classes:
Hi$.class Hi.class
%

31 May, 2009

[Scala] S-99: Ninety-Nine Scala Problems (P01-28)

S-99: Ninety-Nine Scala ProblemsのList編(P01-P28)の抄訳です。

  • アスタリスクの数は難易度です。

  • 効率も大事ですが、エレガントな回答を求めます。可能ならばより簡潔で、計算量が少なく、末尾再帰になっている回答を作りましょう。

  • Scalaの組み込み関数を使ってもOKです。が、使わないほうが勉強になります。

  • 答えが知りたければ、元の英語文書の各問題のリンクをクリックして下さい。



P01 (*) リストの最後の要素を求めよ。

scala> last(List(1, 1, 2, 3, 5, 8))
res0: Int = 8


P02 (*) リストの最後から二番目の要素を求めよ。

scala> penultimate(List(1, 1, 2, 3, 5, 8))
res0: Int = 5


P03 (*) リストのn番目の要素を求めよ。但しリストの最初の要素は0番目とする。

scala> nth(2, List(1, 1, 2, 3, 5, 8))
res0: Int = 2


P04 (*) リストの要素の数を求めよ。

scala> length(List(1, 1, 2, 3, 5, 8))
res0: Int = 6


P05 (*) リストを逆順にせよ。

scala> reverse(List(1, 1, 2, 3, 5, 8))
res0: List[Int] = List(8, 5, 3, 2, 1, 1)


P06 (*) リストが回文になっているか調べよ。

scala> isPalindrome(List(1, 2, 3, 2, 1))
res0: Boolean = true


P07 (**) ネストされたリスト構造を平坦化せよ。

scala> flatten(List(List(1, 1), 2, List(3, List(5, 8))))
res0: List[Any] = List(1, 1, 2, 3, 5, 8)


P08 (**) リスト要素の連続した重複物を除去せよ。もしリストの要素で繰り返し要素が含まれていたならば要素一つに置き換えよ。要素の順序は変えてはならない。

scala> compress(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[Symbol] = List('a, 'b, 'c, 'a, 'd, 'e)


P09 (**) 連続した重複物を子リストに纏めよ。もしリストの要素が繰り返し要素ならば、別々の子リストに分割せよ。

scala> pack(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[List[Symbol]] = List(List('a, 'a, 'a, 'a), List('b), List('c, 'c), List('a, 'a), List('d), List('e, 'e, 'e, 'e))


P10 (*) リストをランレングス・エンコードせよ。P09の結果を用いていわゆるランレングス・エンコーディングによるデータ圧縮法を実装せよ。連続した重複要素はタプル(N,E)にエンコードされる。但しNは要素Eの重複数。

scala> encode(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[(Int, Symbol)] = List((4,'a), (1,'b), (2,'c), (2,'a), (1,'d), (4,'e))


P11 (*) 修正ランレングス・エンコーディング。P10の結果を修正し、もし要素に重複が無ければ単に要素を結果にコピーせよ。重複している要素だけを(N,E)の形に変換せよ。

scala> encodeModified(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[Any] = List((4,'a), 'b, (2,'c), (2,'a), 'd, (4,'e))


P12 (**) ランレングス・エンコードされたリストをデコードせよ。P10の仕様で生成されたランレングス・エンコードされたリストを元の圧縮されていないものに戻せ。

scala> decode(List((4, 'a), (1, 'b), (2, 'c), (2, 'a), (1, 'd), (4, 'e)))
res0: List[Symbol] = List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)


P13 (**) リストのランレングス・エンコーディング(直接解法)。いわゆるランレングス・エンコーディングを直接実装せよ。すなわち(P09のpackの様な)自分で書いた他のメソッドを使ってはならない。直接書く事。

scala> encodeDirect(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
res0: List[(Int, Symbol)] = List((4,'a), (1,'b), (2,'c), (2,'a), (1,'d), (4,'e))


P14 (*) リスト要素を重複させよ。

scala> duplicate(List('a, 'b, 'c, 'c, 'd))
res0: List[Symbol] = List('a, 'a, 'b, 'b, 'c, 'c, 'c, 'c, 'd, 'd)


P15 (**) 指定した個数、リスト要素を重複させよ。

scala> duplicateN(3, List('a, 'b, 'c, 'c, 'd))
res0: List[Symbol] = List('a, 'a, 'a, 'b, 'b, 'b, 'c, 'c, 'c, 'c, 'c, 'c, 'd, 'd, 'd)


P16 (**) 毎N番目の要素を除去せよ。

scala> drop(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k))
res0: List[Symbol] = List('a, 'b, 'd, 'e, 'g, 'h, 'j, 'k)


P17 (*) リストを二つに分割せよ。前半の長さは与えられるものとする。結果はタプルで返す。

scala> split(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k))
res0: (List[Symbol], List[Symbol]) = (List('a, 'b, 'c),List('d, 'e, 'f, 'g, 'h, 'i, 'j, 'k))


P18 (**) リストのスライスを抽出せよ。二つの添字 i と j が与えられたとき、スライスとは元のリストの i 番目の要素を含むが j 番目の要素を含まないリストである。要素は0番目から始まるとする。

scala> slice(3, 7, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k))
res0: List[Symbol] = List('d, 'e, 'f, 'g)


P19 (**) リストの要素をn個左ローテートせよ。

scala> rotate(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k))
res0: List[Symbol] = List('d, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'a, 'b, 'c)

scala> rotate(-2, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k))
res1: List[Symbol] = List('j, 'k, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i)


P20 (*) リストのk番目の要素を除去せよ。除去されたリストと除去した要素をタプルで返せ。要素は0番目から始まるとする。

scala> removeAt(1, List('a, 'b, 'c, 'd))
res0: (List[Symbol], Symbol) = (List('a, 'c, 'd),'b)


P21 (*) リストの指定された場所に要素を追加せよ。

scala> insertAt('new, 1, List('a, 'b, 'c, 'd))
res0: List[Symbol] = List('a, 'new, 'b, 'c, 'd)


P22 (*) 与えられた範囲の整数のリストを作れ。

scala> range(4, 9)
res0: List[Int] = List(4, 5, 6, 7, 8, 9)


P23 (**) リストから指定された数だけ値をランダムに選択せよ。(ヒント:P20を使え)

scala> randomSelect(3, List('a, 'b, 'c, 'd, 'f, 'g, 'h))
res0: List[Symbol] = List('e, 'd, 'a)


P24 (*) ロト:1〜Mからn個の異なるランダムな値を選べ。

scala> lotto(6, 49)
res0: List[Int] = List(23, 1, 17, 33, 21, 37)


P25 (*) 要素のランダムな順列を作成せよ。(ヒント:P23を使え)

scala> randomPermute(List('a, 'b, 'c, 'd, 'e, 'f))
res0: List[Symbol] = List('b, 'a, 'd, 'c, 'e, 'f)


P26 (**) n要素数のリストからk個の異なるオブジェクトを取り出す組み合わせを生成せよ。12人から3人の委員会を作る方法は何通りだろうか?答えは C(12,3)=220通り(C(n,k)はよく知られた二項係数)である。数学者にとってはこれで十分であるが、我々は本当に全ての解を生成したい。

scala> combinations(3, List('a, 'b, 'c, 'd, 'e, 'f))
res0: List[List[Symbol]] = List(List('a, 'b, 'c), List('a, 'b, 'd), List('a, 'b, 'e), ...


P27 (**) 集合の要素を、互いに素な部分集合に纏めよ。
a) 9人の人をそれぞれ2,3,4人の3グループに纏める方法は何通りか?全ての組み合わせを生成する関数を書け。

scala> group3(List("Aldo", "Beat", "Carla", "David", "Evi", "Flip", "Gary", "Hugo", "Ida"))
res0: List[List[List[String]]] = List(List(List(Aldo, Beat), List(Carla, David, Evi), List(Flip, Gary, Hugo, Ida)), ...

b) 上の問題を一般化してグループの大きさのリストを与えるとグループのリストを与える様にせよ。グループメンバーの順列は求めていない、すなわち((Aldo,Beat),...)は((Beat,Aldo),...)と同じ解である。しかし、((Aldo,Beat),(Carla,David),...)は((Carla,David),(Aldo,Beat),...)と異なる解である。

scala> group(List(2, 2, 5), List("Aldo", "Beat", "Carla", "David", "Evi", "Flip", "Gary", "Hugo", "Ida"))
res0: List[List[List[String]]] = List(List(List(Aldo, Beat), List(Carla, David), List(Evi, Flip, Gary, Hugo, Ida)), ...

この組み合わせ問題に関して知りたければ離散数学の良い本で「多項係数」について調べよ。
P28 (**) リストのリストを長さでソートせよ。
a) 要素がリストであるリストを考える。そのリストの要素を長さでソートする、すなわち短いリストを前に、長いリストを後にする。

scala> lsort(List(List('a, 'b, 'c), List('d, 'e), List('f, 'g, 'h), List('d, 'e), List('i, 'j, 'k, 'l), List('m, 'n), List('o)))
res0: List[List[Symbol]] = List(List('o), List('d, 'e), List('d, 'e), List('m, 'n), List('a, 'b, 'c), List('f, 'g, 'h), List('i, 'j, 'k, 'l))

b) 次に同様にリストを長さでソートするが、今回は長さの頻度でソートする。すなわち稀な長さのものを前に、頻度の高い長さのものを後ろにする。例の場合、長さ4と1のリストはただ1度しか現れない。3番目と4番目は長さ3のリスト2つである。最後に3つの最も頻度の高い長さ2のリストが現れる。

scala> lsortFreq(List(List('a, 'b, 'c), List('d, 'e), List('f, 'g, 'h), List('d, 'e), List('i, 'j, 'k, 'l), List('m, 'n), List('o)))
res1: List[List[Symbol]] = List(List('i, 'j, 'k, 'l), List('o), List('a, 'b, 'c), List('f, 'g, 'h), List('d, 'e), List('d, 'e), List('m, 'n))

22 May, 2009

[Scala] Sudoku in Scala

Scalaユーザ会5/22(金)19:00-21:00@新宿三井ビル3で話をする予定の「Scalaで数独を解く」話です。

ScalaSudoku.pdf : プレゼン資料
Sudoku.scala : ソースコード

20 May, 2009

[Joke] Translation of "A Brief, Incomplete, and Mostly Wrong History of Programming Languages"

A Brief, Incomplete, and Mostly Wrong History of Programming Languagesの翻訳です。面白かったので翻訳してみました。

「簡潔で不完全でほとんど間違っているプログラミング言語の歴史」

1801 - Joseph Marie Jacquardが、織機にパンチカードで命令することで、タペストリーに「hello, world」を織り込んだ。(しかし)末尾再帰やコンカレンシーの欠如、あるいは適切に大文字が使用されていないため、当時のRedditerたちは感銘を覚え無かった。

1842 - Ada Lovelaceが最初のプログラムを書いた。その過程に於いて、コードを走らせる実際のコンピュータを持っていないという些細な困難に妨げられた。後のエンタープライズアーキテクトたちは、UMLでプログラムする為に、彼女のテクニックを再習得した。

1936 - Alan Turingが、(将来に亘る)全てのプログラミング言語を発明した。しかし彼がその特許をとる前に、英国情報部は彼を007にするために強制徴募した。

1936 - Alonzo Churchもまた、(将来に亘る)全てのプログラミング言語をさらにうまく発明した。彼のλ算法はC言語に十分似ていない為に無視された。この批判は、当時まだCが発明されていないという事実にも関わらず生じた。

1940年代 - 結線とスイッチによって様々な「コンピュータ」が「プログラム」された。「タブ vs 空白」の論戦を避けるために、技術者たちはこの方式を採用した。

1957 - John BackusとIBMがFORTRANを作った。IBMにもFORTRANにも面白いところは何も無い。青いネクタイを着用せずFORTRANを書くのは文法エラーである。

1958 - John McCarthyとPaul GrahamがLISPを発明する。戦後の戦略的括弧備蓄の枯渇による高コストの為、LISPは決してポピュラーにはならなかった[1]。ポピュラーでは無いにも関わらず、LISP (今では "Lisp" あるいは時には "Arc") は「再帰と他人を見下すことなどの重要なアルゴリズム技法」に於ける影響度の高い言語であり続けている。

1959 - L. Ron Hubbardとの賭けに負けた後に、Grace Hopperを含む何人かのサディスト達が「大文字化された定型文志向言語」 (Capitalization Of Boilerplate Oriented Language = COBOL) を発明する。後に、Hooper提督のCOBOLの業績に対する見当外れで性差別的な報復として、Rubyコンファレンスでは嫌女性的題材が取り上げられる。

1964 - John KemenyとThomas Kurtzが、非計算機科学者の為の非構造化プログラミング言語であるBASICを作った。

1965 - KemenyとKurtzはGO TO 1964.

1970 - Guy SteeleとGerald SussmanがSchemeを作った。彼らの著作は「Lambda the Ultimate」の一連の論文をもたらし、「究極の台所用品ラムダ」にその頂点を迎えた。この論文はロングランの基礎となったが、深夜のインフォマーシャルとしては究極的に失敗であった。Javaがラムダを持たないことによってラムダをポピュラーにするまでラムダは比較的目立たないところへと左遷された。

1970 - Niklaus Wirthが手続き型言語のPascalを作った。Pascalは直ちに批難されたが、"x := x + y"という構文を、より親しみやすいC的な"x = x + y"の代わりに使用したためであった。この批判は、当時まだCが発明されていないという事実にも関わらず生じた。

1972 - Dennis Ritchieは前後を同時に撃つことの出来る強力な銃を発明した。発明のもたらした多くの死者及び障害者に満足することなく、彼はCとUnixを発明した。

1972 - Alain Colmerauerは論理型言語Prologをデザインした。彼の目標は二歳児の知性を持った言語を作ることであった。全てのクエリに「No」と答えるPrologセッションを示すことによって、目標に達したことを証明した。

1973 - Robin MilnerはM&M型理論に基づく言語のMLを作った。MLの子供として形式仕様意味論を持つSMLが生まれた。形式意味論の形式意味論を質問されてMilnerの頭は爆発した。ML一家の他の良く知られた言語にはOCaml, F#, Visual Basicがある。

1980 - Alan kayはSmalltalkを作り、用語「オブジェクト指向」を発明した。その意味を聞かれると彼は「Smalltalkのプログラムは単にオブジェクトである」と答えた。オブジェクトは何から作られるのかを聞かれると彼は「オブジェクトだ」と答えた。再度質問されると彼は云った。「だからさ、下の下まで全部オブジェクトなんだってば。亀にたどり着くまでは。」

1983 - Bjarne Stroustrupは耳にしたもの全てをCにねじ止めすることでC++を作った。その結果、言語は非常に複雑になり、プログラムをSkynet人工知能でコンパイルするために未来へ送らねばならなかった。ビルド時間は犠牲となった。Skynetがサービスを提供し続けた動機は依然としてはっきりしないが、未来からの広報担当はオーストリア訛りで単調に「気にするようなことは何も無い、ベイビー」と云った。Skynetはバッファーオーバーランを飾り立てたものに過ぎないという推測もある。

1986 - Brad CoxとTom LoveがObjective-Cを作り、アナウンスした。「この言語はCのメモリ安全性とSmalltalkの素晴らしい実行速度が結びついたものです。」現代の歴史家たちは二人が失読症であったと疑っている。

1987 - Larry Wallが眠気を催し、キーボードに額をぶつけた。目を覚ましたとき、Larry Wallのモニターの上の文字列はランダムなのではなく、神が預言者Larry Wallにデザインすることを欲しているプログラミング言語のサンプルプログラムだと悟った。Perlが生まれた。

1990 - Simon Peyton-Jones, Paul Hudak, Philip Wadler, Ashton Kutcher そして「動物の倫理的扱いを求める人々の会」からなる委員会は、純粋非正格関数型言語Haskell を作った。副作用を制御するためモナドを使用する複雑さの為、Haskellは抵抗を受けた。Wadler は批判を和らげるために説明した。「モナドは自己準同系ファンクタの圏のモノイドなんだ。何か問題が?」

1991 - オランダ人プログラマのGuido van Rossumが謎の手術の為にアルゼンチンへと旅行した。頭部に大きな傷を負って帰国し、Pythonを発明し、多数の賛同者によって終身独裁者に任じられ、世界に対して「あることをするのにひとつしかやり方がない」と報じた。ポーランドは神経質になっている。

1995 - 「Mad Matz」こと、まつもとゆきひろは、漠然とした特定されない終末を避けるためにRubyを作ったが、その終末においてはオーストラリアはモヒカン戦士とティナ=ターナーが支配する砂漠となる。その言語は後にRuby on Railsと、真の発明者David Heinemeier Hanssonによって改名された。[まつもとがRubyと呼ばれる言語を発明した云々は実際には起きておらず、この記事の次の改訂時に削除されるべきだ - DHH].

1995 - Brendan Eichはプログラミング言語設計で起きた全ての失敗について読み、自分でも幾つか発明し、LiveScriptを作成した。後にその言語は、Javaの人気に肖る為、JavaScriptと改名された。後になっても、皮膚病の人気に肖る為にECMAScriptと改名された。

1996 - James GoslingはJavaを発明した。Javaは比較的冗長で、ガベージコレクションをし、クラスベースで、静的型付けで、シングルディスパッチで、単一実装継承と複数インタフェース継承のオブジェクト指向な言語である。SunはJavaの新規性を宣伝した。

2001 - Anders HejlsbergがC#を発明した。C#は比較的冗長で、ガベージコレクションをし、クラスベースで、静的型付けで、シングルディスパッチで、単一実装継承と複数インタフェース継承のオブジェクト指向の言語である。MicrosoftはC#の新規性を宣伝した。

2003 - 酔っ払ったMartin Oderskyは誰かのピーナツバターが別の人のチョコレートにくっつくというReeseのピーナツバターカップのCMを見て着想を得た。彼はオブジェクト指向と関数型言語の作り上げたものを統合する言語Scalaを作った。これを見た両派閥とも激怒し、それぞれ直ちに聖戦を宣告した。

Footnotes

1. 計算機科学にとっては幸運なことに、中括弧と山括弧の供給は潤沢であった。
2. Catch as catch can - Verity Stob

03 May, 2009

[Scala] Jersey with Scala + Jetty

Jersey は JAX-RS (Java API for RESTful Web Service)のreference実装です。
これをScalaで動かしてみます。

1. Libraries : Jetty 6.1.17. Jersey 1.0.3 を使用しました。下記をEclipseプロジェクトのreference librariesに登録

Jetty : jetty-XX.jar, jetty-util-XX.jar, servlet-api-XX.jar
Jersey : jsr311-api-XX.jar, jersey-core-XX.jar, jersey-server-XX.jar, asm-XX.jar

2. test.jersey.JerseyTest.scala : メインルーチンです

package test.jersey

import javax.servlet.ServletException
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

import org.mortbay.jetty.Server
import org.mortbay.jetty.nio.SelectChannelConnector
import org.mortbay.jetty.servlet.Context
import org.mortbay.jetty.servlet.ServletHolder

import com.sun.jersey.spi.container.servlet.ServletContainer

object JerseyTest {
def main(args: Array[String]) {
val server = new Server(8080)
val connector = new SelectChannelConnector()
server.addConnector(connector)

val holder:ServletHolder = new ServletHolder(classOf[ServletContainer])
holder.setInitParameter(
"com.sun.jersey.config.property.resourceConfigClass",
"com.sun.jersey.api.core.PackagesResourceConfig")
holder.setInitParameter(
"com.sun.jersey.config.property.packages",
"test.jersey.resource")
// URLをクラスにマッピングする為のpackage名

val context = new Context(server, "/", Context.SESSIONS)
context.addServlet(holder, "/*")

server.start()
server.join()
}
}


3. test.jersey.resource : /helloworld に対応するリソース

package test.jersey.resource

import javax.ws.rs.GET
import javax.ws.rs.Produces
import javax.ws.rs.Path

@Path("/helloworld")
class HelloWorldResource {
@GET
@Produces(Array("text/plain"))
def getMessage:String = "Hello, World"
}


4. テスト
Eclipseでtest.jersey.JerseyTest をアプリケーションとして実行させます。

2009-05-03 22:56:18.064::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2009-05-03 22:56:18.123::INFO: jetty-6.1.17
2009-05-03 22:56:18.205::INFO: Started SocketConnector@0.0.0.0:8080
2009-05-03 22:56:18.226::INFO: Started SelectChannelConnector@0.0.0.0:55736

次いでアクセス

% telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /helloworld HTTP/1.0

HTTP/1.1 200 OK
Content-Type: text/plain
Server: Jetty(6.1.17)

Hello, WorldConnection closed by foreign host.
%



2009/05/03 22:57:08 com.sun.jersey.api.core.PackagesResourceConfig init
情報: Scanning for root resource and provider classes in the packages:
test.jersey.resource
2009/05/03 22:57:08 com.sun.jersey.api.core.PackagesResourceConfig init
情報: Root resource classes found:
class test.jersey.resource.HelloWorldResource
2009/05/03 22:57:08 com.sun.jersey.api.core.PackagesResourceConfig init
情報: Provider classes found:

[Scala] Scala Servlet with Jetty6

Jetty + ScalaでServletを書いてみました。

1. Projectの作成
Eclipseで普通にScala Projectを作ります。
ScalaServletという名前のScala Projectを作りました。

2. Jetty6 の入手
JettyのサイトからJetty6を入手します。私がダウンロードしたのはJetty 6.1.17でした。
zipを解凍し、jetty-6.1.17.jar, jetty-util-6.1.17.jar, servlet-api-2.5-20081211.jar をプロジェクトにimportします。

3. ソースを書く
パッケージtest.jettyを作って、下記の様なJettyTest.scalaというファイルを作成します

package test.jetty

import javax.servlet.ServletException
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

import org.mortbay.jetty.Server
import org.mortbay.jetty.nio.SelectChannelConnector
import org.mortbay.jetty.servlet.ServletHandler

object JettyTest {

def main(args: Array[String]) {
val server = new Server(8080)
val connector = new SelectChannelConnector()
server.addConnector(connector)

val handler = new ServletHandler()
handler.addServletWithMapping(HelloServlet.getClass, "/")
server.addHandler(handler)

server.start()
server.join()
}
}

object HelloServlet extends HttpServlet {
override def doGet(req:HttpServletRequest, resp:HttpServletResponse) {
val out = resp.getWriter
resp.setContentType("text/html")
out.println("<html><body>Hello, World!</body></html>")
}
}


4.サーバ起動
上記のJettyTest.scalaを普通にScala Applicationとして起動します。
コンソールに下記の様に表示されます。

2009-05-03 02:20:29.316::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2009-05-03 02:20:29.358::INFO: jetty-6.1.17
2009-05-03 02:20:29.392::INFO: Started SocketConnector@0.0.0.0:8080
2009-05-03 02:20:29.413::INFO: Started SelectChannelConnector@0.0.0.0:52134


5.アクセス
ブラウザからhttp://localhost:8080/で確認してもOKですがコンソールから確認。

% telnet localhost 8080
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying fe80::1...
telnet: connect to address fe80::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.1 200 OK
Content-Type: text/html; charset=iso-8859-1
Content-Length: 40
Server: Jetty(6.1.17)

<html><body>Hello, World!</body></html>
Connection closed by foreign host.
%

18 April, 2009

[Event] Type-Level Programming Meeting

型レベルプログラミングの会

定員がすぐいっぱいになってしまって会場参加は出来ず、自宅からustreamで拝聴。
発表された皆さん、どれも興味深い話でした。ありがとうございます。


  • Scala : Scalaの型プログラミングの話は前にも聞かせて頂いたような気がするけど、更に話題が広がっていたような。

  • C++ : 定番ではありながら、私はC++は経験が少ないので興味深く聞けました。

  • Haskell : フォロー出来ない話も多かったですが勉強になりました。勉強すべき事、多いなぁ。

  • D : 型プログラミングの為のような言語ですね。興味深い。

  • 依存型プログラミング : プレゼン資料が大変面白いというか芸達者というか。April Foolの予告のGirardの逆理の話では無かった。

  • G'Caml : 存在を始めて知りました。OCaml関係も色々あるなぁ。



この手の勉強会に参加すると、今まで知らなかったことがいっぱいあると判り、もっと勉強しなくちゃ、と思う。
日々の仕事は非技術的な作業ばかりが増えているので、知的好奇心の刺激の為にもこの手の勉強会に積極的に参加しないと。

05 April, 2009

[Coq] Coq'Art Chap.2

Interactive Theorem Proving and Program Development; Coq'Art: The Calculus of Inductive Constructions の Chapter 2 です。

この章では概ね次の4つが語られているように見えます。

(1) Coq のコマンド

下記の様なコマンドがあります。

★Require Import library.
ライブラリをロードして environment に追加する。
libraryとしては、Arith, ZArith, Boolなどがある。

★Open Scope scope名.
Scope は notation を interprete する規則。例えばZ_scopeを指定すると各種演算子記号が Z の演算のものと判断される。

★Print Scope scope名.
Scopeで定義された notation の一覧を得る。

★Check term.
termのtypeを表示させる。

★Parameter t:A.
t:Aを environment に追加する。environmentはglobalなもの。

★Section id.
     ...

 End id.
localなblockを作る。

★Variable t:A.
t:Aを context に追加する。contextはlocal。なので、Section...Endの範囲内で有効。

(2) Inference Rules

様々な推論規則があります。


(x,A)∈E∪Γ
Var ------------ x:identifier
E,Γ |- x:A

E,Γ|- e_1:A->B E,Γ|- e_2:A
App ------------------------------ 関数適用
E,Γ|- e_1 e_2:B

E,Γ|- e:A_1 -> A_2 -> ... -> A_n -> B E,Γ|- e_i:A_i (i=1,...,n)
App* -------------------------------------------------------------------
E,Γ|- e e_1 ... e_n:B

E,Γ|- t_1:A E,Γ::(v:=t_1) |- t_2:B
Lam -------------------------------------- λ式
E,Γ|- fun v:A => e:A->B

E,Γ|- t_1:A E,Γ::(v:=t_1) |- t_2:B
Let-in -------------------------------------- let
E,Γ|- let v:=t_1 in t_2:B

E,Γ|- A:Set E,Γ|- B:Set
Prod-Set --------------------------
E,Γ|- A->B:Set

E,Γ|- t:A E,Γ|- A ≦_{beta delta iota zeta} B
Conv --------------------------
E,Γ|- t:B


(3) Eval

★cvb : call-by-value (cbvでなくlazyで遅延評価)

★delta-reduction : t ==> t{v/t'}。tのidentifier vをその定義t'で置換。

★beta-reduction : (fun v:T => e_1) e2 ==> e_1{v/e_2}。λ式を簡約。

★zeta-reduction : let v := e_1 in e_2 ==> e_2{v/e_1}。letを簡約。

★iota-reduction : 帰納的な処理で詳しくはChap.6でということで後回し。nat,Zは帰納的な定義なので計算する時はiota-reductionが必須。

★compute = cvb iota beta zeta delta の略

★Eval compute in (....). : ...を実際に計算する。

Reductionに関する性質

  • strong normalization : reductionはfinite
  • confluence : t |> t_1, t |> t_2 ならば、あるt_3があって、t_1 |> t_3 かつ t_2 |> t_3。


(4) Universe

typeもtermだとすると、typeのtypeがあるはず。それをsortという。
CoqではSetというデフォルトのsortが定義されている。
更にその上の無限の階層まで考えるようだが、CoqではType(i)は全部ひっくるめてTypeにしてしまう様子。

Level 0 : 具体的な項とか関数。O, S, trinomial,...
Level 1 : データの型。nat, nat->nat, ...
Level 2 : ソート。Set
Level 3 : Type(0)
:

29 March, 2009

[Coq] Coq'Art Reading in Tokyo ?

ところで東京近辺でSpringerのCoq本Interactive Theorem Proving and Program Development. Coq'Art: The Calculus of Inductive Constructionsに興味のある人っています?
何人かいるようならば読書会でもとか思うのですが。なお、私は素人で型理論の専門家とかではありません。なので他人に教える程の知識は無く、一緒に勉強する感じになってしまいますが。
逆に既に本を読んでいるとかで、私が参加しても構わないグループとかありましたら声をかけて下さい。

[Coq] Coq'Art Chap.1

Interactive Theorem Proving and Program Development; Coq'Art: The Calculus of Inductive Constructionsを読み始める事にしました。

Chap.1ではinsertion sortを例にしてCoqの解説が行われています。そこで、Appendixのinsertion sortのコードをCoqIdeに入力してみました。実際に証明が自分で出来る様になるのは将来の課題として、とりあえずCoqでの開発の流れを追ってみます。
なお証明自体はinsertionv8.vにあります。

まず、list Zがソートされた状態を定義するsorted : list Z -> Propを定義します。こんな感じです。

Inductive sorted : list Z -> Prop :=
| sorted0 : sorted nil
| sorted1 : forall z:Z, sorted (z :: nil)
| sorted2 : forall (z1 z2:Z) (l:list Z),
z1 <= z2 -> sorted (z2 :: l) -> sorted (z1 :: z2 :: l).
sortedは定義なのでこれ自体は証明の対象ではありません。

これを用いて、

Theorem sorted_inv :
forall (z:Z) (l:list Z), sorted (z :: l) -> sorted l.
を証明します。

次いで、並べ替えであることを示すequivを定義します。まず、リストのなかの出現数を示す

Fixpoint nb_occ (z:Z) (l:list Z) {struct l} : nat :=
match l with
| nil => 0%nat
| (z' :: l') =>
match Z_eq_dec z z' with
| left _ => S (nb_occ z l')
| right _ => nb_occ z l'
end
end.
を定義します。このnb_occは定義なので証明しません。

これを使って、

Definition equiv (l l':list Z) := forall z:Z, nb_occ z l = nb_occ z l'
を定義します。これ自体も証明しません。

nb_occ, equivの定義から、下記の補題を証明します。

Lemma equiv_refl :
forall l:list Z, equiv l l.
Lemma equiv_sym :
forall l l':list Z, equiv l l' -> equiv l' l.
Lemma equiv_trans :
forall l l' l'':list Z, equiv l l' -> equiv l' l'' -> equiv l l''.
Lemma equiv_cons :
forall (z:Z) (l l':list Z), equiv l l' -> equiv (z :: l) (z :: l').
Lemma equiv_perm :
forall (a b:Z) (l l':list Z), equiv l l' -> equiv (a :: b :: l) (b :: a :: l').


次いで、実際にinsertion sortを行う関数aux : Z -> list Z -> list Zを定義します。

Fixpoint aux (z:Z) (l:list Z) {struct l} : list Z :=
match l with
| nil => z :: nil
| cons a l' =>
match Z_le_gt_dec z a with
| left _ => z :: a :: l'
| right _ => a :: (aux z l')
end
end.


auxがinsertion sortの性質を持っている事を示す補題、つまりauxの結果がequivであること、sortedであることを証明します。

Lemma aux_equiv :
forall (l:list Z) (x:Z), equiv (x :: l) (aux x l).
Lemma aux_sorted :
forall (l:list Z) (x:Z), sorted l -> sorted (aux x l).


最後に、sort:list Z -> list Zを定義というか、sortされた出力の存在を証明します。

Definition sort : forall l:list Z, {l' : list Z | equiv l l' /\ sorted l'}.

証明を追いきれてないのですが、どうもexists (aux a l')の様に、実例を示して存在を証明するみたいにauxを使っているようです。

----

sorted, nb_occ, equivは定義なので証明の対象ではありません。
aux, sortは成果物として得る、証明済みのプログラムです。sortを満たす具体的なl'の値としてauxを使った式を与えます。その値がl'の条件を満たす事はaux_equiv, aux_sortedから保証されます。

一般的に

  • 仕様を表す様な述語(X -> Prop)を書く。仕様に関する補題も証明しておく。
  • 解を与える様な関数を書く。
  • その関数の出力が仕様を満たす事を証明する。
とかすれば良い様に見えます。

----

ある程度、この本を読み終わった後で、またこのプログラムを振り返ってみたいと思います。

27 December, 2008

[Haskell][RWH] Chapter 1

RWHのChapter 1を読む。


  • not equalは!=ではなく/=。

  • temporary definitionをletで。例えば、ghci> let e = exp 1とか。

  • ghci> :info []とかすれば、[]の情報が調べられる。

  • ghci> :set +tで型情報を表示する。解除は:unset +t。:type itでunsetしても簡単に調べられる。

  • itで前の計算結果を参照出来る。



簡単にHaskellのプログラムを書くにはinteractを使えば良いらしい。
main = interact f 但し、fはString -> Stringな関数。これで標準入力から読んで標準出力に書けば良い。

% cat WC.hs
main = interact wordCount
where wordCount input = show (length (lines input)) + "\n"
%

Prelude> :info interact
interact :: (String -> String) -> IO () -- Defined in System.IO

実行は
% runghc WC.hs < quux.txt

Exerciseはとりあえずこんな感じ?

main = interact wordCount
where wordCount input = numLines input ++ "\t" ++ numWords input ++ "\t" ++ numChars input ++ "\n"
numLines input = show (length (lines input))
numWords input = show (length (words input))
numChars input = show (length input)

04 December, 2008

[Scala] map and flatMap on scala.Function1

scala.Function1 lacking @ λ Tony’s blog λ

Scalaの関数の型の scala.Function1 にもっと便利な機能を、という話。
mapはともかくflatMapをこうやって定義するのか、というのはなんか勉強になったので、メモ代わりに。
同様に S combinator というのも自分ではなんか巧く使えないものなので参考になるなぁ、と。on は普通に便利かも。

[Misc] These days...

Project Eulerはその後も細々と解いてはいるのだが、いちいち解法をblogに書くのが面倒になって更新をさぼってました。あと1問でLevel 2なんだけどなー。

 あと、Scala勉強会@関東2の準備が忙しかったりとか。仕事でのプレゼンは勿論あるけど、私的な勉強会でのプレゼンはこれが始めてで、なんていうか仕事とは別の緊張感がありますね。つまり仕事と違って、「実は自分は良く解ってなくて外しているんじゃないかしらん」みたいな不安とか。

そういえばそろそろReal World Haskellの発売ですね。東京近辺で興味がある人がいれば是非とも読書会とか検討しません?

20 October, 2008

[Project Euler] Problem 22

Problem 22

 この問題では、入力データをコードに直接貼付けず、ファイルから読み込むようにする必要がある。
 ファイルからデータをどう読み込むかだが、scala.io.Source.fromFile("filename").getLines で行単位で読み込めるイテレータを生成するので、それを使うのが楽だと思う。(本当はそういう目的のライブラリでは無い様にも思うのだが...。)
 names.txt がどこに置かれているのかというと、Eclipse上のプロジェクトProjectEuler下の、srcの下の、パッケージP020の下に、names.txtが置かれているので、filenameとしては"src/P020/names.txt"になった。

object P022 {
def main(args:Array[String]) {
val line:String = scala.io.Source.fromFile("src/P020/names.txt").getLines.next
val names1:List[String] = line.split(",").map{s => s.substring(1, s.size-1)}.toList
val names2:List[(String,Int)] = names1.sort{(a,b) => a.compareTo(b)<0}.zipWithIndex.map{t => (t._1, t._2+1)}
println(names2.head) // (AARON,1)
def f(t:Tuple2[String,Int]):Int = t match {
case (s,i) => i * s.toCharArray.map{c => c-'A'+1}.foldLeft(0)(_+_)
}
println(f(names2.head)) // (AARON,1)
println(names2.map(f).foldLeft(0)(_+_))
}
}

[Project Euler] Problem 21

Problem 21

素数リストとかは過去の問題のを使い回し。

object P021 {
val initPrimes:List[Int] = List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97, 101)
def primes(init:List[Int]):List[Int] = {
def min(a:Int, b:Int) = if (a<b) a else b
def sq(a:Int) = a*a
init ::: List.range(init.last+1, min(100000,sq(init.last))).filter{x => init.forall{x%_!=0}}
}
val primeList:List[Int] = primes(primes(initPrimes))
def factors(n:Int):List[Tuple2[Int,Int]] = {
def g(n:Int, p:Int, c:Int):Tuple2[Int,Int] = if (n%p==0) g(n/p, p, c+1) else Tuple2(n,c)
def f(n:Int, ps:List[Int], ts:List[Tuple2[Int,Int]]):List[Tuple2[Int,Int]] = (n,ps) match {
case (1,_) => ts
case (_,Nil) => error("n="+n)
case (_, a::as) if n%a==0 => {
val z = g(n,a,0)
f(z._1, as, Tuple2(a,z._2)::ts)
}
case (_, a::as) => f(n, as, ts)
}
f(n, primeList, Nil)
}
def sumDiv(ts:List[Tuple2[Int,Int]]):Int = {
def pow(a:Int, b:Int):Int = b match {
case 0 => 1
case _ => pow(a,b-1)*a
}
def f(t:Tuple2[Int,Int]):Int = {
List.range(0, t._2 +1).map{pow(t._1, _)}.foldLeft(0)(_+_)
}
ts.map(f).foldLeft(1)(_*_)
}
def sumDivisors(n:Int):Int = sumDiv(factors(n))-n
def main(args:Array[String]) {
println(sumDivisors(220))
println(sumDivisors(284))
val l = List.range(2,10000).filter{a => {
val b=sumDivisors(a)
val c=sumDivisors(b)
val r=(a!=b)&&(a==c)
if (r) println(a+"<->"+b)
r
}}
println(l)
println(l.foldLeft(0)(_+_))
}
}

---
 ここまでProject Eulerを解いてきて思ったのだが、組み込みライブラリとか言語仕様で、

  • アノテーションを付けるだけで関数の値をメモ化してくれる。
  • とりあえず1,000,000以下の素数は予め計算されている。
  • 内部でIntの計算が桁溢れしたら勝手にLong -> BigInt と桁を増やしてくれるような数値型。
  • 素因数分解も組み込み関数で用意されている。
とかだと、かなり楽が出来るのになぁ...。

19 October, 2008

[Project Euler] Problem 20

Problem 20

BigIntegerを使うだけ。

object P020 {
def main(args:Array[String]) {
def fact(n:Int):BigInt = n match {
case 0 => new BigInt(java.math.BigInteger.ONE)
case _ => new BigInt(java.math.BigInteger.valueOf(n))*fact(n-1)
}
val r = fact(100).toString.toCharArray.map{c => c-'0'}.foldLeft(0)(_+_)
println(r)
}
}

[Project Euler] Problem 19

Problem 19

難しくは無いはずなんだが、1900を1990と入力していたtypoの為でなかなか正解に辿り着けなかった。

object P019 {
def isLeap(y:Int):Boolean = y match {
case _ if y%400==0 => true
case _ if y%100==0 => false
case _ if y%4==0 => true
case _ => false
}
def daysOfMonth(y:Int, m:Int):Int = m match {
case 9 => 30
case 4 => 30
case 6 => 30
case 11 => 30
case 2 => if (isLeap(y)) 29 else 28
case _ => 31
}
def daysInYear(y:Int):Int = if (isLeap(y)) 366 else 365
def daysFrom1Jan1900(year:Int, month:Int, day:Int):Int =
List.range(1900,year).map{y => daysInYear(y)}.foldLeft(0)(_+_) +
List.range(1,month).map{m => daysOfMonth(year,m)}.foldLeft(0)(_+_) +
(day-1)
val day0Jan1900:Int = daysFrom1Jan1900(1900,1,0) // Sunday
def dayOfTheWeek(year:Int, month:Int, day:Int):Int = (daysFrom1Jan1900(year,month,day)-day0Jan1900)%7
def main(args:Array[String]) {
println(dayOfTheWeek(1901,1,1))
println(dayOfTheWeek(2000,12,1))
println(
(for(y <- List.range(1901,2001); m <- List.range(1,13))
yield dayOfTheWeek(y,m,1)).filter{_==0}.size)
}
}

[Project Euler] Problem 18

Problem 18

n段目では、(n-1)段目で左右を選ぶ選択で大きな方を選ぶ、というのを再帰的にやれば良いが、計算時間を減らす為に、Problem 15と同様にメモ化する。

import scala.collection.mutable.HashMap
object P018 {

val input:Array[String] = Array(
"75",
"95 64",
...中略...
"63 66 04 68 89 53 67 30 73 16 69 87 40 31",
"04 62 98 27 23 09 70 98 73 93 38 53 60 04 23") // y=0
val data:Array[Array[Int]] = input.map{s => s.split(" ").map{x => x.toInt}}
val height = input.size
def value(x:Int,y:Int):Int = data(height-1-y)(x)
val map:HashMap[Tuple2[Int,Int],Int] = new HashMap()
def search(x:Int,y:Int):Int = map.get(Tuple2(x,y)) match {
case Some(x) => x
case None => (x,y) match {
case (_,0) => {
val v = value(x,y)
map.put(Tuple2(x,y),v)
v
}
case _ => {
val s0 = value(x,y)
val s1 = search(x,y-1)
val s2 = search(x+1,y-1)
val v = if (s1>s2) s0+s1 else s0+s2
map.put(Tuple2(x,y),v)
v
}
}
}
def main(args:Array[String]) {
println(search(0,height-1))
}
}

[Project Euler] Problem 17

Problem 17

綴りが間違っていると正解が出ないので、念のために数詞を調べ直したりとか、英語を使ってない我々にはそれだけで敷居が高い問題。
この手の問題を解く時は、match-case構文を便利だと特に感じる。

object P017 {

val digitOne:List[String] = List("", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine")
val teens:List[String] = List("", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen")
val digitTen:List[String] = List("", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety")
def toEnglish(n:Int):String = n match {
case 1000 => "one thousand"
case _ if (n/100 != 0) => digitOne(n/100)+" hundred" + (if (n%100==0) "" else " and "+toEnglish(n%100))
case _ if (10<n)&&(n<20) => teens(n-10)
case _ => Tuple2(n/10, n%10) match {
case (a,0) => digitTen(a)
case (0,b) => digitOne(b)
case (a,b) => digitTen(a)+"-"+digitOne(b)
}
}
def countLetter(s:String) = s.split("[ -]").map{s => s.length}.foldLeft(0)(_+_)
def main(args:Array[String]) {
/*
println(List(1,9,10,11,19,20,21,99).map{x => Tuple3(x,toEnglish(x),countLetter(toEnglish(x)))})
println(List(100,101,110,111,120,121,199).map{x => Tuple2(x,toEnglish(x))})
println(List(200,201,210,211,299).map{x => Tuple2(x,toEnglish(x))})
println(List(900,901,910,911,999,1000).map{x => Tuple2(x,toEnglish(x))})
*/
println(List(342,115).map{x => Tuple3(x,toEnglish(x),countLetter(toEnglish(x)))})
println(List.range(1,1001).map{x => countLetter(toEnglish(x))}.foldLeft(0)(_+_))
}
}

[Project Euler] Problem 16

Problem 16

java.math.BigIntegerを使うだけ。

object P016 {
def main(args:Array[String]) {
val v2 = new BigInt(java.math.BigInteger.valueOf(2L))
val r = v2.pow(1000).toString.toCharArray.map{c => c-'0'}.foldLeft(0)(_+_)
println(r)
}
}

[Project Euler] Problem 15

Problem 15

これも計算機を使わないで解いた方が速い問題。
n x n grid で考えると、2n ステップの中から n 個右を選ぶということだから、
_{2n}C_n を計算すればいい。つまり、(2n)! / (n!)^2 。

もうちょっと計算機っぽく考えると、nx x ny のグリッドの場合、最初に右に行くか下に行くかで、
f(nx,ny) = f(xn-1,ny) + f(xn,ny-1)
但し、
f(0,_) = f(_,0) = 1
を解けば良いが、最初の式は実は _nC_r = _{n-1}C_r + _{n-1}C_{r-1} の事である。
実際にはこのままではf(nx,ny)回の関数呼び出しが発生するので、メモ化する。

import scala.collection.mutable.HashMap
import java.math.BigInteger

object P015 {
val map:HashMap[Tuple2[Int,Int],Long] = new HashMap()
def f(nx:Int, ny:Int):Long = map.get(Tuple2(nx,ny)) match {
case Some(x) => x
case None => {
val z:Long = (nx,ny) match {
case (0,_) => 1
case (_,0) => 1
case (x,y) => f(x-1,y) + f(x,y-1)
}
val zz = map.put(Tuple2(nx,ny),z)
z
}
}
def main(args:Array[String]) {
println(f(20,20))
println(40L/20*39*38/19*37*36/18*35*34/17*33*32/16*31*30/15*29*28/14*27*26/13*25*24/12*23*22/11*21/10/9/8/7/6/5/4/3/2/1)
}
}

[Project Euler] Problem 14

Problem 14

 有名なCollatz問題。最初はListを使っていたのだがOutOfMemoryになるので普通に命令型っぽいプログラムになっている。

object P014 {
def collatz(n:Long):Int = {
def f(n:Long, c:Int):Int = {
if (n==1L) c
else if (n%2L==0) f(n/2, c+1)
else f(3L*n+1, c+1)
}
f(n,1)
}
def mx(a:Tuple2[Long,Int], b:Tuple2[Long,Int]):Tuple2[Long,Int] = if (a._2 > b._2) a else b
def maxLength(m:Int):Tuple2[Long,Int] = {
var ts:Tuple2[Long,Int] = Tuple2(0L,0)
for(x <- List.range(1, m)) {
val c:Int = collatz(x)
if (ts._2 < c) {ts = Tuple2(x,c)}
}
ts
}
def main(args:Array[String]) {
println(collatz(13L))
println(maxLength(1000000))
}
}

[Project Euler] Problem 13

Problem 13

文字列 -> BigInt の変換をして和を取るだけ。

import java.math.BigInteger

object P013 {
val input:Array[String] = Array(
"37107287533902102798797998220837590246510135740250",
...中略...
"53503534226472524250874054075591789781264330331690")
def main(args:Array[String]) {
val sum:BigInt = input.map{s => new BigInt(new BigInteger(s))}.foldLeft(new BigInt(BigInteger.ZERO))(_+_)
println(sum)
println(sum.toString.substring(0,10))
}
}

[Project Euler] Problem 12

Problem 12

 最初に素数列を作成するのに結構時間がかかる気がする。今回は最初10000以下の素数のリストを作り、エラーが出たので30000まで作った。
 約数の数は素因数分解して各素数の指数から求められるのを使う。

object P012 {
val maxRange = 30000
def primes(init:List[Int]):List[Int] = {
def min(a:Int, b:Int) = if (a<b) a else b
def sq(a:Int) = a*a
init ::: List.range(init.last+1, min(maxRange,sq(init.last))).filter{x => init.forall{x%_!=0}}
}
val primeList = primes(primes(List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101)))
val emptyFactors = primeList.map{x => 0}
def factor(n:Int):List[Int] = {
def f(x:Int, fs:List[Int], ps:List[Int]):List[Int] = (x,fs,ps) match {
case (1,_,_) => fs
case (_,Nil,_) => error("fs empty:x="+x)
case (_,_,Nil) => error("ps empty:x="+x)
case (y,a::as,b::bs) if y%b==0 => f(y/b, (a+1)::as, ps)
case (y,a::as,b::bs) => a::f(y,as,bs)
}
f(n, emptyFactors, primeList)
}
def numFactors(n:Int):Int = factor(n).foldLeft(1){(a,b) => a*(b+1)}
def triangle(n:Int):Int = n*(n+1)/2
def main(args:Array[String]) {
println(primeList.length)
println(numFactors(28))
val t:Tuple3[Int,Int,Int] =
Stream.range(1,maxRange).map{x => Tuple3(x,triangle(x),numFactors(triangle(x)))}.filter{x => x._3>500}.head
println(t)
println(factor(triangle(t._1)).zip(primeList).filter{t => t._1 != 0}.map{x => x._2+"^"+x._1})
}
}

18 October, 2008

[Project Euler] Problem 11

Problem 11

表をArray[Array[Int]]に変換、4つの並びを(x,y)のTuple2のListとして表現し、全組み合わせ ( List[List[Tuple2[Int,Int]]]) から積が最大になるものを選択する。

object P011 {
val input:Array[String] = Array(
"08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08",
"49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00",
"81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65",
"52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91",
"22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80",
"24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50",
"32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70",
"67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21",
"24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72",
"21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95",
"78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92",
"16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57",
"86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58",
"19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40",
"04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66",
"88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69",
"04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36",
"20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16",
"20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54",
"01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48")
val table:Array[Array[Int]] = input.map{x => x.split(" ").map{_.toInt}}
def v(x:Int, y:Int):Int = table(y)(x)
val xscan:List[List[Tuple2[Int,Int]]] =
for(y <- List.range(0,20); x0 <- List.range(0,20-4)
) yield for(x <- List.range(x0, x0+4)) yield (x,y)
val yscan:List[List[Tuple2[Int,Int]]] =
for(x <- List.range(0,20); y0 <- List.range(0,20-4)
) yield for(y <- List.range(y0, y0+4)) yield (x,y)
val diagscan1 =
for(x0 <- List.range(0,20-4); y0 <- List.range(0,20-4)
) yield for(i <- List.range(0,4)) yield (x0+i, y0+i)
val diagscan2 =
for(x0 <- List.range(0,20-4); y0 <- List.range(0,20-4)
) yield for(i <- List.range(0,4)) yield (x0+3-i, y0+i)
def f(ps:List[Tuple2[Int,Int]]):Long = ps.foldLeft(1L){(a,t) => a * v(t._1,t._2)}
val result = (xscan ::: yscan ::: diagscan1 ::: diagscan2).sort{(a,b) => f(a)>f(b)}.head
def main(args:Array[String]) {
println(result)
println(result.map{t => v(t._1,t._2)})
println(f(result))
}
}

[Project Euler] Problem 10

Problem 10

 2000000以下の素数を得るには1415以下の素数が判れば良く、1415以下の素数を得るには37以下の素数が判れば良く、37以下の素数を知るには7以下の素数が2,3,5,7であることを知っていればOKである。
 という訳で、エラトステネスの篩を三段重ねにしてみた。

object P010 {
def main(args:Array[String]) {
val maxRange = 2000000
def primes(init:List[Int]):List[Int] = {
def min(a:Int, b:Int) = if (a<b) a else b
def sq(a:Int) = a*a
init ::: List.range(init.last+1, min(maxRange,sq(init.last))).filter{x => init.forall{x%_!=0}}
}
println(new java.util.Date())
val ps1 = List(2,3,5,7)
val ps2 = primes(ps1)
println(ps2.last)
val ps3 = primes(ps2)
println(ps3.last)
val ps4 = primes(ps3)
println(ps4.last)
println(ps4.foldLeft(0L)(_+_))
println(new java.util.Date())
}
}

[Project Euler] Problem 9

Project EulerのProblem 9。実は簡単に手で解ける問題。

a^2 + b^2 = c^2 を満たす a,b,c は、一般性を失わずに、
a = m^2 - n^2, b= 2mn, c = m^2 + n^2 (m > n)
と書ける事が判っている。
a + b + c = 1000 より、m(m+n) = 500 となる。
500 の約数は、(1,2,4,5,10,20,25,50,100,125,250,500) で、
m+n > m, m > n を使うと、m = 20, n = 5。


object P009 {
def main(args:Array[String]) {
println(new java.util.Date)
val result = for(a <- List.range(1,1000);
b <- List.range(a,1000-a);
c = 1000 - a - b
if a*a + b*b == c*c
) yield (a,b,c,a*b*c)
println(result)
println(new java.util.Date)
}
}

[Project Euler] Problem 8

Project EulerのProblem 8。

特に工夫せずに、文字列からList[Int]に変換、頭から要素を5つ取り出して、1文字ずらして次へ、という感じ。

object P008 {
def c2i(c:Char):Int = c - '0'
val data:List[Int] = {(
"73167176531330624919225119674426574742355349194934"+
"96983520312774506326239578318016984801869478851843"+
"85861560789112949495459501737958331952853208805511"+
"12540698747158523863050715693290963295227443043557"+
"66896648950445244523161731856403098711121722383113"+
"62229893423380308135336276614282806444486645238749"+
"30358907296290491560440772390713810515859307960866"+
"70172427121883998797908792274921901699720888093776"+
"65727333001053367881220235421809751254540594752243"+
"52584907711670556013604839586446706324415722155397"+
"53697817977846174064955149290862569321978468622482"+
"83972241375657056057490261407972968652414535100474"+
"82166370484403199890008895243450658541227588666881"+
"16427171479924442928230863465674813919123162824586"+
"17866458359124566529476545682848912883142607690042"+
"24219022671055626321111109370544217506941658960408"+
"07198403850962455444362981230987879927244284909188"+
"84580156166097919133875499200524063689912560717606"+
"05886116467109405077541002256983155200055935729725"+
"71636269561882670428252483600823257530420752963450")}.toCharArray().toList.map(c2i)
def mul(cs:List[Int]):Int = cs match {
case List(a,b,c,d,e) => a*b*c*d*e
case _ => -1
}
def f(max:Int, list:List[Int]):Int = {
if (list.length<5) max
else {
val a = mul(list.take(5))
if (a>max) f(a, list.tail)
else f(max, list.tail)
}
}
def main(args:Array[String]) {
println(new java.util.Date)
println(f(-1,data))
println(new java.util.Date)
}
}

[Project Euler] Problem 7

Project EulerのProblem 7。

 Problem 3で作った素数列のStreamは10001個の素数を計算させようとするとOutOfMemoryで落ちてしまった。本当はちゃんと理由を考えるべきだよなぁ。計算を遅延しているのは確かめたし、あとは実装上で非常にメモリ効率が悪いとかそういうのなのかなぁ。うぅぅむ、Streamへの理解が足りない様だ。
 とりあえず奇素数10000個のListを作れば良いので、下記の様に済ませた。計算時間は自機で15秒程度。

object P007 {
def main(args:Array[String]) {
println(new java.util.Date())
val ps = primes(10000)
println(ps(0))
println(ps(9999))
println(new java.util.Date())
}
def primes(num:Int):List[Int] = {
def odds(n:Int):Stream[Int] = Stream.cons(n, odds(n+2))
def primes2(ps:List[Int], ss:Stream[Int]):List[Int] = {
if (ps.length >= num) ps
else {
val x = ss.head
if (ps.forall(x%_!=0)) primes2(x::ps, ss.tail) else primes2(ps, ss.tail)
}
}
primes2(Nil,odds(3))
}
}

[Project Euler] Problem 6

Project EulerのProblem 6。

 本当は高校の数学を使って紙と鉛筆で解ける様な問題のはずなんだが、いざ解いてみようとすると案の定 k^2 の和の公式を忘れていて、k(k+1) の和の公式から作り直したりとか。手でこういう計算をしなくなってから長いからなぁ。

object P006 {
def p006(n:Int):Int = sq(sum(List.range(1,n+1))) - sum(List.range(1,n+1).map(sq))
def sum(xs:List[Int]):Int = xs.foldLeft(0)(_+_)
def sq(x:Int):Int = x*x
def main(args:Array[String]) {
println(new java.util.Date)
println(p006(10))
println(p006(100))
println(new java.util.Date)
println(f(100))
}
def f(n:Int):Int = sq(n*(n+1)/2) - n*(n+1)*(2*n+1)/6
}

[Project Euler] Problem 5

Project EulerのProblem 5。

これはまぁ、1,2,...,20 の最小公倍数を求めれば良いだけの話。

object P005 {
def gcd(a:Long, b:Long):Long = {
if (a>b) gcd(b,a)
else if (a==0) b
else gcd(b%a, a)
}
def lcd(a:Long, b:Long):Long = a/gcd(a,b)*b
def p005(n:Int):Long = List.range(1, n+1).foldLeft(1L)(lcd(_,_))
def main(args:Array[String]) {
println(new java.util.Date)
println(p005(10))
println(p005(20))
println(new java.util.Date)
}
}

[Project Euler] Problem 4

Project EulerのProblem 4。

 最初に作ったコードはは3桁 x 3桁の2重ループで積を求めて、それが回文数になっているかをチェックし、積の大きさでソート、という解法で解きました。
 まぁ計算時間が1分以内ならどんな解き方でも良い様なものですが、最大の回文数を1つだけ求めれば良いならば、逆に大きな回文数から順に3桁 x 3桁になるか試した方が無駄が無い様にも思ったので次の様なコードに。ここでもStreamを作ってheadで最初の要素だけ求めます。
 この手のパズル的な問題を解く時には遅延リストは便利ですね。Haskellで解けばもっと楽なのだろうなぁ...。

object P004 {
def main(args:Array[String]) {
time({println(p004)})
}
val palins:Stream[Int] =
for(x <- Stream.range(9,0,-1);
y <- Stream.range(9,-1,-1);
z <- Stream.range(9,-1,-1))
yield (100001*x + 10010*y + 1100*z)
def f(n:Int):Stream[Tuple3[Int,Int,Int]] = {
Stream.range(999,99,-1).filter{x => (n%x==0)&&(100<=n/x)&&(n/x<1000)}.map{x => (x,n/x,n)}
}
def p004:Tuple3[Int,Int,Int] = palins.flatMap(f).head
def time(block:Unit) {
val t0 = System.currentTimeMillis
block
println((System.currentTimeMillis-t0)+"msec")
}
}

17 October, 2008

[Project Euler] Problem 3

 Project EulerのProblem 3。

 実のところ、素数列を求める必要は全く無く、単に奇数で順に割って行けば良いだけの問題だと思う。が、せっかくなのでStreamの使い方の勉強という事で、素数列のStreamを作る。

 Streamの使い方は、例えば下記の記事とかが参考になると思う。
 基本的にStreamを使いたいケースというのは、(1)パズル系の問題を解く為に無限数列を作りたい、(2)入力やファイルとかで必要があるまで読み込みたく無い、のどっちかの場合かと思う。勿論、Streamはリストの様に扱え便利なので、ループとかイテレータじゃなくて遅延リストに表現したいという事。
 (1)の場合は、a_n → a_{n+1} = f(a_n) の様に書けるならば、val s = Stream.cons(a0, s.map(f))と書けば良い。Problem 2のような二項漸化式の場合は、s.zip(s.tail)の様な(a_n, a_{n+1})なタプルを作って計算というのが定番。
 (2)の例としては、例えばSimplifying JDBC @ Scala Wikiの例が実用例。JDBCのResultSetをStream[X]に変換するコードとして、

private def strm[X](f: RichResultSet => X, rs: ResultSet): Stream[X] =
if (rs.next) Stream.cons(f(new RichResultSet(rs)), strm(f, rs))
else { rs.close(); Stream.empty };

の様に書かれている。rs.nextがtrueならばStreamの新しい要素xを作って、xとStreamの続きであるstrm(f,rs)自身のconsであるStream.cons(x, strm(f,rs))を返し、rs.nextがfalseになったらEmptyを返して終了。

 Problem 3のコードはこんな感じ。

object P003 {
def main(args:Array[String]) {
// println(primes.take(10).force) // -> List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
// println(g(13195)) // -> List(29, 13, 7, 5)
time({println(g(600851475143L))})
}
val odds:Stream[Long] = Stream.cons(3, odds.map(_+2))
def sieve(xs:Stream[Long]):Stream[Long] = Stream.cons(xs.head, sieve(xs.tail.filter{_%(xs.head)!=0}))
val primes:Stream[Long] = Stream.cons(2, sieve(odds))
def f(n:Long, ps:Stream[Long], l:List[Long]):(Long,Stream[Long],List[Long]) = {
if (n==1) (n,ps,l)
else {
val m = ps.head
if (n%m==0) {
f(n/m, ps, m::l)
} else {
f(n, ps.tail, l)
}
}
}
def g(n:Long):List[Long] = f(n, primes, Nil)._3
def time(block:Unit) {
val t0 = System.currentTimeMillis
block
println((System.currentTimeMillis-t0)+"msec")
}
}

 Streamは実際に値が使われるまで計算されないので、デバッグプリントしたいときはtake(10)だけじゃなく、forceとかをつけないと駄目。
 oddsが奇数が無限に続くStreamで、sieveがエラトステネスの篩で素数だけにするフィルタ、primesが素数列です。あとこの問題は素因数分解の対象がIntじゃなくてLongでないと収まらない。最初はIntが駄目なのでBigIntで計算したけど実はLongなんてのもあったな、と計算時間を計るtimeを作っていて思い出したり。

[Project Euler] Problem 2

Project EulerのProblem 2。

Fibonacci数列というと、やはり遅延ストリームで表現するのがお約束ということで、Streamの使い方の復習。
Fibonacci数列と素数列はなんかイディオムとして覚えてもいいかもしれない。それが理解出来ればStreamが自由に扱える様な気がする。

fibはlazy valで宣言しなくてもOKだったが、なぜOKなのかについては自信が無い。

object P002 {
val fib:Stream[Int] = Stream.cons(1, Stream.cons(2, (fib.zip(fib.tail)).map{x => x._1 + x._2}))
def main(args:Array[String]) {
println(fib.take(10)) // --> Stream(1, ?)
println(fib.take(10).force) // --> List(1, 2, 3, 5, 8, 13, 21, 34, 55, 89)
println(fib.filter{x => x%2==0}.takeWhile{x => x<100}.foldLeft(0)(_+_))
println(fib.filter{x => x%2==0}.takeWhile{x => x<4000000}.foldLeft(0)(_+_))
}
}

なんかHaskellで書いたコードに比べると冗長な感じがする。Listと違ってStreamはシンタックスシュガーが弱いから仕方が無いが。

16 October, 2008

[Project Euler] Problem 1

Project EulerをScalaで挑戦してみることにしました。

 Project Eulerってのは数学っぽいプログラミングの問題が出題されているサイトです。「どう書く.org」の数学問題とかに割と近い。特に数学を専攻した人でなくても解けるし、コンピュータでの計算時間も賢くプログラミング出来れば1分以内ぐらいだそうです。
 問題は英語で出題されているけど、和訳サイトもあります。

 アカウントを作ってログインし、profileの画面で解いた問題を選択し、正しい答えを入力すると、その問題は回答済みとなります。とりあえずの目標は25問解いてLevel 1に昇格すること。200問解くとLevel 5なんだが、78人しかいない様子。

 Project Eulerの良いところは、Webサイトに答えを入力すると、正しいか間違っているかが判ることです。現実の問題は予め正解が判っている訳ではないのが、予め正解の判っている問題を解く学校の勉強と違うところだ、というのは良く聞く話ですが、逆に言うと正解の判らない問題を解くのは勉強方法としては効率が悪い訳です。その点でProject Eulerは勉強用に向いています。
 関数型言語(に限らずにCとかJavaとかプログラミング言語一般でも良いと思いますが)の入門用の例題としては良いのではないでしょうか。

 出来るだけ毎日1題づつ解いて行こうと思っています。

 Problem 1のコードはこんな感じで簡単に解けた。

object P001 {
def main(args:Array[String]) {
println(List.range(1,10).filter{x => (x%3==0)||(x%5==0)}.foldLeft(0)(_+_))
println(List.range(1,1000).filter{x => (x%3==0)||(x%5==0)}.foldLeft(0)(_+_))
}
}

 List.rangeで1, 2,..., 9という数値のリストを作り、filterで3または5の倍数を選択し、foldLeftで合計を求めます。まぁ割とScalaとか関数型言語での定番のイディオムなんじゃなかろうか。
 解法としては勿論、受験数学的に数列の和の公式とかを使って解いてもOKなんだけど、計算時間が1分以内ならばどんな解き方でもOKと考えることにして、素直に実装した。

27 September, 2008

[Scala] Web Flavor

Web Flavorの0.2.0が公開されたので動かしてみました。
Web Flavorは、

  • Scalaコードをスクリプトとして記述してすぐ実行できる
  • 更新されれば自動的にコンパイルされる
という機能を備えたWebフレームワークだそうです。

●動かし方。

 とりあえず何も考えずに、WebFlavor-samples-0.2.0.zip (Apache Tomcat入り実行パッケージ、ソースなし)をダウンロードしてみる。

 私の場合、Mac OS Xだが、~/work にファイルを置いて zip ファイルを解凍。~/work/WebFlavor-samples/ というディレクトリが作られた。

[~/work/WebFlavor-samples] % ls
Apache-Tomcat-LICENSE.txt* logs/
Apache-Tomcat-RELEASE-NOTES* startup.bat*
LICENSE.txt* startup.sh*
Scala-LICENSE.txt* temp/
bin/ webapps/
conf/ work/
lib/
[~/work/WebFlavor-samples] %

startup.sh を起動すると実はこれだけでサーバが立ち上がる。

[~/work/WebFlavor-samples] % ./startup.sh
Using CATALINA_BASE: /Users/miyamoto/work/WebFlavor-samples
Using CATALINA_HOME: /Users/miyamoto/work/WebFlavor-samples
Using CATALINA_TMPDIR: /Users/miyamoto/work/WebFlavor-samples/temp
Using JRE_HOME: /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
2008/09/27 1:38:24 org.apache.catalina.startup.ClusterRuleSetFactory getClusterRuleSet
情報: Unable to find a cluster rule set in the classpath. Will load the default rule set.
2008/09/27 1:38:24 org.apache.catalina.startup.ClusterRuleSetFactory getClusterRuleSet
情報: Unable to find a cluster rule set in the classpath. Will load the default rule set.
2008/09/27 1:38:24 org.apache.catalina.core.AprLifecycleListener init
情報: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: .:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
2008/09/27 1:38:24 org.apache.coyote.http11.Http11Protocol init
情報: Initializing Coyote HTTP/1.1 on http-8080
2008/09/27 1:38:24 org.apache.catalina.startup.Catalina load
情報: Initialization processed in 431 ms
2008/09/27 1:38:24 org.apache.catalina.core.StandardService start
情報: Starting service Catalina
2008/09/27 1:38:24 org.apache.catalina.core.StandardEngine start
情報: Starting Servlet Engine: Apache Tomcat/6.0.18
CLASS_PATH: /Users/miyamoto/work/WebFlavor-samples/webapps/webflavor/WEB-INF/lib/scala-compiler.jar:/Users/miyamoto/work/WebFlavor-samples/webapps/webflavor/WEB-INF/lib/scala-library.jar:/Users/miyamoto/work/WebFlavor-samples/webapps/webflavor/WEB-INF/lib/webflavor.jar:/Users/miyamoto/work/WebFlavor-samples/webapps/webflavor/WEB-INF/classes::/Users/miyamoto/work/WebFlavor-samples/bin/bootstrap.jar
2008/09/27 1:38:25 org.apache.coyote.http11.Http11Protocol start
情報: Starting Coyote HTTP/1.1 on http-8080
2008/09/27 1:38:25 org.apache.jk.common.ChannelSocket init
情報: JK: ajp13 listening on /0.0.0.0:8009
2008/09/27 1:38:25 org.apache.jk.server.JkMain start
情報: Jk running ID=0 time=0/54 config=null
2008/09/27 1:38:25 org.apache.catalina.startup.Catalina start
情報: Server startup in 605 ms


●サンプルを表示させてみる

http://localhost:8080/webflavor/ にアクセスすると、トッブページが表示される。

リンクを辿って、Echo (samples/Echo.scala) を表示させてみる。コンテキストルートの /webflavor/下と、/WEB-INF/src/下とがそのまま対応していることが判る。
Source = ~/work/WebFlavor-samples/webapps/webflavor/WEB-INF/src/samples/Echo.scala
URL = http://localhost:8080/webflavor/samples/Echo.scala

Eco.scala は、表示したいHTMLをXMLリテラルとして返す様なscalaのコード片(Flavorと呼ぶ)で、JSPみたいなもの。
内部的には前後にコードを付加する事で(context: Context, request: Request, response: Response, session: collection.mutable.Map[String,Object]) => AnyRef なクラスに書き換えられる。

●管理画面からFlavorを作ってみる

トップページ http://localhost:8080/webflavor/ からリンクを辿って管理画面 http://localhost:8080/webflavor/admin/ に入る。
何も設定を変更してなければ、~/work/WebFlavor-samples/conf/tomcat-users.xml をみれば管理者ユーザ webflavor のパスワードが判るのでそれを使ってログインすればOK。

簡単な例として全く芸のない話だがDate.scalaというのを作ってみる。
フォームにDate.scala を入力して create ボタンを押すと、Date.scalaのソースコード入力ページになるので下記を入力して、

// Title
val title = "Date"

// Return output XML elements.
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{"Time="+(new java.util.Date()).toString()}</h1>
</body>
</html>

updateボタンでサーブして、実行するにはExecuteのリンクを押せば、http://localhost:8080/webflavor/Date.scala として、現在時刻が表示されるのが見える。

●感想

 手軽にscalaで動的ページを作成出来ることが判った。例えばscalaを教育用言語に使う場合とかには、初心者が簡単にWebアプリを書けたりなど教材として良いのではないかなぁ。
 

30 August, 2008

[Scala] How to Use Combinator Parser (3)

Scala の Parser Combinator の使い方の勉強 (続き)

 今回は小ネタ。

 例えばXMLとかでタグが対応していたりとか、(La)TeXのenvironmentなどの¥bigin{xxx}...¥end{xxx}のように、ある範囲の最初と最後に同じ文字列が出てくることを要請したい場合。こんな感じで実現出来る。

 例えばXML風のタグの対応を実装する場合、

def tagged:Parser[Tagged] =
"<"~>ident~">"~tkn~"</"~ident<~">" ^? {
case i~_~t~_~j if i==j => Tagged(i,t)
}

 a~>b, c<~d というのは、a~b, c~d と同じ様なものだが、a, d の結果を使用しない場合に使えるscala.util.parsing.combinator.Parsers.Parserのメソッド。今回の場合は最初の"<"と最後の">"はマッチさせても後で使わないので。但し、~と違って、a~b~c~... と繋いで結果のcaseを作れないので、"</"とかは~で前後を繋いで、caseでは_を使って結果を使わない事を示してみた。
 ^? というのは ^^ と同じ様なものだが、^^ は case にマッチする事が必要。^? の場合、caseのマッチで失敗した場合にはパース失敗と出来る。今回は i!=j になる場合は失敗させるため、^? を使う。

 全体のコードはこんな感じ。(...しかし不等号をblogに書くのはいちいちエスケープするのが面倒だった...)

package test;

import scala.util.parsing.combinator._

abstract class Tkn
case class Lit(s:String) extends Tkn
case class Tagged(tag:String, t:Tkn) extends Tkn
object Test3 extends JavaTokenParsers {
def tagged:Parser[Tagged] =
"<"~>ident~">"~tkn~"</"~ident<~">" ^? {
case i~_~t~_~j if i==j => Tagged(i,t)
}
def tkn:Parser[Tkn] =
( tagged
| ident ^^ {case s => Lit(s)}
)
def main(args:Array[String]) {
val s = "<aaa><bbb>ccc</bbb></aaa>"
println(s)
println(parse(tagged, s))
}
}

結果は下記の様な感じ

結果1
<aaa><bbb>ccc</bbb></aaa>
[1.26] parsed: Tagged(aaa,Tagged(bbb,Lit(ccc)))

---
結果2
<aaa><bbb>ccc</bbbb></aaa>
[1.21] failure: Constructor function not defined at ((((bbb~>)~Lit(ccc))~</)~bbbb)

<aaa><bbb>ccc</bbbb></aaa>
---
結果3
<aaa><bbb>ccc</bbb></aaaa>
[1.27] failure: Constructor function not defined at ((((aaa~>)~Tagged(bbb,Lit(ccc)))~</)~aaaa)

<aaa><bbb>ccc</bbb></aaaa>

26 August, 2008

[Scala] How to Use Combinator Parser (2)

Scala の Parser Combinator の使い方の勉強 (続き)

以下は Scala 2.7.1 の元での話です。

●数式のパーサを書いてみる (続き)

 前回の記事[Scala] How to Use Combinator Parser (1)で、使い方練習として定番の数式パーサを書きました。
 その際に、パーサコンビネータでは左再帰が苦手なので
expr ::= expr "+" term | expr "-" term | term

を、繰り返し
expr ::= term { ("+" | "-") term}

と変形して、それをそのままにコードに直しました。

 実は、用意されている関数 chainl1 を使ってもうちょっと奇麗に処理する事が出来ます。右結合性の演算子に対しては chainr1 を同じ様に使えばいいはず。

 まず、構文木のノード型を定義します。

abstract class Expr {
def eval:Int
}
case class Add(e:Expr,t:Term) extends Expr {
def eval = e.eval + t.eval
}
case class Sub(e:Expr,t:Term) extends Expr {
def eval = e.eval - t.eval
}
abstract class Term extends Expr {
def eval:Int
}
...

と書きます。
 Term は単独で Expr になる事が出来るから、Term が Expr のサブクラスになります。
 Expr のサブクラスの case class として Add, Sub を定義します。
 ここまではいいですよね。

 さて、chainl1 は、
def chainl1 [T, U](first : => Parser[T], p : => Parser[U], q : => Parser[(T, U) => T]) : Parser[T]

というメソッドです。これは、first { q p } にマッチするパーサみたいなもの。
 我々の場合だと、Expr をパースするパーサ def expr:Parser[Expr] が作りたいのだから、T = Expr です。
 expr (+|-) term => expr を表現したいのだから、U = term で、p = term:Parser[Term] です。
 q は "+", "-" とかの演算子の部分ですが、(Expr,Term) => Expr という関数を返すパーサです。つまり、(Expr,Term) => Add の様な関数を返せば OK。
 first をどうするかですが、ここに T = expr だから expr と書くと無限ループになって NG。正解は term を指定します。これで term { q term } になりますね。
 これをコードに直すと、

def add = "+" ^^ {case a => ((e:Expr,t:Term)=>Add(e,t))}
def sub = "-" ^^ {case a => ((e:Expr,t:Term)=>Sub(e,t))}
def expr:Parser[Expr] = chainl1(term, term, (add | sub))

となります。

 term についても同様に考えれば OK で、完成したコードは下記の様。

package test;

import scala.util.parsing.combinator._

abstract class Expr {
def eval:Int
}
case class Add(e:Expr,t:Term) extends Expr {
def eval = e.eval + t.eval
}
case class Sub(e:Expr,t:Term) extends Expr {
def eval = e.eval - t.eval
}
abstract class Term extends Expr {
def eval:Int
}
case class Mul(t:Term,f:Factor) extends Term {
def eval = t.eval * f.eval
}
case class Div(t:Term,f:Factor) extends Term {
def eval = t.eval / f.eval
}
abstract class Factor extends Term {
def eval:Int
}
case class Parens(e:Expr) extends Factor {
def eval = e.eval
}
case class Num(n:Int) extends Factor {
def eval = n
}
class ExprParser2 extends JavaTokenParsers {
def add = "+" ^^ {case a => ((e:Expr,t:Term)=>Add(e,t))}
def sub = "-" ^^ {case a => ((e:Expr,t:Term)=>Sub(e,t))}
def expr:Parser[Expr] = chainl1(term, term, (add | sub))
def mul = "*" ^^ {case a => ((t:Term,f:Factor)=>Mul(t,f))}
def div = "/" ^^ {case a => ((t:Term,f:Factor)=>Div(t,f))}
def term:Parser[Term] = chainl1(factor, factor, (mul | div))
def factor:Parser[Factor] =
( wholeNumber ^^ {case s => Num(s.toInt)}
| "("~expr~")" ^^ {case a~b~c => Parens(b)}
)
}
object Test2 extends ExprParser2 {
def main(args:Array[String]) {
val s = "1 * (2 * 3) - 4*5+(6*7 + 8*9)"
println(s)
println(parse(expr, s))
println(parse(expr, s).get.eval) // -> 100
}
}

実行すると、

1 * (2 * 3) - 4*5+(6*7 + 8*9)
[1.30] parsed: Add(Sub(Mul(Num(1),Parens(Mul(Num(2),Num(3)))),Mul(Num(4),Num(5))),Parens(Add(Mul(Num(6),Num(7)),Mul(Num(8),Num(9)))))
100

23 August, 2008

[Scala] How to Use Combinator Parser (1)

Scala の Parser Combinator の使い方の勉強 (1)

以下は Scala 2.7.1 の元での話です。

●数式のパーサを書いてみる

使い方練習という事で、整数の括弧有り四則演算の出来るパーサを書くことにします。
scala.util.parsing.combinator.JavaTokenParsers を元に作成するのが簡単です。なぜならば、

  • 識別子(大文字+小文字+数字+'_'からなる文字列)、数(整数、実数、指数形式の実数)、文字列リテラル("..."な文字列でUnicode対応)のパーサが予め用意されている。

  • 空白文字を自動的に読み飛ばす機能付き。
だから。

パーサクラスを、

import scala.util.parsing.combinator._

class ExprParser1 extends JavaTokenParsers {
...
}
と宣言して、...の部分を考えることにします。

まず数式の(E)BNF文法ですが、

<expr> ::= <expr> "+" <term> | <expr> "-" <term> | <term>
<term> ::= <term> "*" <factor> | <term> "/" <factor> | <factor>
<factor> ::= "(" <expr> ")" | <number>
と書くと、左再帰でパーサコンビネータでは都合が悪い(expr の解析で expr を呼んで...を繰り返しすぐにstack overflowする)ので、(以下、HTMLでは面倒なので < > は省略)

expr ::= term { ("+" | "-") term}
term ::= factor { ("*" | "/") factor}
と書き直す事にします。

expr ::= term { ("+" | "-") term}
の部分をどうするかですが、

case class OpTerm(op:String, t:Term) { // op = "+" or "-"
def eval(n:Int):Int = op match {
case "+" => n + t.eval()
case "-" => n - t.eval()
case _ => error(this.toString)
}
}
case class Expr(t:Term, ots:List[OpTerm]) {
def eval():Int = ots.foldLeft(t.eval()){(n:Int, ot:OpTerm) => ot.eval(n)}
}
def opTerm:Parser[OpTerm] = ("+" | "-")~term ^^ {case a~b => OpTerm(a,b)}
def expr:Parser[Expr] = term~rep(opTerm) ^^ {case a~b => Expr(a,b)}
と、構文木のノード型を定義し、パーサ関数を定義します。
 関数 opTerm は ("+" | "-" ) term の部分を表現しています。JavaTokenParsersでは、文字列 "+" は「文字列 "+" にマッチするパーサ」に暗黙の型変換がされます。"~" は、あるパーサにマッチした後に別のパーサにマッチすることを表します。"^^" は、 パーサがマッチした後の値を与える関数を書きます。"a~b" は実は case classで、a には("+" | "-")のマッチした結果 (文字列 "+" か "-") が入り、b には term のマッチした結果 (型 Term の値) が入ってます。
 関数 expr は term { opTerm } を表現します。rep(opTerm) で、opTerm の0回以上の繰り返しに対応するパーサになり、opTerm の繰り返しなので List[OpTerm] を与えるパーサとなります。
 case class の OpTerm, Expr の eval メソッドについては説明は省略。

 term ::= factor { ("*" | "/") factor } についても同様に書けるので説明は省略。

 factor ::= "(" expr ")" | number については下記の様に書けます。

def factor:Parser[Factor] =
( wholeNumber ^^ {case s => Num(s.toInt)}
| "("~expr~")" ^^ {case a~b~c => Parens(b)}
)
abstract class Factor {
def eval():Int
}
case class Num(n:Int) extends Factor {
def eval():Int = n
}
case class Parens(e:Expr) extends Factor {
def eval():Int = e.eval()
}

 wholeNumber は、JavaTokenParsersで定義されている、整数を表す文字列にマッチするパーサ。
"("~expr~")" ^^ ... の部分は見当がつくと思います。その2つを "|" で繋いでいます。

パーサを使う部分はこんな感じで書けます。

object Test1 extends ExprParser1 {
def main(args:Array[String]) {
val s = "1 * (2 * 3) - 4*5+6*7 + 8*9"
println(s)
println(parse(expr, s))
println(parse(expr, s).get.eval) // -> 100
}
}
作成したクラス ExprParser1 のメソッド parse に、パースしたい関数 (我々の場合は expr )と、入力 (String以外にjava.io.ReaderなどもOK) を渡します。得られたParseResult[Expr] のメソッド get で Expr 型の結果を得て、eval すれば OK です。

実はもっと要領よく書けるはずですが、判りやすさを優先して書いてみました。

全体のソースは下記の通り。

package test;

import scala.util.parsing.combinator._
// BNF with Left Recursion
// expr ::= expr + term | expr - term | term
// term ::= term * factor | term / factor | factor
// factor ::= ( expr ) | number
//
// BNF without Left Recursion
// expr ::= term { (+|-) term }
// term ::= factor { (*|/) factor }
// factor ::= ( expr ) | number

class ExprParser1 extends JavaTokenParsers {
def opTerm:Parser[OpTerm] = ("+" | "-")~term ^^ {case a~b => OpTerm(a,b)}
def expr:Parser[Expr] = term~rep(opTerm) ^^ {case a~b => Expr(a,b)}
def opFactor:Parser[OpFactor] = ("*" | "/")~factor ^^ {case a~b => OpFactor(a,b)}
def term:Parser[Term] = factor~rep(opFactor) ^^ {case a~b => Term(a,b)}
def factor:Parser[Factor] =
( wholeNumber ^^ {case s => Num(s.toInt)}
| "("~expr~")" ^^ {case a~b~c => Parens(b)}
)
case class OpTerm(op:String, t:Term) { // op = "+" or "-"
def eval(n:Int):Int = op match {
case "+" => n + t.eval()
case "-" => n - t.eval()
case _ => error(this.toString)
}
}
case class Expr(t:Term, ots:List[OpTerm]) {
def eval():Int = ots.foldLeft(t.eval()){(n:Int, ot:OpTerm) => ot.eval(n)}
}
case class OpFactor(op:String, f:Factor) { // op = "*" or "/"
def eval(n:Int):Int = op match {
case "*" => n * f.eval()
case "/" => n / f.eval()
case _ => error(this.toString)
}
}
case class Term(f:Factor, ofs:List[OpFactor]) {
def eval():Int = ofs.foldLeft(f.eval()){(n:Int, of:OpFactor) => of.eval(n)}
}
abstract class Factor {
def eval():Int
}
case class Num(n:Int) extends Factor {
def eval():Int = n
}
case class Parens(e:Expr) extends Factor {
def eval():Int = e.eval()
}
}
object Test1 extends ExprParser1 {
def main(args:Array[String]) {
val s = "1 * (2 * 3) - 4*5+6*7 + 8*9"
println(s)
println(parse(expr, s))
println(parse(expr, s).get.eval) // -> 100
}
}


結果:

1 * (2 * 3) - 4*5+6*7 + 8*9
[1.28] parsed: Expr(Term(Num(1),List(OpFactor(*,Parens(Expr(Term(Num(2),List(OpFactor(*,Num(3)))),List()))))),List(OpTerm(-,Term(Num(4),List(OpFactor(*,Num(5))))), OpTerm(+,Term(Num(6),List(OpFactor(*,Num(7))))), OpTerm(+,Term(Num(8),List(OpFactor(*,Num(9)))))))
100

29 July, 2008

[Scala] Scala exercises for beginners

初心者の為の練習問題

 原文はScala exercises for beginnersを参照の事。
 関数型言語の初心者向けの良い課題だと思うのだが。...が、関数型言語に不慣れだと、そもそもどんな関数を作る事が期待されているのか判んないかもしれないなぁ。(メソッド名とか関数の型から大体の見当がつくかな?)
 ソースコード中の error("課題") の部分を自分の書いたコードで置き換える事が期待されています。
 初心者の回答を自動で採点する為に、scalacheck で回答の正当性を検証する為の方法を誰か解説しない?


// 下記の List のメソッドは使用してはならない:
// * length
// * map
// * filter
// * ::: (および ++ のようなその変形)
// * flatten
// * flatMap
// * reverse (および reverseMap, reverse_::: のような変形)
// これはまた、List に対する for-構文の使用も禁止している。
// 自分で書いた関数は使用して良い。例えば問題 2 では問題 1 あるいは問題 3 を使用して良い。
// 許可された既に存在するメソッドを適切に使用した場合は、エレガントさが評価される。
// 満点: 66点
object Exercises {
def succ(n: Int) = n + 1
def pred(n: Int) = n - 1
// Exercise 1 (問題 1)
// Relative Difficulty 1 (難易度: 1)
// Correctness: 2.0 (正しい回答に: 2.0 点)
// Performance: 0.5 (性能: 0.5 点)
// Elegance: 0.5 (エレガントさ: 0.5 点)
// Total: 3 (合計: 3)
def add(x: Int, y: Int): Int = error("課題: x, yは 0 または正の数と仮定せよ。Int に対する +, - の使用を禁止する。上述の succ/pred の使用のみ許す。")
// Exercise 2
// Relative Difficulty: 2
// Correctness: 2.5 marks
// Performance: 1 mark
// Elegance: 0.5 marks
// Total: 4
def sum(x: List[Int]): Int = error("課題")
// Exercise 3
// Relative Difficulty: 2
// Correctness: 2.5 marks
// Performance: 1 mark
// Elegance: 0.5 marks
// Total: 4
def length[A](x: List[A]): Int = error("課題")
// Exercise 4
// Relative Difficulty: 5
// Correctness: 4.5 marks
// Performance: 1.0 mark
// Elegance: 1.5 marks
// Total: 7
def map[A, B](x: List[A], f: A => B): List[B] = error("課題")
// Exercise 5
// Relative Difficulty: 5
// Correctness: 4.5 marks
// Performance: 1.5 marks
// Elegance: 1 mark
// Total: 7
def filter[A](x: List[A], f: A => Boolean): List[A] = error("課題")
// Exercise 6
// Relative Difficulty: 5
// Correctness: 4.5 marks
// Performance: 1.5 marks
// Elegance: 1 mark
// Total: 7
def append[A](x: List[A], y: List[A]): List[A] = error("課題")
// Exercise 7
// Relative Difficulty: 5
// Correctness: 4.5 marks
// Performance: 1.5 marks
// Elegance: 1 mark
// Total: 7
def concat[A](x: List[List[A]]): List[A] = error("課題")
// Exercise 8
// Relative Difficulty: 7
// Correctness: 5.0 marks
// Performance: 1.5 marks
// Elegance: 1.5 mark
// Total: 8
def concatMap[A, B](x: List[A], f: A => List[B]): List[B] = error("課題")
// Exercise 9
// Relative Difficulty: 8
// Correctness: 3.5 marks
// Performance: 3.0 marks
// Elegance: 2.5 marks
// Total: 9
def maximum(x: List[Int]): Int = error("課題")
// Exercise 10
// Relative Difficulty: 10
// Correctness: 5.0 marks
// Performance: 2.5 marks
// Elegance: 2.5 marks
// Total: 10
def reverse[A](x: List[A]): List[A] = error("課題")
}

[Scala] 2 not problem

3 not problem@ヒビルテ経由で知ったパズル。
3 not problem@パラメトロン計算機、参照の事。

 で、Scalaで解いてみた。
 最初はお約束としてNodeのcase classとしてAnd, Orとか作ってevalをパターンマッチとかで書いていたんだけど、それだと答えが出るのに30分ぐらい必要だったので、出力の組み合わせ8通りをList[Boolean]で持たせてみた結果、5分ぐらいで答えが出る様になった。多分、8bitなのでIntで表現すればもっと速くなるはずだが。
 もっと賢く解く方法がありそうだが、まぁ解けたのでこれで良しとする。


// Brute-force Solver for "The 2-NOTs problem"
// "Synthesize a black box which computes NOT-X, NOT-Y, and NOT-Z from X, Y, and Z,
// using an arbitrary number of ANDs and ORs, but only 2 NOTs."
// See http://www.inwap.com/pdp10/hbaker/hakmem/boolean.html#item19
//
object TwoNots {
// Possible combination of (X,Y,Z)
val input:List[(Boolean,Boolean,Boolean)] =
for(x <- List(false, true);
y <- List(false, true);
z <- List(false, true)) yield (x,y,z)
// Node : input=(X:Boolean, Y:Boolean, Z:Boolean) => output:Boolean
// output : Node value for "input"
// desc : Description
case class Node(val output:List[Boolean], val desc:String) {
override def toString():String = desc
def ===(that:Any) = that match {
case Node(o, _) => output==o
case _ => false
}
def =/=(that:Any) = !(this === that)
def unary_~ :Node = Node(output.map{b:Boolean => !b}, "~["+this.desc+"]")
def zipWithF(that:Node, f:(Boolean,Boolean)=>Boolean):List[Boolean] = (output zip that.output).map{t => f(t._1, t._2)}
def *(that:Node):Node = Node(zipWithF(that, (_ && _)), this.desc + that.desc)
def +(that:Node):Node = Node(zipWithF(that, (_ || _)), "("+this.desc +"+"+ that.desc+")")
def memberOf_?(ns:List[Node]):Boolean = ns.exists(_ === this)
def const_?():Boolean = (this === TrueNode) || (this === FalseNode)
}
val TrueNode:Node = Node(input.map{in => true}, "T")
val FalseNode:Node = Node(input.map{in => false}, "F")
val X:Node = Node(input.map{in => in._1}, "X")
val Y:Node = Node(input.map{in => in._2}, "Y")
val Z:Node = Node(input.map{in => in._3}, "Z")
val notX:Node = Node((~X).output, "~X")
val notY:Node = Node((~Y).output, "~Y")
val notZ:Node = Node((~Z).output, "~Z")
val XYZ:Node = Node((X*Y*Z).output, "XYZ")
val X_Y_Z:Node = Node((X+Y+Z).output, "(X+Y+Z)")
val XY_YZ_ZX:Node = Node((X*Y+Y*Z+Z*X).output, "(XY+YZ+ZX)")

def append(ns:List[Node], n:Node):List[Node] = if (n.memberOf_?(ns) || n.const_?()) ns else n::ns
def append(ns:List[Node], xs:List[Node]):List[Node] = xs.foldLeft(ns){(as:List[Node], b:Node) => append(as,b)}
// Fixed point of f1,f2,... in fs
def fixedPoint(ns:List[Node], fs:List[List[Node] => List[Node]]):List[Node] = {
val ns1 = fs.foldLeft(ns){(as:List[Node], f:List[Node] => List[Node]) => append(as, f(as))}
if (ns1.length==ns.length) ns else fixedPoint(ns1, fs)
}
def spanAnd(nodes:List[Node]):List[Node] = for(na <- nodes; nb <- nodes if (na =/= nb)) yield (na * nb)
def spanOr(nodes:List[Node]):List[Node] = for(na <- nodes; nb <- nodes if (na =/= nb)) yield (na + nb)
def spanWithAndOr(nodes:List[Node]):List[Node] = fixedPoint(nodes, List(spanAnd, spanOr))
def chooseNodeForNot(nodes:List[Node]):List[List[Node]] = for(n <- nodes) yield append(nodes, ~n)
def satisfy_?(nodes:List[Node]):Boolean = notX.memberOf_?(nodes) && notY.memberOf_?(nodes) && notZ.memberOf_?(nodes)
// Solving tactics:
// (1) Start with initial nodes ( = initialNodes)
// (2) Take any two nodes from initial nodes, and make AND and OR of them to add to lists. Repeat this procedure so that
// all possible combinations have been added. ( = spanWithAndOr(...) = n0)
// n0 contains no NOTs.
// (3) Choose one node from n0, and add NOT of the chosen node to n0. Span the nodes list with ANDs and ORs. ( = n1)
// n1 contains one NOT.
// (4) Do same as above to obtain node list "n2", which contains two NOTs. If the n2 contains all of ~X, ~Y, and ~Z,
// the n2 is a solution.
def solve(initialNodes:List[Node]):List[(List[Node],List[Node],List[Node])] = {
val n0 = spanWithAndOr(initialNodes)
for(n1 <- chooseNodeForNot(n0).map{ns:List[Node] => spanWithAndOr(ns)};
n2 <- chooseNodeForNot(n1).map{ns:List[Node] => spanWithAndOr(ns)} if satisfy_?(n2)) yield
(n2.filter(_ === notX), n2.filter(_ === notY), n2.filter(_ === notZ))
}
def main(args : Array[String]) : Unit = {
println("----"+(new java.util.Date()))
// In principle, we can solve with initial=List(X,Y,Z), however, the answer becomes quite
// ugly because X+Y+Z might be expressed as (X+Y)+Z. In order to see answers expressed in
// a symmetric way, some nodes such as XYZ, XY+YZ+ZX, and X+Y+Z are added to initial nodes.
val initial:List[Node] = List(XYZ, XY_YZ_ZX, X_Y_Z, X, Y, Z)
// val initial:List[Node] = List(X, Y, Z)
for(answer <- solve(initial)) {
println("~X = "+answer._1)
println("~Y = "+answer._2)
println("~Z = "+answer._3)
}
println("----"+(new java.util.Date()))
}
}

13 April, 2008

[Scala]"Scala By Example" 翻訳中

ScalaByExample和訳 @ プログラミング言語 Scala Wiki

 Scala By Example (PDF) の和訳を Wiki 上で開始しました。
 現時点で Chapter 4 がほぼ完了。最終的に翻訳が終わった段階で LaTeX -> PDF 化したいと思っていますが、とりあえずは Wiki 上で英文和文の対訳形式で皆様に翻訳のレヴューを行って頂ければと思います。
 Wiki 上で編集するかコメント欄にご指摘を頂ければと思います。

15 March, 2008

[Scala] Re: "A Scala Tutorial for Java Programmer" 翻訳PDF版

A Scala Tutorial for Java Programmers 和訳PDF

翻訳用に使っていたWikiでご指摘頂いた、翻訳文の間違いを修正したPDFを作り直して差し替えました。URLは以前と変わっていません。

ご指摘を下さった、mko様、どうもありがとうございました。