今月号のオープンソースマガジンはSICPとの併読がおすすめ

今月号のオープンソースマガジンの「プログラミング言語の進化を追え」の特集が面白そうだったので、買ってみました。その特集の中でも特に「Part 3 クロージャとオブジェクトの微妙な関係」が興味深かったです。

このPart 3の記事をもっと楽しむには、SICPも一緒に読むと良いと思います。

というのも、今ちょうどSICPの3章のはじめの方を読んでいて、「3.1 代入と局所状態」のセクションをGaucheを使いながら勉強している最中で、Part 3の記事の話題と密接に関係していることがわかったからです。このセクションでは、局所環境とlambdaで「クロージャ」を形成できるとは、明示的には説明されていません。*1 しかし、確かにこのセクションではクロージャを扱っています。

よって、Part 3の記事とSICPを併読することで、よりスムーズにクロージャを理解できるようになるのでは?と思った次第です。また、Part3の説明で出てくる「環境」という言葉をもっと深く理解するためにも、SICPと併読した方が良いかと思います。(くどい)

Part 3の記事を読んで、結局、

  • クロージャもオブジェクトも、それらの挙動に注目してみれば、本質的には同じ
  • 構文レベルではオブジェクト指向をサポートしていない言語であっても、オブジェクトの挙動を記述できるという点で、クロージャの方がより超越的(meta)な存在

という印象を持つようになりました。

また、Perlでのクロージャの話題を説明されているnaoyaさんの記事も興味深いので、Perlにも興味がある方は一緒に読むと良いかと思います。

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

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

*1:クロージャ」という言葉は出てこないという意味

AJAJA: Perlモジュールを使えるようにするpmconnectが熱い(かもしれない)

AJAJAsvnリポジトリに面白いコードが追加されました。pmconnectという新しいモジュールです。

http://ajaja.alphageek.jp/trac/changeset/32

これは、PerlモジュールをAJAJAから利用できるようにする試みのようです。面白いですね。Linuxの環境が手元にないので、まだ試していませんが、CPANモジュールなんかも普通に動作するんじゃないかと期待しています。

ちなみに、pmconnectのC言語に関するソースコードの量は以下の通りです。

% cd src/ajaja/pmconnect
% find . -type f | grep -v svn | egrep "\.(c|h)$" | xargs wc -l | sort
      18 ./utfutil.h
      27 ./utfconv.h
      30 ./pmconnect_impl.h
      39 ./pmconnect.h
      55 ./poprivate.c
      62 ./utfutil.c
     104 ./pmconnect.c
     158 ./pmctest.c
     178 ./utfconv.c
     269 ./jspl.c
    1119 ./pmobject.c
    2059 total

pmobject.cは他のソースコードと比べて結構大きいですね。どうやらpmobject.cで、PerlインタプリタSpiderMonkeyとの橋渡しをしているようです。このソースコードを理解するには、PerlのXSを理解する必要がありそうです。逆に考えれば、XSを勉強する良い機会かもしれません。

jspl.cはAJAJAのssjsと同じようなJavaScriptインタプリタで、異なる所は、

  • Perlモジュールの利用をサポート
  • コマンドライン引数で指定したJavaScirptファイルの実行のみをサポート(インタラクティブな利用は不可)
  • 標準出力にバイナリ出力するprint_binary関数、16進ダンプをするdump関数などの組み込み関数を実装
  • AJAJAのSystemモジュール、SQLiteモジュール、JSANなどは利用不可
  • SpiderMonkeyを走らせるための必要最低限な実装(シンプル)

所といったところでしょうか。SpiderMonkeyインタラクティブに利用するのではなく、コマンドライン引数で指定したJavaScriptファイルをSpiderMonkeyで走らせるようなコードを書きたい開発者には、jspl.cは参考になるソースコードだと思います。

あと、pmconnect/scripts/ディレクトリ以下には、Perlモジュールの利用例があるので、興味を持たれた方は、こちらの方も見てみると面白いかもしれません。

Extending and Embedding Perl

Extending and Embedding Perl

AJAJA: memcachedを使えるようにしてみよう

