2012年11月10日土曜日

はじめての育児休暇を終えて - 買い物リストとか

はじめに

初めての子どもが生まれてから 3 週間が経ちました。これまでの育児の感想は、「予想どおりに大変」です。楽でもないけど予想以上に大変でもないです。
これは、日本では長い方であろう 3 週間の育児休暇を取って、育児に集中できたことが大きいと思います。おかげで、いろいろ気づいたこともあるので、買ったものリストをまとめました。

なお、パパ・ママ向けの「Amazon ファミリー」 というサービスに登録すると、お急ぎ便が使い放題になる Amazon プライムが 3 ヶ月間無料になるのでとても便利です。履歴を見たところ、3 週間で 19 件注文してました。クロネコヤマトの人に「なんだこの家? 毎日注文しやがって」と思われていることでしょう。

基本方針

基本的な方針は次の 2 点です。
  1. 「数千円のものはどんどん買う(結果的に無駄になっても気にしない)」
    育児の機会は一生に 1, 2 回(日本の出生率は 1.4)であることを考えると、一回の育児で例え 10 万円無駄遣いしたとしても、生涯支出から見れば誤差の範囲です。買うか買わないか悩む時間がもったいないです。
  2. 「楽をする、不安を無くすことを優先」
    親に時間的・精神的余裕が無いと良い育児はできないと思います。時間を買える、不安をお金で解決できるなら出費しましょう。

買い物リスト

(以下の項目は、私が特に便利だと思ったものです。購入アイテム全てではありません。)

哺乳瓶・哺乳瓶消毒器

はじめに「ピジョン 母乳実感哺乳びんプラスチック240ml」を 3 本買ったのですが、これだと「飲みやすすぎる」ようで母乳を飲むのがうまくならないようなので、「ピジョン 桶谷式直接授乳訓練用 母乳相談室 乳首SSサイズ」を買って、付け替えました。
さらに、最初は煮沸消毒をしていたのですが、これはちょー面倒(大きな鍋を用意 & 大量のお湯を沸かす & 哺乳瓶を熱湯でゆで続ける)だったので、急いで「コンビ 消毒じょ~ず & 衛生ケース HW」を購入しました。レンジに 5 分入れれば良いだけなのでかなり楽になります。

そもそも「煮沸消毒は必要なのか?」という疑問はあるのですが、それは方針 2 に則って余計な不安を抱えないようにしました。

哺乳瓶はなぜ 3 本なのか?

新生児には 3 時間ごとに授乳する必要があります。例えば 0 時(深夜)に授乳すると次は 3 時、6 時、9 時・・・となります。
新生児の親も人間なので、夜は眠いです。授乳のために起きるのは仕方ないとしても、夜間の作業は最小限にしたいところです。哺乳瓶が 1 本しかないと、3 時間ごとに哺乳瓶の洗浄・消毒作業が必要になります。ミルクをあげるのは(ミルクを飲んでる子どもがかわいいので)眠くても耐えられますが、夜間に台所仕事はしたくありません。
2 本だと 0 時の授乳後(1 時ぐらい)に洗浄・消毒したとして、次は(9 時の授乳に備えて) 8 時ごろに洗浄・消毒が必要になります。3 本だと 0 時の授乳したら、次は 11 時ごろに洗浄・消毒すれば良いことになります。

「8 時まで寝られれば十分じゃないか」という意見もあるかもしれませんが、「0 時の時点で全ての哺乳瓶を使い切ってない」「搾乳のために哺乳瓶を 1 本余分に使う」などのケースがあるので、2 本だと危険です。
なお、消毒後の哺乳瓶はそれなりに清潔な場所に保管しなくてはいけないので、「30 本買っておく」という戦略はよほど広い家に住んでないと難しいでしょう。「コンビ 消毒じょ~ず & 衛生ケース」には、ピジョンの哺乳瓶を 3 本入れて保管できます。4 本は入らないような気がします。




粉ミルク

明治 ほほえみ」「森永 はぐくみ」「ビーンスターク すこやか」と試しましたが、違いはよくわかりません。「すこやか」をのみ始めた頃から便秘が直りましたが、同時期に母乳をよく飲むようになったので、どっちが効いたのかわかりません。(Amazon にも「便秘に効いた」というコメントはありますね)

なお、「ビーンスターク」は、大塚製薬の赤ちゃん向けブランド名です。カロリーメイトとポカリスエットの会社かと思うと微妙な気持ちになりますが。




おむつ

病院で使っていた「パンパースのはじめての肌へのいちばん スーパージャンボ 新生児68枚」、試供品をもらった「グーン はじめての肌着 Sサイズ 104枚」と、その上位機種 (?) の「グーン プレミアム 天使の産着 新生児用 62枚入」を使いました。
パンパース (P&G)、GOO.N (大王製紙) ともに、おむつには「プレミアム版」と「スタンダード版」があるんですね。

新生児用おむつ一枚あたりの値段
@ Amazon.co.jp
プレミアム スタンダード
パンパース (P&G) はじめての肌へのいちばん
19円
さらさら コットンケア テープ
14円
GOO.N (大王製紙) プレミアム 天使の産着
24円
はじめての肌着
14円

競合であろう「パンパースのはじめての・・・」よりも 25% も高く値付けした「プレミアム 天使の産着」のプライシングは(皮肉じゃなく)素晴らしいと思います。「天使の産着」というネーミングも素敵です。産着っていうかおむつですけど。触った感じだと、たしかにパンパースよりも厚みがあって柔らかい感じがします。そして、腰の部分の色が何色用意されていて高級感があります。この値段だと普段使いは難しいですが、勝負おむつとして常備しておいても良いかもしれません。
「はじめての肌着」は、薄くてちょっと残念な感じです。おそらく「パンパースのはじめての・・・」を使い続けることになるでしょう。




おむつ処理ポット

コンビ におい・クルルンポイ 紙おむつ処理ポット」は、赤ちゃん用布団セットを買ったときのおまけでもらったのですが、重宝しています。今住んでいるマンションには、いつでもゴミを出せる集積所がないので、おむつ(燃えるゴミ)を数日家の中においておかないといけません。なので、におわないゴミ箱は必須です。




ベビーバス

新生児の育児作業の中で一番難しいのは、何といっても沐浴でしょう。そもそも、ばたばた暴れる新生児を片手で柔らかく支えながら、もう一方の手で洗っていくという作業そのものが困難な上に、緊張感が違います。オムツ替えに失敗したところでせいぜい部屋が汚れるくらいですが、沐浴に失敗(溺れる、頭を風呂桶に打ち付ける、etc.)すると赤ちゃんが死ぬかもしれません。

というリスクを考慮し、「ふかふか ベビーバス グリーン」を購入しました。これは、風呂桶全体が柔らかい(子供用ビニールプールと同じ)ので頭を打ち付けても大丈夫です。そして、ストッパーに座らせたり、足を置いたりすることで体を安定させることができます。ビニールプールなので壊れ(破け)やすい気がしますが、2000 円なので、壊れたらまた買うということで。




加湿器 & オイルヒーター / 温度・湿度計


これから寒くなりますが、風邪をひかれては困ります。子どもにとって良くないのはもちろんですが、病院に連れて行くのは時間・費用がかかり、親へのダメージもあります。部屋の湿度が高いと風邪ウイルスが床に落下して感染しにくくなるという話があるので、「ダイニチハイブリッド式加湿器 HDシリーズ ブルー HD-5012-A」を購入しました。あと、もともと持っていたデロンギ社のオイルヒーターを使ってます。エアコンは空気を乾燥させるし、石油ファンヒーターは空気を汚すので。

加湿器は、「イオン XXX」とか「プラズマ XXX」とかいう余分な機能がついてないという点と、気化式(水を含ませたフィルタに空気を当てて湿度の高い空気を送り出す)が良かったのでこれにしました。

