読書会メモ 第4回 及び 第5回


●消化内
計算機プログラムの構造と解釈

【第4回】第1章 第3節(p31〜p37)
【第5回】第1章 第3節(p37〜p40)


●メモ

クロージャデザインパターン

第一章の山場である「高階手続き」に入る。まず関数を引数や返り値として扱う方法(クロージャ)を知り、それによりどのようにプログラムの抽象化を図れるのかを学んでゆく。javaの世界にしかいなかった自分にとって、この「関数とそれに付帯する環境(スコープ)」を束ねて扱うという概念は新鮮だ。これにより、動詞の多態性を表現することが可能になる。
このアイデアオブジェクト指向言語に持ち込んだのがデザインパターンだったのだろう。データ(オブジェクト)から離れて関数を扱うことができないオブジェクト指向言語では、動的に処理の多態性を表現することは難しい。関数(動詞)を名詞化し組み合わせることで、それを実現しようとするのが(特にComposition系の)デザインパターンなのだろう。
自分などはクラス化できるならそれでいいじゃんと思ってしまうのだが、クロージャに慣れてくると、それを持たない言語がう鬱陶しくて仕方がなくなるという話はよく聞く。そう感じられる程度にはLispを使いこなせるようになりたい。


いずれにせよ、処理の本質を抽出して、抽象化することができるのだという話。

抽象化と漏れの法則

流れで少し話が飛ぶが、「プログラミングとは抽象化である」ということの意味が最近ようやく少し肌身で感じられるようになってきた。『Joel on Software』 の中に、「漏れのある抽象化の法則」という非常に印象的な記事があったことを思い出し、もう一回読んでみた。

TCPは、下層にある信頼性のないネットワークに対する完全な抽象化を提供しようとしているが、しかし、ときどきネットワークは抽象化から漏れ出て、あなたは、抽象化があなたを守ってくれない何物かの存在を感じ取る。そうしてこれは、私が「漏れのある抽象化の法則」と名付けた法則の一例になっている。


自明ではない抽象化はすべて、程度の差こそあれ、漏れがある。


(中略)


10年前、私たちは、将来は新しいプログラミングパラダイムによってプログラミングが簡単になっているだろうと想像していた。確かに、何年にもわたって私たちが築き上げてきた抽象化は、GUIプログラミングやネットワークプログラミングのような、10年、15年前には扱う必要がなかった、ソフトウェア開発の新しいレベルの複雑さを取り扱うことを可能にしてくれた。そして、現代的なオブジェクト指向のフォームベースの言語のような素晴しいツールは、たくさんの仕事を驚くほど早く成し遂げられるようにしてくれる。しかし、ある日突然、抽象化が漏れているところで問題を解明する必要が生じ、それには2週間もかかるのだ。そして、あなたがプログラマを雇うとき、仕事のほとんどがVBプログラミングであっても、VBプログラマを雇ったのでは十分ではない。VBの抽象化が漏れているところに出会うたび、彼らはタールにはまりこんで動けなくなってしまうからだ。


(『Joel on Software』p.219〜p.223)

プログラミングツールや言語の抽象化が進み便利になると同時に、その漏れも積み重なっていく。抽象化は完全にはなされない前提に立てば、プログラマが知識としてカバーすべき領域は大きくなっていく。
この記事はプログラマに必要とされる知識領域という文脈で書かれているが、「抽象化と漏れの法則」は当然もっと一般化することができる(そもそも人類の文明が発達し、分業が進み、産業が起こり、現代に至る過程そのものが、抽象化の過程といってもいい)。


ええと、何がいいたいのか。


・Joelは大きくなっていく漏れの山を嘆いているが、結局抽象化の度合いとは相対的な判断に基づくものなのであって、いつの時代にも、どの分野においても、その類いの嘆きはなくならないのだろう。


・自分の仕事について、機能の仕様策定から実装への落とし込みまでの全過程において、力の及ぶ限り(ここが逃げ)美しくかつ実効的な抽象化を目指したい。あと、様々な現実を前にしたときに、如何に漏らすかというのも面白いポイントだと思う。

クロージャから人生へ

結局、「人生色々あるけど自分がやるべきことをやる他に選択肢はない」というところにこのエントリーは着地しました。おそまつ。

memcachedをさわってみた

WEB+DB PRESS Vol.42

WEB+DB PRESS Vol.42