大よそ、C言語でのAJAJAのモジュールの書き方がわかってきたので、例題としてmemcachedAJAJAで使えるようにしてみました。(何でこのタイミングでmemcachedバインディングを書いたのか?と聞かれると困ります(汗。単に興味があったからやってみただけですw。) memcachedとは、簡単に言うとメモリーキャッシュサーバのことで、LiveJournalmixiはてななどの大規模システムで利用されているツールです。

memcachedC言語のバイディングAPIを提供しているlibmemcacheを使って、memcachedの機能の一部を利用できるようにしました。昨日はじめてmemcachedをインストールして触りだしたので、機能的にはまだまだしょぼいですが、一応動きます。

クライアントでの利用例

現段階で、以下のようなコードが動作します。keyとvalueのペアをmemcachedにキャッシュして、keyをもとにキャッシュしたvalueを単に取り出しているだけです。APIは、CPANモジュールのCache::Memcached::XSを少し参考にしましたが、まだ程遠いのが現状です。現状では、keyとvalueにはStringが利用できます。オブジェクトをmemcachedにキャッシュするのは、どうも難しそうな気配がします。

% vim memcached_test.js
use('System');
use('Memcached');

memd = new Memcached({server:"127.0.0.1", port: "11211"});
memd.set("hello", "world");
memd.set("good", "bye!");

System.puts("hello, " + memd.get("hello") + "\n");
System.puts("good " + memd.get("good") + "\n");

memcachedlocalhostの11211ポートで走らせておいて、ssjsで実行させると、期待通りの結果が得られます。

% memcached -d -p 11211
% ssjs memcached_test.js
hello, world
good bye!

モジュールのソースコード

モジュールのソースコードは以下の通りです。AJAJAに搭載されているSQLiteバインディングのコード(SQLite/SQLite.c)をかなり参考にして書きました。LLな言語になれていると、C言語でのコーディングはやりにくく感じますが、そのぶん細かい制御ができるので、それはそれで楽しいですね。結構C言語にはまりつつあります。。

/*
 * memcached for AJAJA
 *
 */
#include <string.h>

#include "jsapi.h"
#include "jsprf.h"
#include "jscntxt.h"
#include "jsinterp.h"

#include <memcache.h>

#ifdef DEBUG
    #define DBG(msg...)         \
        fprintf(stderr, msg)
#else
    #define DBG(msg...)
#endif

/* prototype */
static JSClass mc_class;

static JSBool
memcached_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str_server, *str_port;
    jsval jsval_server, jsval_port;
    JSObject *obj_argv;
    char *server, *port;
    struct memcache *mc;

    if (!JSVAL_IS_OBJECT(argv[0]))
        goto error;

    obj_argv = JSVAL_TO_OBJECT(argv[0]);

    if (!JS_GetProperty(cx, obj_argv, "server", &jsval_server))
        goto error;

    if (!JS_GetProperty(cx, obj_argv, "port", &jsval_port))
        goto error;

    if ((str_server = JS_ValueToString(cx, jsval_server)) == NULL)
        goto error;

    if ((str_port = JS_ValueToString(cx, jsval_port)) == NULL)
        goto error;

    server = JS_GetStringBytes(str_server);
    port = JS_GetStringBytes(str_port);

    DBG("memcached_constructor(): server = %s, port = %s\n", server, port);

    /* libmemcache */
    mc = mc_new();
    mc_server_add(mc, server, port);

    if (!JS_SetPrivate(cx, obj, mc))
        goto error;

    return JS_TRUE;

error:
    *rval = JSVAL_VOID;
    return JS_FALSE;
}

static void
memcached_finalize(JSContext *cx, JSObject *obj)
{
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    DBG("memcached_finalize()\n");

    mc_free(mc);
}

static JSBool
memcached_set(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str;
    char *key, *value;
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    /* get key */
    if ((str = JS_ValueToString(cx, argv[0])) == NULL)
        return JS_FALSE;
    key = JS_GetStringBytes(str);

    /* get value */
    if ((str = JS_ValueToString(cx, argv[1])) == NULL)
        return JS_FALSE;
    value = JS_GetStringBytes(str);

    DBG("memcached_set(): key = %s, value = %s\n", key, value);

    mc_add(mc, key, strlen(key), value, strlen(value), 10, 0);

    return JS_TRUE;
}