そして「CASIO (カシオ) 置時計 WAVE CEPTOR 電波時計 温度・湿度表示 DQD-700J-8JF」を購入して、室温 20 ~ 23 度、湿度 60% を保つようにしています。




赤ちゃん用体重計

新生児の育児作業は、授乳・沐浴・オムツ替えなど、頭を使わない単純肉体労働です。では何が難しいかというと、「健康に育ってるのかよくわからない」という不安が常にあることです(特に長子の場合)。この不安を解消するには、できるだけデータを計測して、変化を早くキャッチする(キャッチできるという安心感を得る)しかないと思います。
ということで、赤ちゃん用体重計「ベビースケール TH996」を購入しました。新生児の体重は、20~30 g/日増加していくので、100 g 単位でしか計測できない大人用体重計は使えません。

その他に、体温、ミルクの量・回数、尿・便の回数を記録しています。というか、親が医者じゃない場合、これぐらいしか計測できるものがないです。



カーシェア / チャイルドシート

風邪を引いたり、具合が悪くなったら病院に連れていかなくてはいけません。一ヶ月検診などもあります。新生児を電車・バスに載せるのは危険なので、カーシェア (タイムズプラス) に入会し、車を借りて病院まで行ってます。安全性を考えるとタクシーが良いのですが、車の運転に慣れておきたいという、育児とは無関係な個人的な事情があるので、カーシェアにしました。
チャイルドシートは今のところレンタルしています。そのうち買うかもしれません。

手袋(自分用)

子どもが生まれると、水仕事が多くなります。(母乳のために)3 食きちんと食べるようになるので、食事の準備と食器洗い、哺乳瓶の洗浄、沐浴、ベビーバスの洗浄、オムツ替え後の手洗い、洗濯。すぐに指先がひび割れて、水仕事もキーボードを打つのも辛くなりました。台所仕事用の手袋と、オムツ替え用の使い捨て手袋を使ったところ予想外にいろいろ捗ったので、買っておくと良いと思います。数百円ですし。


空き時間にやること

父親にとっての育児休暇とは、産後の大変な時期に家族のサポートをする時期であると同時に、育児と仕事をどう両立していくかを探る時期でもあると思ってます。
数週間の育児休暇の後も育児は続くわけで、例えば、授乳と授乳の間の 2 時間でどの程度の作業ができるか、を把握することは大切だと思います。
技術書を読むとか、コーディングの課題を解く、など手を付けやすい ToDo を用意して、育児とどの程度両立できるのかを調べましょう。

・・・というのが表向きの理由で、精神安定剤としてエンジニアリング的な何かをやってたかった、というのが本当の理由です。
上にも書きましたが、(この時期の)育児は単純肉体労働なので、何か知的労働をしないとバランスが崩れる気がしました。

ちなみに私は、Coursera の Scala コースCompilers コースのビデオを見たり、課題を解いたりしてました。余談ですが、Coursera の資料はとてもよくできている(+ 英語の勉強にもなる)のでオススメです。


最後に

「(特に父親は)育児休暇が取りにくい」という問題はよく聞きますが、なぜでしょうか?
最初に書いたように、日本の出生率は 1.4 です。ということは、育児休暇の期間が 4 週間だとしても、1 人の社員が生涯 (= 35 年 x 52 週/年 = 1820 週) で取得できる育児休暇は 6 週間弱、生涯労働時間のたかだか 0.33% しか影響しないわけです。
0.33% の労働力の減少が企業経営に影響を与えるとは思えないのですが。

育児休暇は少なくとも 3, 4 カ月前には取得時期が確定するので、「突然いなくなって、業務の継続性に問題が」という問題も起こりえません。

同様に日本企業の問題点である「サービス残業」については、「まともに残業代を払うと、人件費が 1.X 倍になって会社がつぶれる」というホンネは理解できます。(だからといってサービス残業を肯定するわけではありません。)
しかし、0.33% の影響しかない育児休暇が取りづらい(取らせたくない)というのは、本当に何でなんですかね?
今の 20~30 代の男性は、「育児なんて男のすることじゃないぜ」という世代ではないはずですしね。

2012年7月15日日曜日

アメイジング・スパイダーマン (3D) の短い感想

桜木町の映画館ブルク 13 で、アメイジング・スパイダーマン (3D) を見た。3D 映画は、アバターに次いで 2 回目。難しいことを考えずに見られるアクション映画としてよくできている。「絶対見なきゃいけない傑作」とは言わないが、ちょっと非日常的な時間を過ごしたいときにおすすめです。

パラレルストーリー

これは前シリーズのパラレルストーリーなんですね。10 年ぐらい前に
ソニーピクチャーズは、シリーズ物に力を入れていく。全く新しい話は当たり外れが読めなくてリスクが大きいが、シリーズ物だと観客数が読みやすい。
という話をどこかで読んだ気がする。シリーズものだと流石にいつまでも続けられないので、同じ設定でパラレルストーリーとして描くのは賢いと思う。

3D

スパイダーマンという話の設定上、マンハッタンで空を見上げたり、ビルの上から見下ろしたり、ビルの間を飛び回ったりするシーン、つまり、画面の深さを描く場面が多い。こういうところに 3D が効いている。アバターのときより 3D の価値があった気がする。
ところで、ストーリーと関係ないけど、普段からメガネをかけているものとしては、裸眼 3D が開発されないかなーと思う。メガネの二重がけはかなり重い。実際、部屋の中で 2 人が話しているような 3D が不要なシーンでは、3D メガネを外してしまった。ちなみにブルク 13 の 3D 方式は XpanD だそうだ。

ストーリー・構成

クモに咬まれた主人公の肉体能力が向上し、おじさんの死をきっかけに正義に目覚め、なんやかんやあって、ボスを倒す、という前シリーズ一作目と全く同じ話。ただし、今作では、肉体的な進化(改良)によって手首から糸を出すのではなく、手首に専用機械を取り付けることで糸が出せるようになっている。疲れると糸が出せなくなるのではなく、機械が壊れると糸がだせなくなる。・・・という機械を作れるくらい、主人公の能力は高いという設定。

スパイダーマンがヒーローとして人々に認知されていく過程の描写が無かったり、学校のいじめっ子といつのまにか友達になってたり、「娘に近づくな」という彼女の父ちゃんの遺言をあっさり破ったり、と構成上突っ込みたくなる点はいくつかある。が、そういう出来を見る映画では無いだろう。「お前らどうせ前シリーズ見てるんだから、スパイダーマンがヒーローになったり、2 作目で彼女と結婚するの知ってるよな。その辺は手抜いてるけど、分かるよな。」ということだろう。

2012年6月30日土曜日

SF はどのように楽しむべきか

読書の幅を広げようと思って SF というジャンルに手を出してみたが、どうにも楽しみ方がわからない。SF を罵倒する意図はないが、どのように楽しむものかを誰かがコメントしてくれたら良いなと期待しつつ、まとめてみる。

ジャンルに依らず、私が好きな構成は「一見関連なさそうな事実がいくつか用意され / 紹介され、最後に全ての事実に整合的なストーリーが提示される」というものだ。

分かりやすい例としてはミステリーが挙げられる。(よくできた)ミステリーでは、事件や証拠 / 証言が提示されるが、それが何を意味するのか最初は分からず、最後に探偵役が何が起こったのかを説明する。ここで読者には「あーそういうことだったのか」という驚きが与えられる。

最近読んだミステリーじゃない例としては「銃・病原菌・鉄」がある。この本では、ニューギニア人の疑問「あなたがた白人は、たくさんのものを発達させてニューギニアに持ち込んだが、私たちニューギニア人には自分たちのものといえるものがほとんどない。それはなぜだろうか?」に、生物学・言語学など様々な分野の知見をもとに答えていく。(誰もがきっと一瞬は考える)「ニューギニア人は馬鹿だから」という誤った根拠や、「文明を発達させたのがヨーロッパだから」という表層的な根拠ではなく、初めての人類がアフリカで誕生してから今までに何が起こったのかを解き明かそうとしていて、多くの驚きを含んだ名作だった。