今月のWEB+DB PRESSニコニコ動画がどのように負荷分散対策をとってきたかということが詳細に書かれていた。その中にmemcachedというメモリキャッシュシステムが紹介されており、会社の先輩とこれはかなり便利なのではないかという話になった。調べてみた結果、ニコニコ動画はおろか、Wikipediamixiはてな等、トップクラスのトラフィックを誇る世界中のwebアプリケーションで利用されていたことがわかった。


参考:
memcache本家
日本語による詳細な解説
インストールからサーバー構成の例まで盛りだくさん


DBや他のキャッシュシステムと比したときのメリットとしては、
・構築が容易であること
・レスポンスが高速であること(MySQLのメモリキャッシュとのベンチマーク
・スケールすること
・クライアントAPIが充実していること(PerlPHPRubyPythonJavaC#....)


こちらの記事を参考に、Macにインストールし、Javaで動かしてみた。
memcachedlibevent*1のインストールが必要なのだが、いずれも configure 、make 、make install でおしまい。起動もmemcachedコマンド一発。クライアントの実装もシンプルで、memcachedサーバーとの接続も簡単だ。

import java.util.Date;
import com.danga.MemCached.*;

public class MemcachedTest {

    public static void main(final String[] args){
    	 
        //Poolの初期化
    	//memcacheのデフォルトポート番号は11211
        String[] servers = { "localhost:11211" };
        SockIOPool pool = SockIOPool.getInstance();
        pool.setServers(servers);
        pool.initialize();

        MemCachedClient cache = new MemCachedClient();
        //キャッシュクライアントにセット
        cache.add("String", "test");
        cache.add("Date",new Date());

        //キャッシュクライアントから取り出す
        System.out.println(cache.get("String"));
        System.out.println(cache.get("Date"));
        System.out.println(cache.get("Hoge"));
    }
}

実行結果:

test
Sat Jan 26 21:22:49 JST 2008
null


用途を考える際に気をつけるべき点としては、設定されたメモリ容量を超えた時点でスワップせずに古いキーから揮発し始めるということ。それが前提だから高速かつ、シンプルで使いやすいということだ。また、当然のことながらアプリケーションサーバーを再起動してもキャッシュは生きている。これはちょっと新鮮な体験だった。


あとは、自分の現在の仕事の中でどのように使えそうか、どれだけのメリットが出るのかを考えたい。データ更新時にリアルタイムに対象のキャッシュのみをクリアする仕組みを作れば、かなり使えるのではないだろうか。少なくともマスタ系のデータに関しては何とかなりそうだ。
また、Windows系OSの利用はあまり前提とされていないので、その辺も調べる必要がある。

*1:libeventについてはまだよくわかっていないので後で調べてみたい。

読書会メモ 第2回 及び 第3回


先週末はネット環境にいなかったため、本日までの2回分まとめてメモを残します。


●消化内
【第2回】第1章 第2節 手続きとその生成するプロセス(p20〜p24)
【第3回】第1章 第2節 手続きとその生成するプロセス(p24〜p31)


●メモ
第1章第1節でSchemeの基本的な文法や特殊形式が解説された後、この第2節では様々な数学的アルゴリズムとそれにより生成されるプロセスについて述べられている。効率のいいアルゴリズムとは何か、如何にしてそのようなアルゴリズムを生み出せるのかを学ぶための節。この節を読んで、初めてアルゴリズムの本質に触れた気がした。ただし、自分の場合、数学の基礎知識がおぼつかないこと、再帰アルゴリズムの構成をまだ体で覚えていないことが足を引っ張り、理解するのに大変な時間がかかる。補助的な書籍を読みながら、だんだんと数学の知識を戻しつつあるが、まだまだ全然足りない。


増加の程度

ある関数F(n)への入力が増加していったとき、「プロセスのステップ数」と「必要とされる計算空間(スペース)」が増加する程度は以下の4種類に分類できる(下へ行くほど効率的)。

1. 指数的増加(α^n に比例)
2. 多項式的増加(n に比例)
3. 対数的増加(log n に比例)
4. 定数的増加(α に比例)


数学的世界では1番と2番の間に大きな線引きがなされる。すなわち、1は遅い、2〜3は充分速いと見なす。判断の根拠はアルゴリズムが実用に堪え得るかどうか(指数的増加を示すアルゴリズムは、容易に現実性を超えた量のステップ数を要求するようになる)。
一方、私たちが身を置く業務システムの世界では当然、よりシビアな線引きがなされる。データ件数に比例して応答時間が遅くなるシステムでは使い物にならない。


具体的に取り上げられていた例題は以下。

[Fibonacci数列のn番目の数を求めるアルゴリズム

木構造再帰の実装により生成されるステップ数は指数的増加を示し、反復的再帰の実装によるステップ数は多項式的増加を示す。指数的爆発の影響力がいかに大きいかが明らかになる。

[α^n(べき乗)を求めるアルゴリズム

直感的にはαをn回掛けるアルゴリズムが採用され、当然これはステップ数、スペース共にnに線形(つまり多項式的)な増加を示す。
一方、逐次平方(下記参照)を利用したアルゴリズムの実装はステップ数、スペース共にnに対数的な増加を示す。これは、再帰的計算プロセス内部において、(nが偶数でわたってくる度に)べき乗アルゴリズムのステップ数の根拠たる指数nを半分に減らすことが可能だからである。

逐次平方
α^n = (α^n/2)^2 = (α^2)^n/2 (nが偶数の場合)

α^n = α・α^n-1 (nが奇数の場合)

[逐次平方の応用]

当初木構造再帰で実装していたFibonacci数の計算式や、Fermatテスト(素数性テスト)を、逐次平方を応用し対数的ステップ数を生成するアルゴリズムとして実装可能であることを学ぶ。



●その他
数学の感覚が少しもどってきたので、ようやくポリアの『いかにして問題をとくか』を抵抗なく読み始められそう。。

いかにして問題をとくか

いかにして問題をとくか


数学ガールでも推薦図書に名を連ねていたので、是非読んでみようと思う。

読書会メモ 第1回

『計算機プログラムの構造と解釈』社内勉強会の第一回が開催されました。当面のコンセプトは「プログラマとしての基礎力の向上」です。
今後、勉強会の中で気づいたことや、演習の解答などをメモしていくことにしたいと思います。

計算機プログラムの構造と解釈

計算機プログラムの構造と解釈

化内

第1章―第1節〜第2節途中(p1〜p20)

メモ

Lisp*1の式の一般形

( …)


Ex (+ 100 35) → 135

 Lispでは基本演算子も、利用者に定義される手続きも、式は全てこの形式に従って評価される(ただし、ifやdefineもしくはdefunなどの特殊形式を除く)。これはLispの言語仕様上大変自明なことではあるが、その単純な規定こそがLisp再帰性、自己言及性の保持を可能足らしめている。例えば、多くの言語では基本演算子はプリミティブに定義された特殊な形式をとっている*2が、その理由は人間の直感的な把握を容易にするための「本質的ではない言語仕様」なのだろうという話だった。つまり、Lispでは可読性を犠牲にする代わりに大きな柔軟性を手にしており、難しいが強力といわれている理由の一つもそこにある。具体的にどのようにその柔軟性を生かすことができて、それによってどのようなメリットが生まれるのかは、この本を通して深く学んでいけるはず。

ある言語の最も基本的な仕様を一つ読んだだけで、これだけの考察がなされ得るということに感動してしまった。以上のような話はJavaの世界にいるだけでは決して知り得ないわけで、ひとつ次元を登ることの面白さに改めて気づかされた。

手続きとその生成プロセスについて

Schemeはfor文やwhile文を持たないため、ループは再帰的手続きによって表現する。ただし、ある手続きが再帰的な構造を持っていることと、その評価結果として生成されるプロセスが再帰的であるかということには関連はない。再帰的手続きによって遅延評価が発生した場合にその計算に必要な情報の量は線形に大きくなるが、遅延評価が発生しない形の再帰的手続きは常に一定量の計算空間でなされる反復的プロセスを生成する。効率を考えるのであればもちろん後者が好ましいが、ツリー構造のデータを扱う場合には前者によって直感的かつ容易な設計が可能となる。

追記

「遅延評価」という言葉は適切ではないというご指摘をいただきました。全くその通りで、遅延評価とは、何らかの式の評価をその評価結果が必要となるときまで遅らせるという処理系の挙動のことです。上で私が言おうとしていたのは、たとえ再帰的手続きであっても、その再帰呼び出しが末尾にないと、命令のスタックが次々に積み上がっていってしまうという状態のことです。(参照:末尾再帰


実際の演習などは次回以降のメモで。

*1:この本の中ではLispの方言であるSchemeが用いられていますが、Lisp一般に関する話に関してはLispと表現します。特にSchemeに限定された話に関しては明示的にSchemeと書きます。

*2: 例えば (+ x y) ではなく (x + y) と書く