static JSBool
memcached_get(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str;
    char *key, *value;
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    /* get key */
    if ((str = JS_ValueToString(cx, argv[0])) == NULL)
        return JS_FALSE;
    key = JS_GetStringBytes(str);

    value = (char *)mc_aget(mc, key, strlen(key));
    *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, value));

    DBG("memcached_get(): key = %s, value = %s\n", key, value);

    return JS_TRUE;
}

static JSClass mc_class = {
    "Memcached",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    memcached_finalize
};

static struct JSPropertySpec mc_props[] = {
    /* {name, tinyid, flags, getter, setter}, */
    {0}
};

static struct JSFunctionSpec mc_funcs[] = {
    /* {name, call, nargs, flags, extra}, */
    {"set", memcached_set, 2, 0, 0},
    {"get", memcached_get, 1, 0, 0},
    {0}
};

int
JS_InitModule_Memcached(JSContext *cx, JSObject *obj)
{
    JS_InitClass(cx, obj, NULL,
                 &mc_class,
                 memcached_constructor,
                 1,
                 mc_props,
                 mc_funcs,
                 NULL,
                 NULL);

    return 1;
}

モジュールのビルドとインストール

以下の手順でモジュールのビルドとインストールができます。libmemcacheを予めインストールしておく必要があります。ajaja-build/と同じ階層でMemcached-0.01.tar.gzを展開すれば、うまくmakeできるはずです。libmemcache.aが/usr/local/lib/以下にあると仮定しているので、環境によっては、これが原因でmakeできないかもしれません。その時は、Makefileにある「LDFLAGS」のパスを調整して下さい。

% cd ajaja
% ls
ajaja-build/
% wget http://vaio.redirectme.net/lib/ajaja/Memcached-0.01.tar.gz
% tar zxvf Memcached-0.01.tar.gz
% cd Memcached-0.01
% make
% sudo make install

AJAJA: Systemモジュールを拡張してsleepできるようにしてみよう

AJAJAのSystemモジュールがロードされるまでの制御フローは大よそ理解できたので、今度はSystemモジュールを拡張してsleepできるようにしてみました。なぜsleepを取り上げたかと言うと、

  • 入出力を伴わないので簡単に実装できそう
  • JavaScriptから下層レイヤーの機能を制御して、SpiderMonkey自身を停止させてみたかった

という2点が大きな理由です。
案外簡単に機能拡張ができたので、その方法を紹介したいと思います。なお、今回はrev #30のソースコードを用いました。

規則性から拡張に必要なコードを推測する

System/System.cを眺めていると、追加したい関数のパターンが見えてきます。JavaScriptから利用されるどの関数(例えば、system_exit関数)も、以下のようなパターンを持っていることがわかります。関数の戻り値の型はJSBoolで、関数名には「system_」とうprefixを付ける必要がありそうです。また関数の引数は、「JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval」になりそうです。(argvには、引数に渡されたデータが順番に入っています。例えば、system_puts関数の場合、System.puts("hello")が呼び出されたとしたら、argv[0]には、"hello"を表現するJavaScriptの値(jsval)のポインタが入っています。)

static JSBool
system_funcname(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    (処理)
    return JS_TRUE;
}

以下の構造体も重要そうです。この構造体は、JavaScriptの世界とC言語の世界を結びつける重要な働きをしています。ここで注目したいのは「/* {name, call, nargs}, */」という親切なコメントです。このコメントから各要素が何を意味しているのか、大よそ推測できます。例えば、sysytem_exit関数は、JavaScirptの世界からSystem.exitで呼び出され、その引数は1つである、と読めます。

struct JSFunctionSpec funcs[] = {
    /* {name, call, nargs}, */
    {"exit",        system_exit,       1},
    {"gets",        system_gets,       0},
    {"puts",        system_puts,       1},
    {"getenv",      system_getenv,     1},
    {"setenv",      system_setenv,     2},
    {"readFile",    system_readFile,     2},
    {0}
};