で、SF だが、「幼年期の終わり」(長編)と、「あなたの人生の物語」(短編集)のうち「バビロンの塔」「理解」「ゼロで割る」を読んでみた。前者はどこかの SF マニアブログで「最高傑作」と言われていたし、後者は「ネビュラ賞」なるものを受賞しているので、そんなに間違った選択ではないはずだ。

以下全力でネタバレしてます。

「幼年期の終わり」は、「オーバーロード」と呼ばれる宇宙人が地球に侵攻してくるが、やけに友好的で、地球を平和にし地球人文化の発達を助けてくれる、というストーリー。「オーバーロードの目的は何なのか」というのが、主題(大きな疑問)だろう。
最後に明かされる答えは「オーバーロードより上位に、オーバーマインドという別の宇宙人がいて、オーバーロードはオーバーマインドに仕えている。オーバーマインドは(ドラゴンボールのセルみたいに)完全体になるために人間を吸収したいが、そのためには人間が幼年期を脱する必要があり、オーバーロードにその手助けをさせていたのだった」だ。

この話が気に入らないのは、オーバーマインドという存在を最後に突然登場させて、それで全てを説明している点だ。伏線がまったくない。それで許されるんだったら「地球人を美味しく食べるために育てていた」でも「危険な星を地球人に探索させるために、地球の科学を発達させていた」でも「オーバーロードは実は未来の地球人で、過去の地球人をかわいがっていた」でも、何でも良いではないか。
一般的に言って、観測されたデータに整合的な解釈をするために、新たな概念を持ち出すのはスジが悪い。新しい概念を使えば、どんなものでも説明できてしまうからだ。オーバーマインドを登場させたいなら、登場させなければいけない理由を伏線として提示するべきだろう。

「バビロンの塔」は、世界設定は多分聖書の時代。その世界の空には「天井」があり、その地方の人々は「天井」の先に何があるのか知りたくて、ものすごく高い塔を作った。で、ある男がついに「天井」に穴をあけて、どんどん上っていったら地上に到達して、「そうかー、天井は地上につながっていたのかー、世界はループしていたのかー」という話。
これもそういう結論になる必然性が感じられない。天井の先が、天国だって、平行世界だって、自宅のトイレだってオチつけれるじゃない。地上につながってると何かおもしろいの?

「バビロンの塔」がそんな感じだったので、「理解」「ゼロで割る」は流し読みしただけ。「理解」は「アルジャーノンに花束を」みたいな話しだと思った。薬を注射された男がどんどん賢くなっていったが、同じように賢い男によくわからないやりかたで殺される。えーと、なんで死んじゃったの? ちゃんと読めばわかるのかな。

「ゼロで割る」は、「好きな人を理解しようと頑張ったら逆に嫌いになっちゃった」みたいな話を、ゲーデルの不完全性定理が生まれた時代背景などに掛けている。「数学の完全性を証明するために頑張ってたら、不完全性が証明されたでござる」みたいな。この話に気に入らない点は特に無いけど、特におもしろくもない。あと余計なお世話だけど、不完全性定理の意義が分からない人はこの話の意味も分からないと思うんだけど、SF の読者ってそんなに理系なんですか?

そんなこんなで SF を楽しむに至っていない。コンピュータ関係者で SF 好きって結構多いように思うので、私も仲間に入りたいんですけどねー。

2012年5月18日金曜日

Yesod の Scaffold を使って Wiki らしきものを作ってみた

Haskell の文法とか理論的なところとかは少しわかったので、具体的なアプリケーションを書いてみようと思ったのだが、今どき具体的なアプリケーションと言ったらウェブアプリに決まってるので、Yesod を触ってみた。
初心者が言うのも何だが、Yesod book は内容が足りないと思う。書籍版では内容が追加されていることを期待したいが、Amazon.com の Look Inside を見る限り、ウェブと同じ内容のようだ。残念。

Yesod book の Examples は、1 アプリケーションが 1 ファイルに記述されていて、読みやすいのかもしれないけど、全く実用的ではない。あと、せっかく scaffold があるんだから、そのサンプルも欲しいところだ。

ということで、"scaffold を使って何か作る" という目的でとりあえず Wiki、というと Wiki ファンに怒られるようなものを作ってみた。どういう点で怒られるかというと、
  • markup なし
  • ページ一覧なし
  • 履歴管理なし
  • ユーザー管理なし
ということで、要するに
  • ページが作れる
  • 編集できる
だけ。今気がついたけど、ページ削除機能作るの忘れてた・・・。

早速 Yesod の scaffold を使ってみる。(Yesod は "cabal install yesod-platform" とか入力すればインストールできるし、インストール情報は結構あるので、ここでは省略する。)

cabal で yesod >= 1 を確かにインストールしておいたのに、最初に yesod init したときは、なぜか 0.8.x が使われてしまったので、yesod version でバージョンを確認しておくと良いだろう。

yesod init で聞かれることは、以下の 3 つ。(以前は Yesod instance にするデータタイプ名も聞かれたけど、無くなったようだ。)
  • 名前: ライセンス表記に使われる
  • プロジェクト名: cabal 名に使われる。全部小文字にして単語をハイフンで繋ぐのが Haskell 的(かな?)
  • データベース: 本格的なアプリを作るなら PostgreSQL を選ぶのだろうが、ここでは SQLite にする
最後に、なんだかよくわからない ASCII art とともに、次に入力すべきコマンドが表示される。

Twitter で「cabal-dev 使っとけ」的な発言を見かけたので、cabal-dev を使うことにする。cabal-dev install は時間がかかる。私の場合は 21 分かかった。かかりすぎ。このすきに珈琲をいれよう。しかし珈琲も冷めるだろう。
yesod --dev devel すると、アプリケーションが起動して http://localhost:3000/ でアクセスできるようになる。こんな感じ。

さて、あらためて hellowiki ディレクトリを見ると、こうなっている。ただし cabal-dev ディレクトリは省略した。
hostname:/tmp/hellowiki% tree
.
├── Application.hs
├── Foundation.hs
├── Handler
│   └── Home.hs
├── Import.hs
├── LICENSE
├── Model
├── Model.hs
├── Settings
│   ├── Development.hs
│   └── StaticFiles.hs
├── Settings.hs
├── config
│   ├── favicon.ico
│   ├── models
│   ├── robots.txt
│   ├── routes
│   ├── settings.yml
│   └── sqlite.yml
├── deploy
│   └── Procfile
├── devel.hs
├── hellowiki.cabal
├── main.hs
├── messages
│   └── en.msg
├── static
│   ├── css
│   │   └── bootstrap.css
│   └── js
├── templates
│   ├── default-layout-wrapper.hamlet
│   ├── default-layout.hamlet
│   ├── homepage.hamlet
│   ├── homepage.julius
│   ├── homepage.lucius
│   └── normalize.lucius
└── tests
├── HomeTest.hs
└── main.hs

ここからの開発方法はいろいろあると思うが、config/route を最初に変更して URL の設計をするのが良いだろう。
hostname:/tmp/hellowiki% cat config/routes
/static StaticR Static getStatic
/auth   AuthR   Auth   getAuth

/favicon.ico    FaviconR GET
/robots.txt     RobotsR GET

/               HomeR   GET POST
/new            NewR    GET POST
/wiki/#String   WikiR   GET POST
/wiki/#String/edit      EditR   GET
WikiR と EditR は 1 つのハンドラにしたいが、そういうことができるのか知らない。

yesod --dev devel を起動しっぱなしにしておくと、ファイルを書き換えるたびにコンパイルされる。Omake の polling mode みたいな感じ。config/route を編集してセーブした直後に、こんな感じでいろいろ怒られる。
Application.hs:27:1: Not in scope: `getNewR'

Application.hs:27:1: Not in scope: `postNewR'