JavaScriptの世界からC言語の世界へデータ変換

manによるとsleep関数は、unsigned intの引数をとるようです。

% man 3 sleep
SLEEP(3)                   Linux Programmer's Manual                  SLEEP(3)

NAME
       sleep - Sleep for the specified number of seconds

SYNOPSIS
       #include <unistd.h>

       unsigned int sleep(unsigned int seconds);

DESCRIPTION
       sleep()  makes  the  current  process  sleep until seconds seconds have
       elapsed or a signal arrives which is not ignored.

RETURN VALUE
       Zero if the requested time has elapsed, or the number of  seconds  left
       to sleep.

CONFORMING TO
       POSIX.1

BUGS
       sleep()  may  be implemented using SIGALRM; mixing calls to alarm() and
       sleep() is a bad idea.

       Using longjmp() from a signal handler  or  modifying  the  handling  of
       SIGALRM while sleeping will cause undefined results.

SEE ALSO
       signal(2), alarm(2)

GNU                               1993-04-07                          SLEEP(3)


ここで、System/System.cにある他の関数に目を向けると、JS_ValueToString関数によって、JavaScriptの世界でのstringをC言語の世界での文字列表現に変換しているコードが多数見つかります。文字列の変換があるのならば、整数の変換もありそうです。

そこで、整数の変換だから、恐らく「ToInt」という文字列が含まれているだろうと予想し、以下のようにgrepしてみました。(マニュアルを読んだ方が確実です。でもgrepの方が早いですw) grepしてみると、全く関係のないSQLiteのコードがマッチしたので、grepのvオプションでそれらをフィルタリングしました。include/jsapi.hの384行目に「JS_ValueToInt32(JSContext *cx, jsval v, int32 *ip)」という、お手ごろそうな関数が見つかりました。

% cd ajaja-build/bin/ajaja
% grep -rn "ToInt" . | grep -v sqlite
Binary file ./SQLite/SQLite.so matches
./include/jsapi.h:366: * for ToInt32.
./include/jsapi.h:384:JS_ValueToInt32(JSContext *cx, jsval v, int32 *ip);
./include/jsnum.h:201: * for ToInt32.
./include/jsnum.h:225:js_ValueToInt32(JSContext *cx, jsval v, int32 *ip);
./include/jsnum.h:239:js_DoubleToInteger(jsdouble d);
Binary file ./ajaja-0.2.i386/bin/ssjs matches
Binary file ./ajaja-0.2.i386/bin/asp_js matches
Binary file ./ajaja-0.2.i386/lib/js/0.2/SQLite.so matches

include/jsapi.hを見てみると、先ほどgrepで見つけたJS_ValueToInt32関数のすぐ上に、もっとふさわしい関数が見つかりました。sleep関数は、unsigned intの引数をとることから、JavaScriptの整数を32ビット型のunsigned intに変換する「JS_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip)」を使った方が良さそうです。

% vim include/jsapi.h
[...]
/*
 * Convert a value to a number, then to an int32, according to the ECMA rules
 * for ToInt32.
 */
extern JS_PUBLIC_API(JSBool)
JS_ValueToECMAInt32(JSContext *cx, jsval v, int32 *ip);

/*
 * Convert a value to a number, then to a uint32, according to the ECMA rules
 * for ToUint32.
 */
extern JS_PUBLIC_API(JSBool)
JS_ValueToECMAUint32(JSContext *cx, jsval v, uint32 *ip);

/*
 * Convert a value to a number, then to an int32 if it fits by rounding to
 * nearest; but failing with an error report if the double is out of range
 * or unordered.
 */
extern JS_PUBLIC_API(JSBool)
JS_ValueToInt32(JSContext *cx, jsval v, int32 *ip);
[...]

完成したコード

最終的に、以下のようなコードを実装しました。ここまで説明すれば、もう何も説明しなくても理解できると思います。予想していた通り、少しの変更で、追加したい機能を実装できました。

% svn diff System/System.c
Index: System/System.c
===================================================================
--- System/System.c     (revision 30)
+++ System/System.c     (working copy)
@@ -182,6 +182,21 @@
     return JS_FALSE;
 }

+static JSBool
+system_sleep(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+    JSBool ret;
+    uint32 seconds;
+
+    ret = JS_ValueToECMAUint32(cx, argv[0], &seconds);
+    if (!ret)
+        return JS_FALSE;
+
+    sleep(seconds);
+
+    return JS_TRUE;
+}
+

 static JSClass system_class = {
     "System",
@@ -210,6 +225,7 @@
     {"getenv",      system_getenv,     1},
     {"setenv",      system_setenv,     2},
     {"readFile",    system_readFile,     2},
+    {"sleep",       system_sleep,      1},
     {0}
 };

ビルド

以下のコマンドでビルドできます。ビルドした共有ライブラリ(System.so)は、ssjsから見える所にコピーしておきます。

% cd System
% make && sudo cp System.so /usr/lib/js/0.2/

実行結果

以下のようなテストコードを実行してみました。

% vim ~/sleep.js
use('System');

System.puts("Hello\n");
System.sleep(5);
System.puts(", world\n");

一応、約5秒間sleepすることを確認できました。

% time ssjs ~/sleep.js
Hello
, world

real    0m5.008s
user    0m0.000s
sys     0m0.010s

AJAJA: Systemモジュールがロードされるまでの制御フローの解析

AJAJAを実際に走らせてみて、AJAJAの動作をもう少し詳しく知りたくなりました。特に、AJAJACGIを実現するのに利用されているSystemモジュールが、どのようにしてユーザーランドから利用できるようにしているのか、興味を持ちました。そこで今回は、Systemモジュールが、実行コンテキストにロードされるまでの制御フローを解析してみたいと思います。今回、解析するソースコードはrev #30を用いることにします。

解析方法

ソースコードの流れを理解する方法として、

の2つがあるかと思います。

今回は、両方のアプローチを用いて、ソースコードの解析をしていきました。

まず、動的な方法で解析するために、ssjs単体をビルドする方法を調べました。

ソースコードを読んで試行錯誤した結果、以下のコマンドでssjs単体をビルドできることがわかりました。makeの引数に「BUILD_OPT=1」を指定すると、ビルドされるssjsの実行バイナリが最適化されます。

% cd ajaja-build/bin/mozilla/js/src
% make -f Makefile.ref BUILD_OPT=1

ちなみに、asp_jsとssjsの実行バイナリの内容は全く同じで、単にハードリンクをしてasp_jsが作られています。(実行バイナリがASPモードとして動作するかどうかの判定は、実行バイナリのファイル名で行っているようです。)

以下のようにすれば、ビルドした実行バイナリの動作確認ができます。(最適化を無効にしてビルドした場合は、Linux_ALL_DBG.OBJディレクトリ以下に実行バイナリが作られます。) 以下の例では、「print("hello")」を評価しています。(print関数は、ssjsのみで利用可能な組み込み関数です。)「quit()」を評価することで、ssjsを終了できます。

% ./Linux_All_OPT.OBJ/js
js> print("hello")
hello
js> quit()
%

このようにssjs単体をビルドできるようにした状態で、ソースコードの解析を行っていきました。

動的解析のテクニック

SpiderMonkeyへのパッチ(ajaja-build/bin/ajaja/js.patch)は、パッチを読むと、ajaja-build/bin/mozilla/js/src/js.cに適用されることがわかります。ですので、このjs.cがAJAJAにおいて核になっているソースコードであると予想できます。

ソースコードを読んでいて、プログラムの制御フローを調べたい時は、C言語の標準ライブラリで提供されているprintf関数を使います。プログラムを走らせて動的にソースコードを解析する際に、printf関数は非常に強力な武器になります。

では、js.cの制御フローの中で気になる所にprintf関数でマーキングしていきます。以下は、Process関数とProcessArgs関数の挙動が不明で、いつ制御が帰ってくるのか調べたいという場合のマーキングの例です。