Application.hs:27:1: Not in scope: `getWikiR'

Application.hs:27:1: Not in scope: `postWikiR'

Application.hs:27:1: Not in scope: `getEditR'
Build failure, pausing...
怒られたので、Handler ディレクトリに以下に、New.hs, Edit.hs, Wiki.hs を作り、そして Application.hs に import を追加する。例えば Handler/New.hs はこう書いた。
module Handler.New where

import Data.Text
import Import

import Model.WikiForm

getNewR :: Handler RepHtml
getNewR = do
  (formWidget, formEnctype) <- generateFormPost $ wikiForm Nothing
  defaultLayout $ do
    setTitle "Creating new wiki page"
    $(widgetFile "new")

postNewR :: Handler RepHtml
postNewR = do
  ((result, _), _) <- runFormPost $ wikiForm Nothing
  case result of
    FormSuccess (title, body) -> do
      runDB $ insert $ Wiki title $ unTextarea body
      redirect $ WikiR $ unpack title
    _ -> undefined -- エラー処理してない・・・
更にいろいろ怒られるはずなので、がんがん直していく。なお、"yesod --dev devel" はいかなる変更も完璧に拾ってくれるわけではないので、困ったときは "rm -rf dist" して "yesod --dev devel" を再起動するとよい。

試行錯誤を繰り返して、"yesod --dev devel" が文句を言わなくなったら、今作ったページにアクセスしてみよう。例えば http://localhost:3000/new とか。「Haskell ではコンパイルが通ればほぼバグは無い」と言われているのだから、これで動くはずだ。


動かない・・・。"yesod --dev devel" の出力を見ると、
Devel application launched: http://localhost:3000
GET /new
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Exit code: ExitFailure 11
となっている。

これが今回の最大の敵だった。"ExitFailure 11" では何もわからないし、Yesod book にも何も書いてない (*1)。困ったなと思って Twitter でつぶやいたら天の助けが。


これが正解で、hellowiki.cabal を編集すれば動くようになった。まぁアプリケーションが落ちる気持ちは分からなくはないが、ExitFailure 11 からそれは推測できない、絶対に。(*2)

このバグ(yesod のバグ?)さえなければ、あとはコンパイラのメッセージを見つつ、型合わせをしていけば動くようになる。

というわけで、Yesod の Scaffold を使ってみるという目的が達成できたので、Wiki まがいのアプリ自体には改良の余地が広大に残っているが、これで終わったことにする。コードはこちら。コメント・批判は大歓迎。
完成画面。なんと、"Pages"と"About"はどこにもリンクしていない・・・。

正直、Yesod の中身は未だにほとんど理解できていない。特にフォーム周りとか。Yesod book にも記述があるけど、これだけじゃねぇ・・・。しかし、全然理解していなくても、型を合わせていくと動いてしまうところが Haskell の凄い(怖い)ところ。ジグゾーパズルやってる感じ。が、それで良いはずはないので、ドキュメントの充実を望みたい。

個人的には、
  • テストの書き方
  • Shakespearn Templates の使い方。hamlet でコメントどう書くのか、とか。
を知りたい。あと、Hackage で、例えば getBy404 が出てこないのはそういうものなんだろうか?

ここまで書いたものを読み返してみると、文句ばっかり書いているようだが、Yesod は素晴らしいフレームワークだと思う。
Rails を最初に触ったときは、「フルスタックフレームワーク」というものに感動したものだが、Yesod はフルスタックフレームワークの手軽さに加えて、Haskell の type check を活かした堅牢性(というのか?)にさらに感動できる。
リンク切れを起こさない routing や、テンプレート内の変数が提供されること、XSS が起こらないことが、全て "type check" によって保証されている。Rails では、テストで確認するか、運を天に任せるしかできなかったことだ。(私の Rails の知識は 3 ~ 5 年前で止まってるので間違ってたらすみません。)

次は、実用的なアプリを作って見よう。

(*1)
と思ったら、ここに軽く書いてあった。

(*2)
よく見ると、"yesod --dev devel" が最初に警告していた。
WARNING: the following source files are not listed in exposed-modules or other-modules:
./Handler/Edit.hs
./Handler/New.hs
./Handler/Wiki.hs
./Model/WikiForm.hs
exposed-modules と other-modues との違いは分かってない。

2012年3月31日土曜日

同窓会サービスのセキュリティ問題

前職の会社から同窓会ネットワークへの招待メールが届いた。退職はしたものの本当に素晴らしい会社・同僚・文化だと思っているので、同窓会に招待してもらったは大変うれしい。うれしいのだが・・・。

これが招待メール。社名と私の個人情報に関わる箇所は赤でマスクした。

"Alumni Network website" リンクをクリックすると、secure.imodules.com というサイトが開き、名前と旧従業員番号の入力を求められる。

問題は、このメールにもそのサイトにも、前職の会社(以下 G)との関係を証明するものが何もないということだ。メールやサイトのデザインは、見る人が見れば G 社のものだとすぐに分かるが、こういうものはほぼコスト0で完全にコピーできる。
サイト自体にはサイト証明書がついていて、Imodules Software Inc. という会社が運営していることがわかる。この会社が同窓会サービスを請け負ってるんだと思うが、そんな話は在職時には聞いたことがないので、Imodules Software に個人情報を渡していいのかどうか判断できない。

「あなたの従業員ナンバーを入力する必要があります」と書いてある。私の ID 確認も結構だが、そっちの(サービスの) ID も確認させてくれと言いたい。

私が退職したことや私の前職を知ってる人はそれなりにいるので、このタイミングでこういう phishing を仕掛けてるのはありそうな話だ。

まぁそうはいっても、私はこのサイトに登録することになるだろうと思う。前職の人とのネットワークは重要だし、(証拠は何も無いが)これが phishing であることはない「気がする」。G 社に電話して「これ本物ですか?」と聞くのもめんどうだ。

しかし、これこそが悪意の無い insecure なサービスによる最大の害であって、こういう経験を積むことで「セキュリティ、セキュリティって言ってるけど、そんなうるさいこと言わなくても全然問題ないよね」と感覚が麻痺して、いつの日か本当の詐欺にあうことになる。そういうユーザーを増やすことに加担している。

そもそもこれをセキュアにするのは難しい話ではない。G 社のサイト証明書つきのウェブサーバに「G 社は同窓会サービスを Imodules Software Inc. に委託しています。下のリンクをクリックすると Imodules Software Inc. のサイトにジャンプします」というページを用意し、招待メールからこのページにリンクすれば良い。
そうすれば、(G 社のサイトが不正に改竄されていない限り)Imodules の同窓会サービスの正当性を確認できる。

なんてことをここに書かずに、G 社に言うべきなんだろうけど。同窓会サービスに関するコンタクトを見つけたからメールするべきなのか? それもなぁ・・・。

zshとscreenを少し便利にした。主にgit向け。

メインの開発環境が git になり、かつ複数の作業を平行してやってるため、「あ、しまった。この変更はこのブランチじゃなかった」と今週だけで 3 回は叫び、git stash -> git checkout XXX -> git stash pop を繰り返してしまった。

会社の他の人のターミナルを盗み見ると、プロンプトの一部にブランチ名を表示している人が多かったので、やり方を調べてブログにまとめよう・・・と思ったが、すでに A Zsh prompt for Git users という素晴らしい記事があり、このとおりに設定できてしまったので、書くことがない。

現在のターミナルはこんな感じ。zsh であることを見せびらかすように、ブランチ名は右プロンプトに、目立つように赤で表示させた。ついでに時刻も表示させた。「この処理何分かかったっけ」と考えることがたまにあったので。普段は幅 180 カラムで使ってるので、これでも全然狭くない。
ついでに GNU screen のステータスバーの表示も少し見直した。直前のコマンドが "cd" なら異動先のディレクトリ名を表示、そうじゃなければコマンド名と第一引数を表示するようにした。ただし第一引数がハイフン '-' で始まる場合は引数は表示しない。(今考えるとこの特別扱い不要だな)

まず、GNU screen のステータスバーを表示するように .screenrc を設定する。
hardstatus alwayslastline "[%02c] %-w%{=b bw}%n %t%{-}%+w"

次に、preexec_update_screen_status というファイルを作り、
emulate -L zsh
local -a cmd; cmd=(${(z)2})
case $cmd[1] in
  cd)
      if [[ $#cmd -eq 2 ]]; then
        echo -n "^[k$cmd[2]^[\\"
      else
        echo -n "^[k$HOME^[\\"
      fi
      ;;
  *)
      if [[ $#cmd -eq 2 && ! ("$cmd[2]" =~ "-.*") ]]; then
        echo -n "^[k$cmd[1] $cmd[2]^[\\"
      else
        echo -n "^[k$cmd[1]^[\\"
      fi
      ;;
esac
と書いておく。"^[" はエスケープシークエンスなので注意。Emacs だと Ctrl+Q ESC で入力する。(このファイル自体もどこかからコピーした気がするが忘れてしまった)

ほとんど分かってないが、"^[k" と "^[\" で囲まれた文字列は、(普通に出力されるのではなく)ウィンドウのタイトルに設定されるようだ。で、hardstatus で設定した "%t" が、「ウィンドウのタイトルをステータスラインに表示する」という意味らしい。(余談だけど、エスケープシークエンス・コントロールシークエンスは複雑すぎて覚える気にならない)

最後に、この preexec_udpate_screen_status 関数を、コマンド実行直前に評価するように、以下の内容を .zshrc に追加する。
typeset -ga preexec_functions
case "$TERM" in
screen)
  preexec_functions+='preexec_update_screen_status'
  ;;
esac
$TERM で場合分けしないと、screen 使ってないときに表示が乱れる。

というようにツールを整備したので、来週は効率10倍で働いてコミット10倍します。あ、まだエイプリルフールじゃなかったか。来週は効率1.05倍で働いてコミット1.05倍します。[追記: 日本ではすでに 4/1 だった。]

zsh とか screen とかもっと便利に使える気がするが、あとどれだけ時間をかければ何が得られるのか分からないので時間のかけようがないのがつらいところだ。

2012年3月19日月曜日

Splay Tree への insert の amortized cost が O(log N) の証明

Purely Functional Data Structures の 5.4 で、Splay Tree に要素を insert すると、一回あたりの最悪計算量は O(N) ですが、amortized cost は O(log N) になる、という話の証明です。

まず insert の定義を確認します。

insert x t = let (a, b) = partition x t
             in T (a, x, b)

(partition は長いので省略します。テキスト (Purely Functional Data Structures) の P.50 or P.198 を見てください。)
partition のポイントは二段ごとに再帰していくこと。あるノードについて partition が呼ばれると、その中で、孫ノードに対して再帰的に partition が呼ばれます。なので、insert の計算量は O(N) となりますが、amortized cost が O(log N) となることを証明します。

始めにいくつか関数を定義します。
  • #t := t (tree) に含まれるノードの数 + 1
  • φ(t) := log(#t)
  • Φ(t) := sum $ map φ $ nodes t (t に含まれる全てのノードに対して φ を適用した結果の和)
次に partition の actual cost を T(t)、amortized cost を A(t) とすると、ポテンシャル関数 Φ(t) を使って次のような関係が成立します。
A(t) = T(t) + Φ(a) + Φ(b) - Φ(t)
ここで、a, b は、t について呼び出した partition の結果である sub-tree です。

補題として、A(t) <= 1 + 2φ(t) = 1 + 2log(#t) を示します (Theorem 5.2)。これが成り立つとすると、挿入後の tree を t' として、
[amortized cost of insert] = A(t) + 1 + Φ(t') - (Φ(a) + Φ(b))     (*1)
                                        = A(t) + 1 + φ(t')
                                        <= 1 + 2log(#t) + 1 + log(#t + 1)
                                         2 + 3log(#t)
(*1) insert の amortized cost は、partition の amortized cost と、データコンストラクタの適用と、ポテンシャルの変化の和
となり、insert の amortized cost が O(log N) であることがわかります。

さて、partition の再起呼び出しには zig-zig ケースと zig-zag パターンの 2 つがあります。(冗長に書くと、左の子を辿るのを zig, 右の子を辿るのを zag として、zig-zig, zig-zag, zag-zig, zag-zag の 4 パターンありますが、zig-zig と zag-zag, zig-zag と zag-zig は対称な操作なのでまとめて、zig-zig と zig-zag だけを考えます)

zig-zig ケースを考えます。下の図のようなツリーを考え、partition pivot s の結果 sub-tree u が二つに分かれて、a, b になったとします。

partition の amortized cost A(s) を展開していきます。
A(s) = T(s) + Φ(a) + Φ(s') - Φ(s)                                            (*2)
        = 1 + T(u) + Φ(a) + Φ(s') - Φ(s)                                     (*3)
        = 1 + A(u) - Φ(a) - Φ(b) + Φ(u) + Φ(a) + Φ(s') - Φ(s)   (*4)
        = 1 + A(u) - Φ(b) + Φ(u) + Φ(s') - Φ(s)                          (*5)
(*2) A の定義より
(*3) T(s) は、u に対して partition を呼んだコスト + 1
(*4) A の定義を T について整理したものを使って、T(u) を A(u), Φ, φ に置換
(*5) Φ(a) はキャンセル
ここで、Φ(s) と Φ(s') を φ を使ってできる限り展開すると、
Φ(s) = φ(s) + φ(t) + Φ(u) + Φ(c) + Φ(d)
Φ(s') = φ(s') + φ(t') + Φ(b) + Φ(c) + Φ(d)
となります。(b, c, d, u は内部構造が分からないので、φ で展開できないことに注意)

これを元の式に代入して整理すると、
A(s) = 1 + A(u) + φ(s') + φ(t') - φ(s) - φ(t)
となります。

u は s より小さい木なので、帰納法の過程より A(u)  <= 1 + 2φ(u) を代入すると、
A(s) <= 2 + 2φ(u) + φ(s') + φ(t') - φ(s) - φ(t)
u は t より小さく、s' は s より小さいので、φ(t) を φ(u) に置き換え、φ(s) を φ(s') に置き換えても式の大小関係は崩れない(φ(t), φ(s) の符号は負であることに注意)ので、
A(s) <= 2 + 2φ(u) + φ(s') + φ(t') - φ(s') - φ(u)
        < 2 + φ(u) + φ(t')
#u + #t' < #s なので、Lemma 5.1 (ここでは省略。テキスト参照のこと) より、
A(s) < 1 + 2φ(s) 
となり、証明終了となります。

zig-zag ケースの場合も、下図を参考にして式を展開をしていくと同じ結果が得られます。


数式を blog で書くのつらいなぁ・・・。

2012年2月27日月曜日

IT 業界で働くとは - エルピーダの会社更生法適用申請を機に思ったこと

エルピーダを存続させた理由として正当化できる(と私が思える)のは雇用確保だけだ。無駄な努力に終わったが、政府が雇用のために動く(= 税金を使う)ことは間違ってはいないと思う。公的資金注入の理由にはたして雇用確保があったかどうかは知らないが、「産業のコメ」がこじつけなのは知ってる。

でも同時に、人材の流動性がもっと高ければ、政府が雇用について悩む必要もなく、税金を 280 億円も失うこともなかっただろうとも思う。

かつて Andrew Grove (Intel) は "Only the paranoid can survive" と言った。Steve Ballmer (Microsoft) は「変化が嫌いな奴は(IT 業界を辞めて)食品業界へ行け」と言った。

IT 業界は変化が大きくて速い。ということは、会社が大成功する可能性も高いし、潰れる可能性も高い。誰も悪くなくても会社は潰れるし、産業は衰退する。エルピーダは経営戦略に問題はあっただろうが、オリンパスと違って罪を犯してはいない。それでも会社は潰れる。

IT 業界で働くのであれば、「誰も何も悪いことをしなくても会社が潰れるリスク(が高いこと)」を覚悟しなくてはいけない。

潰れるリスクを覚悟するというのは、具体的には、会社が潰れても生きていけるようなスキル・人脈を構築するという事だ。会社が潰れないように政府が動いてくれることを期待しないということだ。

これは自己責任論とは異なる話だ。今の日本政府は、イラクに飛び込んでいった日本人 3 人を助けることはできても、会社や産業を救うことはできない。3 年前のエルピーダメモリに 400 億円を投じることはできたが、今後はもっと厳しくなるだろう。だから、自己責任であるべきかどうかという議論はそもそも成立しない。

もし IT 業界への就職を考えている学生とかが身近にいたら、こういうことを伝えてあげたいと思う。(若干余談だが、ITに近づきつつある元家電業界の人たちは、こういうことをわかってるのか心配だ。余計なお世話か。だといいな。) 会社が潰れるのは資本主義社会では当然だし、産業は移ろっていくものだが、そのときに「仕事がなくなってしまう」「雇用のために税金を投入しよう」という議論が起こらないようにしたい。

Disclaimer: 私は IT 業界以外で働いたことはないですし、このエントリに他業界を貶める意図はないので、「XXX 業界の方が大変なんだ!」的なコメントはご遠慮ください。

2012年2月14日火曜日

コミュニケーションには助走が必要


会社で A と B とが会ったとする。
A: こんにちは
B: こんにちは
というのが普通の挨拶だろう。「おはよう」かもしれないけど。

英語だとこうなる。
A: Hi
B: Hi. How are you?
A: Fine. How are you?
B: Not bad. Thanks.
A: How was the weekend? (週の前半の場合。後半だと What's your plan for the weekend?)
B: XXX
という感じ。日本語だと 1 往復で、英語だと 3 往復。

「いやいや、日本語でももっと長く話すこともある」とか「Hiだけで終わることもある」という意見はあるだろう。それはそうかもしれない。統計を取ったわけではないので、正確なところはわからないが、自分の感覚・経験では英語のほうが長い。

どれぐらい長いかというと、日本語の挨拶の間合いと移動速度で会話をスタートすると、"How are you?" の辺りですれちがい終わってしまって、その後の会話が続けられないほど、英語は長い。

そして、ここまでの会話がほぼテンプレート化されていることが重要だ。週末にしたことや週末の予定を聞くのは普通なので、いつでも誰にでも聞ける。(ただし、逆に言うと、週末にしたことやプランを常に聞かれる可能性があるので、それなりの回答を常に準備しておく必要がある。毎週毎週「ずっと寝てた」のでは、「こいつ大丈夫か?」と思われるだろう。)

このようにテンプレート化されている会話が長いと、その後の会話を続けやすくなるようだ。週末の話題から会話を広げられるという利点もあるが、会話のリズムができあがってくるという感覚が個人的にはある。助走をつけている感じ。

日本語でもこういうテンプレートがあれば良いと思う。伝説のスーパー大阪人は、「どないでっか」「ぼちぼちでんなー」という会話から商売の話につなげたといわれているが、一般人には高度すぎる技だ。
普通の人々が使える会話のテンプレートがあれば、コミュニケーションを取りやすくなるだろう。

余談だが、日本語ができる外国人が "How are you?" のつもりで「調子どう?」とたまに聞いてくるが、これは日本語会話のテンプレートに入っていないので答えにくいのであった。

2012年1月15日日曜日

Red-Black Tree の balance 処理の最適化 (をしても速くならない・・・)

Purely Functional Data Structures の Chapter 3.3 では Red-Black trees を扱っている。
Red-Black tree は、各ノードを赤色と黒色に塗り分けて、それが適当な条件を守るように木を変形させることで balanced tree を実現するアルゴリズム。(ここでは、insert (木に要素を追加する)と member (木に要素が含まれているかチェックする)しか扱っていない。)

Exercise 3.10 はバランス処理の最適化に関する問題のようだが、試してみても速くならなかった、というエントリ。ソースコードは github にアップした。

insert は、木を下りながら要素を追加すべき場所を求め、木を上りながら red-black をチェックし、木をバランスさせていく、という処理を行う。本に載っている実装はこんな感じ。ins 関数が、x と v との大小比較をして挿入する場所を求め、最後に balance 関数を呼んで木をバランスさせている。

data Color = R | B deriving Show
data RedBlackSet a = E0 | T0 Color (RedBlackSet a) a (RedBlackSet a) deriving Show

balance :: Color -> RedBlackSet a -> a -> RedBlackSet a -> RedBlackSet a
balance B (T0 R (T0 R a x b) y c) z d = T0 R (T0 B a x b) y (T0 B c z d)
balance B (T0 R a x (T0 R b y c)) z d = T0 R (T0 B a x b) y (T0 B c z d)
balance B a x (T0 R (T0 R b y c) z d) = T0 R (T0 B a x b) y (T0 B c z d)
balance B a x (T0 R b y (T0 R c z d)) = T0 R (T0 B a x b) y (T0 B c z d)
balance color a x b = T0 color a x b

instance Ord a => Set RedBlackSet a where
  empty = E0

  member _ E0 = False
  member x (T0 _ a y b)
    | x < y     = member x a
    | x > y     = member x b
    | otherwise = True

  insert x s = T0 B a y b
    where
      ins E0 = T0 R E0 x E0
      ins n@(T0 color l v r)
        | x < v     = balance color (ins l) v r
        | x > v     = balance color l v (ins r)
        | otherwise = n

      T0 _ a y b = ins s

Exercise 3.10 の主旨は「balance 関数の無駄なチェックを無くせ」というもの。
例えば、左の子に新要素を挿入したなら、右の子は全く変化しないので、右の子の色をチェックする必要はない。Ex. 3.10 (a) の回答が次のコード。balance が lbalance と rbalance とに分かれている。
balance に含まれるパターンマッチの数が 5 で、lbalance, rbalance は 3 なので、分岐の数が減り、速くなると思われる。

data RedBlackSet1 a = E1 | T1 Color (RedBlackSet1 a) a (RedBlackSet1 a) deriving Show

lbalance :: Color -> RedBlackSet1 a -> a -> RedBlackSet1 a -> RedBlackSet1 a
lbalance B (T1 R (T1 R a x b) y c) z d = T1 R (T1 B a x b) y (T1 B c z d)
lbalance B (T1 R a x (T1 R b y c)) z d = T1 R (T1 B a x b) y (T1 B c z d)
lbalance color a x b = T1 color a x b

rbalance :: Color -> RedBlackSet1 a -> a -> RedBlackSet1 a -> RedBlackSet1 a
rbalance B a x (T1 R (T1 R b y c) z d) = T1 R (T1 B a x b) y (T1 B c z d)
rbalance B a x (T1 R b y (T1 R c z d)) = T1 R (T1 B a x b) y (T1 B c z d)
rbalance color a x b = T1 color a x b

instance Ord a => Set RedBlackSet1 a where
  empty = E1

  member _ E1   = False
  member x (T1 _ a y b)
    | x < y     = member x a
    | x > y     = member x b
    | otherwise = True

  insert x s = T1 B a y b
    where
      ins E1        = T1 R E1 x E1
      ins n@(T1 color l v r)
        | x < v     = lbalance color (ins l) v r
        | x > v     = rbalance color l v (ins r)
        | otherwise = n

      T1 _ a y b = ins s

Exercise 3.10 (b) は、この考え方をもう一段拡張して、孫のチェックも無くせ。変更していないパスの色をチェックするな。といっている。
ins v の再帰呼び出しが終わった時点で、v の左右の子どちらが変更されたのかわかるので、[lr]balance の内部で孫を 2 人チェックするのは確かに無駄だ。

ins が、左右どちらを変更したかという情報も返してやれば、それを使うことで孫のパターンマッチを省けるだろう、と思ってこういう実装をしてみた。

data RedBlackSet2 a = E2 | T2 Color (RedBlackSet2 a) a (RedBlackSet2 a) deriving Show

llbalance :: Color -> RedBlackSet2 a -> a -> RedBlackSet2 a -> RedBlackSet2 a
llbalance B (T2 R (T2 R a x b) y c) z d = T2 R (T2 B a x b) y (T2 B c z d)
llbalance color a x b = T2 color a x b

lrbalance :: Color -> RedBlackSet2 a -> a -> RedBlackSet2 a -> RedBlackSet2 a
lrbalance B (T2 R a x (T2 R b y c)) z d = T2 R (T2 B a x b) y (T2 B c z d)
lrbalance color a x b = T2 color a x b

rlbalance :: Color -> RedBlackSet2 a -> a -> RedBlackSet2 a -> RedBlackSet2 a
rlbalance B a x (T2 R (T2 R b y c) z d) = T2 R (T2 B a x b) y (T2 B c z d)
rlbalance color a x b = T2 color a x b

rrbalance :: Color -> RedBlackSet2 a -> a -> RedBlackSet2 a -> RedBlackSet2 a
rrbalance B a x (T2 R b y (T2 R c z d)) = T2 R (T2 B a x b) y (T2 B c z d)
rrbalance color a x b = T2 color a x b

data Modified = LChild | RChild | NoChild

instance Ord a => Set RedBlackSet2 a where
  empty = E2

  member _ E2   = False
  member x (T2 _ a y b)
    | x < y     = member x a
    | x > y     = member x b
    | otherwise = True

  insert x s = T2 B a' y b'
    where
      ins E2        = (T2 R E2 x E2, NoChild)
      ins n@(T2 c l v r)
        | x < v     = case ins l of
                        (insl, LChild)  -> (llbalance c insl v r, LChild)
                        (insl, RChild)  -> (lrbalance c insl v r, LChild)
                        (insl, NoChild) -> (T2 c insl v r, LChild)
        | x > v     = case ins r of
                        (insr, LChild)  -> (rlbalance c l v insr, RChild)
                        (insr, RChild)  -> (rrbalance c l v insr, RChild)
                        (insr, NoChild) -> (T2 c l v insr, RChild)
        | otherwise = (n, NoChild)

      (T2 _ a' y b', _) = ins s

・・・が、たしかに {ll,lr,rl,rr}balance 内のパターンマッチは減るが、ins 内にもう一段パターンマッチが増えてしまっている。これでは速くならん気がする。

別のやり方も考えてみた。そもそも ins のパターンマッチで孫まで見てしまえば、どのパスが変更されるのかわかるはず。

data RedBlackSet3 a = E3 | T3 Color (RedBlackSet3 a) a (RedBlackSet3 a) deriving Show

llbalance3 :: Color -> RedBlackSet3 a -> a -> RedBlackSet3 a -> RedBlackSet3 a
llbalance3 B (T3 R (T3 R a x b) y c) z d = T3 R (T3 B a x b) y (T3 B c z d)
llbalance3 color a x b = T3 color a x b

lrbalance3 :: Color -> RedBlackSet3 a -> a -> RedBlackSet3 a -> RedBlackSet3 a
lrbalance3 B (T3 R a x (T3 R b y c)) z d = T3 R (T3 B a x b) y (T3 B c z d)
lrbalance3 color a x b = T3 color a x b

rlbalance3 :: Color -> RedBlackSet3 a -> a -> RedBlackSet3 a -> RedBlackSet3 a
rlbalance3 B a x (T3 R (T3 R b y c) z d) = T3 R (T3 B a x b) y (T3 B c z d)
rlbalance3 color a x b = T3 color a x b

rrbalance3 :: Color -> RedBlackSet3 a -> a -> RedBlackSet3 a -> RedBlackSet3 a
rrbalance3 B a x (T3 R b y (T3 R c z d)) = T3 R (T3 B a x b) y (T3 B c z d)
rrbalance3 color a x b = T3 color a x b

instance Ord a => Set RedBlackSet3 a where
  empty = E3

  member _ E3   = False
  member x (T3 _ a y b)
    | x < y     = member x a
    | x > y     = member x b
    | otherwise = True

  insert x s = T3 B a' y b'
    where
      ins E3        = T3 R E3 x E3
      ins n@(T3 c E3 v E3)
        | x < v     = T3 c (ins E3) v E3
        | x > v     = T3 c E3 v (ins E3)
        | otherwise = n
      ins n@(T3 c l@(T3 _ _ lv _) v E3)
        | x < lv    = llbalance3 c (ins l) v E3
        | x < v     = lrbalance3 c (ins l) v E3
        | x > v     = T3 c l v (ins E3)
        | otherwise = n
      ins n@(T3 c E3 v r@(T3 _ _ rv _))
        | x < v     = T3 c (ins E3) v r
        | x < rv    = rlbalance3 c E3 v (ins r)
        | x > rv    = rrbalance3 c E3 v (ins r)
        | otherwise = n
      ins n@(T3 c l@(T3 _ _ lv _) v r@(T3 _ _ rv _))
        | x < lv    = llbalance3 c (ins l) v r
        | x < v     = lrbalance3 c (ins l) v r
        | x < rv    = rlbalance3 c l v (ins r)
        | x > rv    = rrbalance3 c l v (ins r)
        | otherwise = n

      T3 _ a' y b' = ins s

・・・しかしこうすると、ins のパターンマッチの場合分けが増えてしまう。当たり前だが。これも速くなりそうにないなぁ。

ということで、実測してみた。1 から 1000 のソート済みリストとランダムなリストを作り、それを RedBlackSet{,1,2,3} に挿入したあと、0 が含まれているかどうかをチェックする。(このチェックがないと遅延評価のために木の生成処理が最後まで評価されない気がしたが、自信はない。)

結果は以下の通りで、全然速くなってないことがわかった。予想どおりだけど。ソート済みリストに対して RedBlackSet1 が RedBlackSet より遅くなるのは予想外。そしてやはり理解できない。


ソート済みリスト ランダムなリスト
RedBlackSet (オリジナルの実装) 2.44 2.23
RedBlackSet1 (3.10 (a) の回答) 2.75 1.62
RedBlackSet2 2.53 2.43
RedBlackSet3 3.47 2.59


この問題の数ページ前に、「Exercise 3.10 の最適化でこの Red-Black tree の実装は飛ぶように速くなる」と書いてあるのだけど、何か見落としてるのだろうか・・・。それとも、Standard ML で実装するとなにか違いが出るのだろうか・・・?

2012年1月8日日曜日

Criterionを使ったHaskellコードのパフォーマンス計測

LeftistHeapは、要素を挿入するときや削除するときに、右か左の部分木に対して再帰的な操作を行う。この時、左右どちらで再帰しても正しく動作するが、パフォーマンスに影響を与える。という話。

Haskell にはパフォーマンス計測用のフレームワーク Criterion があるので、これを使って調べてみた。

まず、計測対象のプログラムを準備する。デフォルトの実装では右の子供に対して再帰しているので、比較用に、左に再帰する関数 insertL と deleteMinL を実装する。(計測用に使うだけなので、LeftistHeapモジュールの外で実装したかったが、データコンストラクタでパターンマッチしないといけない、つまり、LeftistHeapの内部構造を知らないといけないので、LeftistHeap内で実装するしかない。)コードは github にアップした。

次に、計測用のコードを書く。
main = defaultMain [ bench "Left"  $ whnf (findMax insertL deleteMinL) ns,
                     bench "Right" $ whnf (findMax insert deleteMin) ns ]
whnf は、計測対象の関数(を 1 引数にカリー化したもの)とその関数への引数を取って、Weak Head Normal Form に簡約する。同様に Head Normal Form まで簡約する nf もある。

計測用のコード全体は以下の通り。
import Criterion.Main
import LeftistHeap
import System.Random

type Rand a = StdGen -> (a, StdGen)

randomInts :: Int -> Rand [Int]
randomInts 0 rng = ([], rng)
randomInts n rng = let (x, rng') = next rng
                       (xs, rng'') = randomInts (n - 1) rng'
                    in (x:xs, rng'')

findMax :: (Int -> LeftistHeap Int -> LeftistHeap Int) ->
(LeftistHeap Int -> LeftistHeap Int) -> [Int] -> Int
findMax ins del ns = findMax' $ foldr ins empty ns
  where
    findMax' :: LeftistHeap Int -> Int
    findMax' h = let e  = findMin h
                     h' = del h
                  in if isEmpty h' then e else findMax' h'

main :: IO ()
main = do stdGen <- newStdGen
          let (ns, _) = randomInts 1000 stdGen
           in defaultMain [ bench "Left"  $ whnf (findMax insertL deleteMinL) ns,
                            bench "Right" $ whnf (findMax insert deleteMin) ns
                          ]  
これを ghc でコンパイルして実行する。当然だが、パフォーマンスの計測なので、ghciで実行しても意味ない。
> ghc -O --make PerfLHeap.hs && ./PerfLHeap
warming up
estimating clock resolution...
mean is 4.512760 us (160001 iterations)
found 3996 outliers among 159999 samples (2.5%)
3234 (2.0%) high severe
estimating cost of a clock call...
mean is 81.74308 ns (34 iterations)
found 6 outliers among 34 samples (17.6%)
1 (2.9%) high mild
5 (14.7%) high severe

benchmarking Left
mean: 18.68245 ms, lb 18.60457 ms, ub 18.83024 ms, ci 0.950
std dev: 532.2852 us, lb 332.5989 us, ub 909.0032 us, ci 0.950
found 14 outliers among 100 samples (14.0%)
2 (2.0%) high mild
12 (12.0%) high severe
variance introduced by outliers: 22.891%
variance is moderately inflated by outliers

benchmarking Right
mean: 1.579322 ms, lb 1.574251 ms, ub 1.589812 ms, ci 0.950
std dev: 35.82818 us, lb 20.36439 us, ub 58.87072 us, ci 0.950
found 6 outliers among 100 samples (6.0%)
6 (6.0%) high severe
variance introduced by outliers: 16.123%
variance is moderately inflated by outliers
赤字のとおり Right の方が速いことがわかった。
Leftist Heap は、左の子の rank (一番右末端までの深さ) を、右の子より長く保とうとするので、左の部分木の方が高くなる。insertL, deleteMinL は、右の子に対して再帰的な操作 (merge) を行うことで、木をバランスさせる方向に働くため、Right の方が速い、と直感的には理解した。

Criterion は、単にプログラムの経過時間を測るだけではなく、もっと賢いことをしてくれるようだ。
最初にクロックの分解能や、クロックの値を取得するコストを調べ、これらの測定誤差に影響されないようにするには、測定対象の関数を何回実行すれば良いかを計算し、その回数実行する。
上記の結果は、Thinkpad X121e Core i3 2367M 1.4GHz + Fedora 16 での計測結果だが、分解能が 4.5us で、呼び出しコストが 81ns だった(青字の部分)。Left については、計測 1 回ごとに計測対象 "whnf (findMax insertL deleteMinL) ns" を 1 回呼び出し、これを 100 回ループする。
Right については、計測 1 回ごとに計測対象 "whnf (findMax insert deleteMin) ns" を 3 回呼び出し、これを 100 回ループする。

平均値と標準偏差、そしてそれぞれの信頼度 95% での上限値と下限値(統計素人なので用語がおかしいかも)を表示する。外れ値が標準偏差にどのくらい影響しているかも表示する。

time コマンドを使って、プロセス開始から終了までの経過時間を測定するのと比べると、だいぶ賢く計測するので、「ちゃんと計測したぜ」感が出せる。

ただし、計測対象のコードを正しく書くことが当然必要。今回の場合、Leftist Heap に突っ込む乱数データの生成が測定対象に入っているのかいないのかよくわからない(遅延評価なので)。どっちにしろ Right が優位なのは変わらないが。

シーシェパードという愉快犯型テロリストの傾向と対策 - 第1版

9.11が起こったあと、「テロとの戦争」という言葉を聞くようになった。従来の国と国との戦争は、代表者(政府)と交渉したり、代表者を殲滅すれば解決したけど、テロリストはどういう団体かわからないし、わからないから殲滅することもできない。だからそもそもテロリストが生まれないようにしよう、という話があった。

従来のテロリストの傾向と対策

いわゆる「テロリスト」、日本人にとっての典型的な「テロリスト」は「某宗教から派生した過激派たちの集まり」だと思うが、はなぜ生まれるのか。

職・収入がない人たちがいて、その国には社会保障もない。このままだと死んでしまう、というときに「食べ物あげるよ」といって、ある団体が助けてくれる。普段は中央政府から不合理な差別を受けているが、その団体の中ではそんなことはない(あったとしても普段よりましだ)。

・・・というのが想像できるストーリーだ。

なので、テロリストが生まれないようにするには、公正な中央政府を作り、すべての国民に最低限の生活を保証することが重要だ。貧しい人々・国をほっとくと、単に貧困で死んでいくのではなく、テロリストになって豊かな人たちの害にもなるから、みんなで豊かになりましょう、ということだ。

これは従来型の戦争の根本的対策(共産化の防止とか)とほとんど同じだけど、国という大きな単位で豊かなだけでは十分じゃなくて、地方団体や個人のレベルでも豊かにならないと戦争は防げないという点で、さらに対策が難しくなったと思う。


愉快犯型テロリストの傾向と対策は?

今や珍しくもないが、またシーシェパードが拘束された。彼らは、不法行為を意図的に繰り返しているので、テロリストであることは間違いない。しかし、従来型テロリストとは違うようだ。

シーシェパードの活動拠点のアメリカ(とオーストラリア?)は典型的な先進国で、公正な政府があり、職も社会保障も充実している。シーシェパードで「働いて」いる人たちは、おそらく普通の社会でも職を見つけて生活できるだろう。この人たちは、死なないためにテロ活動をしているのではなく、生きがいのためにテロ活動をしているのではないか。「クジラと地球を守ってるオレかっこいい」ということだ。

都合が悪いことに(彼らにとっては都合が良いことに)、この「生きがい」は有名人や大企業の支援によって、正当なものになりつつある(と、彼らが錯覚できるような環境が整いつつある)。

従来型テロリストと同様に考えると、政府が「生きがい」を提供する、という対策に行き着くが、これは難しい。
生きがいとはある程度主観的なもので、政府が画一的に提供できるものではない。それに、よくある市民団体や左翼運動と同じで、「政府・マジョリティに反発するのがかっこいい」からだ。

シーシェパードは元は、「クジラかわいい♡」人たちの市民団体だったと思う。それが今や馬鹿なハリウッドスターが支援し、「環境にやさしい」(笑)大企業が資金援助するテロリストになってしまった。多くの人が支援していて自国に害がないので、無責任なオーストラリア政府は黙認状態だし、公式に支持している政治家すらいる。

誰がいつ何をしていれば、このテロリストはここまでひどくならなかったのか、まだわからない。でも、こういう愉快犯型テロリストは今後もでてきそうだ。


追記
もっとうまくまとめられそうだけど、時間が無いので第1版としておく。