% vim ajaja-build/bin/mozilla/js/src/js.c
[...]
int
main(int argc, char **argv, char **envp)
{
[...]
    printf("#1\n");

#ifdef ASP_JS
    /* init.js */
    sprintf(buff, "%s/init.js", JSPATH);
    Process(cx, glob, buff, JS_FALSE);
#endif

    printf("#2\n");

    result = ProcessArgs(cx, glob, argv, argc);

    printf("#3\n");

[...]

ビルドをして、実行してみます。

% cd ajaja-build/bin/mozilla/js/src
% make -f Makefile.ref BUILD_OPT=1
% ./Linux_All_OPT.OBJ/js
#1
#2
js> print("hello")
hello
js> quit()

#3
%

この実行結果から、Process関数はすぐに制御を戻してくる一方で、ProcessArgs関数は、シェル内でquit()が評価されるまで制御が戻ってこないことがわかります。この結果からわかるように、ソースコードを単に眺めただけではわかりにくい制御フローが、少しの努力で解析できました。

解析結果

このような動的解析のテックニックと、静的なコードリーディングを通して、Systemモジュールが、実行コンテキストにロードされるまでの制御フローを解析した結果が、以下の図です。青とオレンジの線が制御フローを示しています。

青の線(1)は、ユーザーランドにあるJavaScriptのコードが実行されるまでの制御フローを示しています。init.jsのuse関数に矢印が入っていますが、これは、Process関数によってuse関数がSpiderMonkey仮想マシンが解釈可能なAST(抽象木)に変換されると考えられるからです。

オレンジの線(2)は、ユーザーランドにあるJavaScriptのコードで「use('System');」というコードが存在する場合の制御フローです。(1)でSpiderMonkey仮想マシンの実行コンテキストに、先ほどのuse関数がロードされているので、use関数の本体が実行可能です。

まだまだ説明不足の所が多くありますが、今回の試みが、Systemモジュールのような共有ライブラリ型(*.so)のモジュールを、自分で書いてみたいという方の何かの役に立てれば幸いです。

Code Reading―オープンソースから学ぶソフトウェア開発技法

Code Reading―オープンソースから学ぶソフトウェア開発技法

AJAJA: 勉強材料としての可能性とSQLiteを使ったちょっとした実験

AJAJA: Asynchronous JavaScript and JavaScript/ASP

AJAJA は「ブラウザだけでなくサーバサイドでも JavaScript を使おう!」というプロジェクトです。

  • できること

- JavaScript を埋め込んだ ASP ファイルを処理する
- JavaScript から SQLite などのシステムライブラリを呼び出す
- JSAN のライブラリをサーバサイドで利用する
- Server Side JavaScript の実行処理系 asp_js を提供しています

  • asp_js の特徴

- IISASP/JavaScirpt の記法をそのまま処理することができます
- Mozilla の C の JavaScript 処理系 SpiderMonkey を組み込んでいます
- SQLite を標準サポート、拡張も容易です

AJAJA - Trac

AJAJA、おもしろいです。久しぶりにワクワクするソフトウェアが出た感じがします。Pugs 6.0.0のコードを読んでいた頃と同じような感覚です。

そこで今回は、

  • 非常に良い勉強材料になる可能性が高いAJAJA
  • 面白かったコード
  • AJAJASQLiteを使ったデータベースプログラミングの実験

について書いてみたいと思います。

外部コンポーネントとの連携には勉強材料がいっぱい

普通、JavaScriptと言えばクライアントサイドで動作させますが、このAJAJAではサーバーサイドでJavaScriptのコードが走ります。

ここで、ふと疑問に思いました。

「ブラウザを経由しないで、AJAJAはどうやってJavaScriptのコードを実行しているのだろう?」

その答えは、SpiderMonkeyでした。SpiderMonkeyMozillaのCによるJavaScriptの実行処理系で、FireFoxなどで使われているコンポーネントの1つのようです。

SpiderMonkeyという実行処理系とうまくリンクさせて、JavaScriptが走るようにしている所が凄いなぁと思います。すでにある外部コンポーネントとの連携のやり方を勉強するのに、AJAJAは良い勉強材料になりそうです。(C言語のコードを読む練習をしよう)

外部コンポーネントと言えば、JSANのライブラリやSQLiteもそうです。これらのコンポーネントについても、どのようにしてAJAJAの核のコードとバインディングされているのかを勉強すれば、なかなか面白いのでは?と思っています。

AJAJAが誕生して、まだ日が浅いので、ソフトウェアの複雑さはそれほど高くありません。ですので、勉強をするには今がチャンスだと思います。勉強をするには、今くらいがちょうど良い複雑さだと直感的に思っています。(Pugs 6.0.0がそうであったように)

面白かったコード

それで、ちょっとだけコードを読んでみました。コードを読んで、特に「あっ」と思ったコードは以下の2つです。(今の所)

SpiderMonkeyのコードへのパッチ

今は、全然意味がわかっていませんが、SpiderMonkeyのコードへのパッチ(ajaja-build/ajaja/js.patch)の書き方が1つ勉強になりました。それは、プリプロセッサの命令(#define、#ifdef、#endif)を使って、パッチを当てた部分を明確化するというテクニックです。

以下がそのパッチの一部です。

[...]
===================================================================
RCS file: /cvsroot/mozilla/js/src/js.c,v
retrieving revision 3.118
diff -u -r3.118 js.c
--- js.c    26 Jun 2006 21:22:12 -0000  3.118
+++ js.c    11 Jul 2006 09:22:49 -0000
@@ -92,6 +92,14 @@
 #include <io.h>     /* for isatty() */
 #endif

+#define ASP_JS 1
+
+#ifdef ASP_JS
+#define JSPATH "/usr/lib/js/0.2"
+
+static int asp_js = 0;
+#endif
+
 #define EXITCODE_RUNTIME_ERROR 3
 #define EXITCODE_FILE_NOT_FOUND 4

@@ -355,6 +363,11 @@
         return 1;
     }

+#ifdef ASP_JS
+    if (asp_js && i > 0)
+        i--;
+#endif
+
     length = argc - i;
     for (j = 0; j < length; j++) {
         JSString *str = JS_NewStringCopyZ(cx, argv[i++]);
@@ -476,8 +489,16 @@
         }
     }

+#ifdef ASP_JS
+    if (asp_js) {
+        char buff[FILENAME_MAX];
+        sprintf(buff, "%s/asp_js", JSPATH);
+        Process(cx, obj, buff, JS_FALSE);
+    } else
+#endif
     if (filename || isInteractive)
         Process(cx, obj, filename, forceTTY);
[...]

パッチを良く見ると、追加分のコードは、「#ifdef ASP_JS ... #endif」でラッピングされています。こうしておくと、パッチを当てて変更された部分が強調されて、パッチを当てたソースを読む時に、オリジナルのコードとそうでないコードをはっきり区別できるようになります。こんなちょっとした工夫ですが、僕にとっては凄い勉強になるコードでした。

(僕は、C言語で書かれたソフトウェアのコードを読んでいると、つい、プリプロセッサでどういった面白いテクニックが使われているのか注目してしまいます。プリプロセッサのテクニックを見れば、だいたいその人のC言語の運用スキルが見えてくる気がするからです。そう考えるようになったのは、昔、Linuxカーネルソースコードを眺めていた時に、プリプロセッサによる高度なテクニックを見て感動したためです。)

ビルドスクリプト

何の変哲もないシェルスクリプトですが、ビルドするために、SubversionCVSリポジトリから、それぞれAJAJASpiderMonkeyの最新のコードをチェックアウトしている所が面白いです。ダイナミックな感じが新鮮です。普通のUNIX系のソフトウェアの「tarballを展開してconfigureしてmakeするというテンポ」じゃないのが良いです。少なくとも僕にとっては新鮮で、勉強になりました。

#!/bin/sh

# Download AJAJA
echo "checking out ajaja..."
svn co svn://ajaja.alphageek.jp/trunk/ajaja || exit 1

# Download SpiderMonkey
echo "checking out js..."
cvs -d :pserver:anonymous:anonymous@cvs-mirror.mozilla.org:/cvsroot \
    co mozilla/js/src || exit 1

# Patch SpiderMonkey
(cd mozilla/js/src && patch < ../../../ajaja/js.patch)

# configure
cd ajaja
./buildconf || exit 1
make update || exit 1

# make
make
  • 追記: 2006/07/21

このビルドスクリプトのコードは、rev #28で大きく変更が加えられました。

SQLiteを使ったデータベースプログラミングの実験

Cybozu Labsの竹迫さんのPowerPointにあった掲示板のコードを参考にしながら、ちょっとしたコードを書いてみました。

以下は、実行される毎に、カウント値をインクリメントするカウンターのプログラムです。(かなり適当に書いたコードなので、排他処理はしていません)

% vim counter.js
use('SQLite');
use('System');

var DB_FILE = "counter.db"

function executeQuery(query)
{
    db = new SQLite(DB_FILE);
    db.query(query);
    db.close();
}

if (arguments[0] == 'init') {
    executeQuery("DROP TABLE IF EXISTS counter;");
    executeQuery("CREATE TABLE counter (counter INTEGER NOT NULL);");
    executeQuery("INSERT INTO counter (counter) VALUES (0);");
    System.puts("Database initialized\n");
    System.exit();
}

var db = new SQLite(DB_FILE);
if ((result = db.query("SELECT counter FROM counter")) != null) {
    counter = result.fetch()[0];
    System.puts("counter = " + counter + "\n");

    db.query("UPDATE counter SET counter = " + (counter + 1));
}
db.close();

このコードを実行すると、実行する毎にカウンターの値がインクリメントされていくことが確認できます。こんなJavaScriptのコードが(データベースにアクセスしている!)、ちゃんと走るのを見て、ちょっと感動しました。

% ssjs counter.js init
Database initialized
% ssjs counter.js
counter = 0
% ssjs counter.js
counter = 1
% ssjs counter.js
counter = 2

今回は、AJAJAが勉強材料になるということを強調しましたが、もちろん、これからの実用化も期待しています。どこまで面白いソフトウェアに発展するのか楽しみです。

LLR2006: HaskellでCollatz予想に挑戦

お待たせしました!
キミならどう書く 2.0 ROUND 2の開催です!!

今回のLL Ringでは「LLで関数プログラミング」のセッションをはじめとし,関数型言語の活躍が期待されます.そこで,前哨戦にも関数型のお題を用意しました.

お題は「Collatz予想」(角谷予想,3x+1問題)についての問題です.

キミならどう書く 2.0 - ROUND 2 -

面白そうなので、Haskellでこの問題に挑戦してみました。
う〜ん。もっとコンパクトに書きたかったのですが、ダメでした。
まず、インデントのレベルが深すぎます。ガードとlet...inとcase...of...を組み合わせて使うと、すぐにこんな感じになってしまいます。
あと、無駄な計算もあるので何とかしたいですね。これについては、計算結果の状態をハッシュか何かで保持しておけば、解決できそうです。
時間があれば、改良版を考えてみたいと思います。

import System

main :: IO ()
main = do
    args <- getArgs
    putStrLn $ show $ h (read $ head args)

h :: Int -> Int
h n = h' n n 0
    where
        h' :: Int -> Int -> Int -> Int
        h' n maxi maxv
            | n == 1    = maxi + 1
            | otherwise =
                let r = collatz n 0 in
                    case (r > maxv) of
                        True  -> h' (n - 1) (n - 1) r
                        False -> h' (n - 1)  maxi   maxv

        collatz :: Int -> Int -> Int
        collatz n c
            | n == 1    = c + 1
            | even n    = collatz (n `div` 2) (c + 1)
            | otherwise = collatz (3 * n + 1) (c + 1)

以下は実行結果です。

% ghc collatz.hs
% ./a.out 100
97

追記 (2006/07/13 19:30)

問題の読み間違いをしていて、プログラムが間違っていたので、修正しました。