haskell and pure (3)

某宴会で聞いた話。大地主もとえ大痔主の体験記。

幼少の頃から便秘がちだった某君。我慢を重ねて、切れ痔、いぼ痔の主になりましたとさ。 仕事が運転手。余計に痔に悪いわな。痔主が重宝する穴空き座布団を併用しながら、痔と 共生してきたそうな。

でも、最近になってどうしても我慢がならず、一大決心して、痔と決別する事にしたそうな。 痔の手術では有名な先生が居るという病院を訪ねた所、予約で一杯。半年待ちのキューに つっこまれたとか。そして、最近やっとサービスの順番が回ってきたそうな。

ネットであらかじめ準備品とか体験談を調べておいたそうな。どうも痔のサークルと言うか 掲示板があるみたい。それによると、入院は月初めがお勧めとか。月をまたぐと還付金が 十分に返ってこなくて損との事。

術後は出血する事があるので、厚手のナプキンが必需品とか。多くても安心、朝まで 安心ってやつが安心だよーーと、品名を具体的に挙げて説明してくれてたとか。痔同盟は 結束が固いな。同病相憐れむってのの具体例ですな。彼女にでも買ってきてもらえばいいんだろうけど、 残念ながら某君は彼女いない歴が長く、一人ぐらしの為それも出来ず。コンビニにさりげなく (恥ずかしさを押さえて)買ったそうな。2回目からは、堂々と買えるようになったって 言ってた。男は度胸だな!!

で、いよいよ手術。麻酔もいろいろなオプションがあるらしい。オプションを選択すると 長時間効果が持続するとか。標準を選んじゃった為、枕を濡らしてしまったそうだ。 先生はサービス精神旺盛で、これも切っておくかって事で、痔の芽も撤去してくれたとか。 それで余計に痛かったんですかね?

術後の排泄は一大決心が必要との事。普通にウォシュレットが付いている所なんだけど、 弱い水流にもかかわらず、飛び上がってしまったそうだ。病室によっては、痔主用の 設備が用意してあるらしい。水中出産ならぬ水中排便が出来る便器とか。これが一番 痔主に負担をかけないものらしい。

いろいろ面白い話を聞かせてくれて、ありがとう。お大事に!

Haskell vs. Ocaml

オイラーは暇な時、人様の記事を読むのが好きだ。んでもって、いつものようにブラブラと ブラウズしてたら、 HaskellとOcamlの速度を比較してみた なんてのに行き当たった。まあ、普通の人はやってみたくなるわな。よっぽど、ruby love みたいに一穴主義でない限り。。。いろいろ比べてみたい訳だ。

この記事の何が面白いって、外野の突っ込みと言うか応援が、代理戦争っぽくてGoodな記事に なってる所。

IT Proに記事を書いてる方が、コンパイルオプションを付けたらいいんでないかいと言ったり、 別な人が、使う関数を変えてみたらと突っ込んだり、重鎮が出てきて、コードの改良に 取り組んだり。。 Ocamlの宣教師さんまで登場して、マジックを披露してくださったり。 Loveな人が沸いてきてて面白いし、勉強になるなあ。これが高じると、ゴルフになって 自慢が始まる訳だ。たまにはゴルフ場へ行ってみるかな。

そうそう、元記事を書いた方、オイラーみたいに浮気症っぽくて、いろいろな物に手を出して いる。 ScalaのパーサコンビネータでScheme処理系を作る なんてのもやってる。パーサコンビネータって、Haskellの特許かと思ったら、類似品が 出ているのね。F#版のものもあるぞ。以下、それぞれの言語用パーサコンビネータ。

Pythonicな文字列パーサ parse

Rubyによるパーサコンビネータrparsec

純粋関数型雑記帳

Real World Haskell

上で出てきた、最適化の話、どこかで読んだなと思ったら、 Real World Haskell本の所に 強烈なやつが出てた。このRWH本は熱さ(厚さ)ゆえ、最後まで読みきれず、途中で投げ出して しまっていたんだ。

章ごとに拾い読みでもいいから読むとためになるぞ。ってな事で、プロファイリングの他にも 目を通してみた。ネットワークの章。pureでUDPをやったからね。GHCではどうなっているかと 思って見たんだ。

そしたら、純粋なネットワークの部分は、インポートしてる、Network.Socket とNetwork.BSD っぽい。幸いな事に、常用してるPCBSDにGHCが入っていたよ。 まずは、どんなパッケージがはいっているかだな。

[sakae@pcbsd ~]$ ghc-pkg list
/usr/local/lib/ghc-7.4.1/package.conf.d
     :
   base-4.5.0.0
     :
   ghc-prim-0.2.0.0
   haskell2010-1.1.0.1
   haskell98-2.0.0.1
     :
   unix-2.5.1.0

haskell98とかhaskell2010とか散見されるけど、これって、haskellの歴史を背負ってるって 事なんかな? ここに入っているのは、全てバイナリーになってて、ソースを見る楽しみが無い。

で、Haskellの重鎮の方が、Haskellの元になったのを見るといいよと言っていたのを思い出した。 その名は、ゴルフっぽかったな。えと、カタログを調べてみるか。

Gofer

[sakae@pcbsd /usr/ports/lang/Gofer]$ cat pkg-descr
------------------------------------------------------------------------------
Gofer is an interactive functional programming environment (i.e. an
interpreter) supporting a language based on the draft report for Haskell
version 1.2.

Gofer is intended as an experimental language, particularly where type classes
are involved.  Gofer extends the Haskell type class system in several ways.

The most significant features of Haskell not currently supported are:
modules, arrays, overloaded numeric constants, default declarations, derived
instances, contexts in datatype definitions.
------------------------------------------------------------------------------
And just in case you wondered:

   The name "Gofer" is not a trademark, registered  or  otherwise,  and
   you are free to mention this name in published material, public  and
   private correspondence, or other documents  without  restriction  or
   obligation.
------------------------------------------------------------------------------

WWW: http://web.cecs.pdx.edu/~mpj/goferarc/

ゴルフじゃなくて、goferだった。辞書で意味を調べてみると、【名】雑用係、小使、使い走り ってような意味らしい。こき使えって事だな。

srcの中に置いてあったReadmeを見ると

This directory contains the source for Gofer 2.30a, including both the
interpreter and compiler.

何と、コンパイラーまで付いている。そして、どんな環境でコンパイル出来るかと言うと

#define TURBOC   0      /* For IBM PC, using Turbo C 1.5                   */
#define BCC      0      /* For IBM PC, using Borland C++ 3.1               */
#define WATCOM   0      /* For IBM PC, using WATCOM C/C++32 v9.5           */
#define ZTC      0      /* For IBM PC (>= 386) Zortech C++ v3.0 (-mx)      */
#define DJGPP    0      /* For DJGPP version 1.09 (gcc2.2.2) and DOS 5.0   */
#define OS2      0      /* For IBM OS/2 2.0 using EMX GCC                  */
#define SUNOS    0      /* For Sun 3/Sun 4 running SunOs 4.x               */
#define MIPS     0      /* For MIPS RC6280/Sony machine NWS-3870        UN */
#define NEXTSTEP 0      /* For NeXTstep 3.0 using NeXT cc                  */
#define NEXTGCC  0      /* For NeXTstep with gcc 2.x, doesn't work w/ NS3.2*/
#define MINIX68K 0      /* For Minix68k with gcc                        UN */
#define AMIGA    0      /* For Amiga using gcc 2.2.2                    UN */
#define HPUX     0      /* For HPUX using gcc                              */
#define LINUX    0      /* For Linux using gcc                          UN */
#define FREEBSD  1      /* For FreeBSD using gcc                        UN */
#define RISCOS   0      /* For Acorn DesktopC and RISCOS2 or 3             */
#define ALPHA    0      /* For DEC Alpha with OSF/1 (32 bit ints, no gofc) */
#define SVR4     0      /* For SVR4 using GCC2.2                           */
#define ULTRIX   0      /* For DEC Ultrix 4.x using GCC2.3.3               */
#define AIX      0      /* For IBM AIX on RS/6000 using GCC                */
#define ATARI    0      /* For Atari ST/STE/TT/Falcon w/ Lattice C 5.52 UN */
#define SGI4     0      /* For SiliconGraphics Indigo, IRIX v*4*.0.5    UN */
#define NETBSD   0      /* For NetBSD-current                              */

時代がかったマシンが並んでますなあ。SunOSの所には、M68Kマシンでコンパイルする にはなんて事が書かれていて、いったいいつの時代よと思っちゃうぞ。1994年のクレジットが 刻印されてた。 取り合えず、コンパイルしてみた。

Gofer Version 2.30b  Copyright (c) Mark P Jones 1991-1995

Reading script file "standard.prelude":

Gofer session for:
standard.prelude
Type :? for help
? 

20年前から蘇ってきましたよ。その骨格はどうなってるかというと

[sakae@pcbsd /usr/ports/lang/Gofer/work/src]$ ls *.o
builtin.o       compiler.o      input.o         runtime.o       type.o
cbuiltin.o      gofc.o          machine.o       static.o
cmachine.o      gofer.o         output.o        storage.o

ああ、preludeも何種類か用意されてて、一番小さいやつが、これ

infixr 5 :
infixr 3 &&
infixr 2 ||

(&&), (||)     :: Bool -> Bool -> Bool
False && _      = False     -- (&&) and (||) names predefined in Gofer
True  && x      = x
False || x      = x
True  || _      = True

flip           :: (a -> b -> c) -> b -> a -> c
flip  f x y     =  f y x

primitive error "primError" :: String -> a

これを元にして、自分色に仕立て上げていくんだな。論理演算のショートカットが好く 分かる例になってるな。

で、冒頭のgoferの説明では、C語へのトランスレータも付いているって事なんで、試してみる。 例題は、例に載ってた、calender.gsってやつ。サフィックスが何かとかぶっているようだけど、 気にしない。emacsから開いてみたら、ちゃんとhaskell一族(って言うか、ご先祖様)って 認識してくれたぞ。

[sakae@pcbsd ~/tmp]$ goferc calendar.gs
Gofer->C Version 1.03 (2.30b)  Copyright (c) Mark P Jones 1992-1995

Reading script file "/usr/local/lib/Gofer/standard.prelude":
Reading script file "calendar.gs":

Writing C output file "calendar.c":
[Leaving Gofer->C]
[Compiling with gcc]
/usr/local/bin/goferc: %%CC%%: not found
strip: './calendar': No such file

あろう事か、エラーを喰らってしまったぞ。今の環境に馴染まないんですかね? 何せ古いソフトですから。まてまて、諦めのは早いぞ。どうも/usr/local/binの下に置いて あるgofercが、曲者っぽいぞ。精査してみるか。

GOFER=/usr/local/lib/Gofer/standard.prelude
export GOFER
/usr/local/lib/Gofer/gofc $args
echo '[Compiling with gcc]'
%%CC%% -o $prog %%CFLAGS%% $prog.c /usr/local/lib/Gofer/runtime.o -lm
strip $prog
rm $prog.c

要は、こういうコンパイラードライバーですた。今後の事を考えて、改修しておけば いいんだろうけど、取り合えず、その前に自分がshellになった積もりで実験。

[sakae@pcbsd ~/tmp]$ export GOFER=/usr/local/lib/Gofer/standard.prelude
[sakae@pcbsd ~/tmp]$ /usr/local/lib/Gofer/gofc calendar.gs
Gofer->C Version 1.03 (2.30b)  Copyright (c) Mark P Jones 1992-1995

Reading script file "/usr/local/lib/Gofer/standard.prelude":
Reading script file "calendar.gs":

Writing C output file "calendar.c":
[Leaving Gofer->C]
[sakae@pcbsd ~/tmp]$ gcc calendar.c /usr/local/lib/Gofer/runtime.o -lm

来年も近いので、2014年のカレンダーを出力しとくか。

[sakae@pcbsd ~/tmp]$ ./a.out 2014
                                   2014

         January                 February                   March
   Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
             1  2  3  4                        1                        1
    5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
   12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
   19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
   26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                     30 31
         :

折角なので、haskell風味のカレンダー作成術を見ておけよ。。昔、perlで予定表の CGIを書いた事がある。その時、当月を中心に前後の月のカレンダーを表示させた。 どうやって、3月分を横に並べるか苦労したものだ。今となっては、どうやったか 忘れてしまったけど、Haslellだと、苦労せずに綺麗に書かれているんだろうな。

このまま、goferのソース探検に出て行ってもいいんだけど、その前にpureのソースを globalにかけておいたのを思い出した。先にそちらを探検してみるか。

pureのソース探検

いきなり探検と言われても目的地を明確にしておかないと、物見予讃になってしまう。 そこでだ、目的を絞って、blobをどうやって実現してるか見てく事にする。blobって、pure界の シリアライズ系。

ヤマトに頼むにしろ日本郵便に頼むにしろ飛脚便を頼むにしろ、途中で 解凍とかの憂き目に会うご時勢。 パケットは効率良く届けましょう、破損は検出してあげましょうっという自己防御機構を 実現したものです。皆さんも自衛の為、クール便を頼む時は、温度センサーを入れて おきましょうって事です。そういう茶々はさておき、blobって何処で定義されてるの?

軽くgrepしたら、runtime.h とruntime.ccに有った。おいらは情弱なんで果たしてCフラフラ語を 読めるかな? 当たって砕けろ!! 強い味方globalの支援も有る事だし。

10122 pure_expr *blob(pure_expr *x)
10123 {
10124   Blob b;
10125   map<pure_expr*,size_t> ref;
10126   size_t key = 0;
10127   bool ret = dump(b, ref, key, x);
              :

こんなコードで始まっていました。Blobって型式はどういうの? マウスのカーソルを近づけたら 9275行目で定義されてるよーって教えてくれました。段々親切になってくね。global君。 仰せに従って、飛んで行ってみると、

9275 struct Blob {
9276   void *buf, *mem;
9277   size_t pos, size;
9278   int src_endian, dest_endian;
9279   map<int32_t,symentry> symtab;
9280   size_t align(size_t pos)
9281   {
9282     // Align to 8 byte boundaries.
9283     unsigned a = pos%8;
9284     if (a > 0) a = 8-a;
9285     return pos+a;
9286   }

ふむふむ、endianなんてのも囲ってて、ネットワーク対応が万全でっせって事か。8バイト 境界に揃えてるのは、早く64Bitの世界へおいでよって言う、無言の圧力だな。今使ってる 32Bitマシンでは、とうとう最新のPCBSDがサポートされなくなっちゃったからなあ。

こういう戯言は、あの人と居酒屋でやるに限るな。けんとーしてるMAC君、今有るプリンターが 動くのかいな? 周辺のサポートが不安で窓XPから脱却出来ない人を何人も知っていますよ。 そう、問題は周辺に有りって事をどっかのメーカーの人は知らないのよ。一発でXPから8へ 引越し出来るって言われたって、庶民の事を考えていない絵空言ですからね。

ああ、愚痴っちゃった。blobの本命はdumpに有りって事を心に留めて、上記の型式定義の 周辺を見ると、

9287   // Create and write a blob.
9288   Blob()
9289     : buf(0), mem(0), pos(0), size(0), src_endian(0), dest_endian(0)
9290   {
9291     write_header();
9292   }

コロン区切りで書いてある、buf(0)とかは、Cふらふら語の方言かな? 何となく、 それぞれの項を初期化してると思えるんだけど、今更ロベールの本を読む(買う)気には ならないよな。ロベールのC++教室 を拾い読みしておくかな。write_headerをつついてみると、

9321   void write_header()
9322   {
9323     hdrdata h = { (int32_t)MAGIC, // magic header
9324                   // these will be patched up later
9325                   0,   // crc
9326                   0,   // size
9327                   0 }; // offset of symbol table
9328     write(sizeof(hdrdata), &h);
9329   }

MAGICなんていう、怪しげなやつが出てきたので、探ってみると

9227 #define MAGIC 0x87329d00

ふむふむ、それじゃ少し実験。pureを動かして、blobを作ってみる。pureはマトリックスも 得意らしいので、それを使ってみた。何でも入るのね。

> let a = blob {"START", ["aa", 1023, "bb"], "END"};
> a;
#<pointer 0x2a155b00>
> #a;
196L
> uint $ blob_crc a;
2695866606L

小さいデータでも、結構厳重に梱包してるのね。だからこれを使うには、大きい荷物の 方が効率いいよって事だな。そして、怪しげに、blobの格納場所とおぼしきポインターを 報告してきているぞ。

だったら、動いているpureを生体解剖して、観察してみましょう。えと、久しぶりにメス じゃなかった、gdbを取り出してみるか。まずは、検体番号をpsで取得しといて、

[sakae@pcbsd ~]$ gdb pure 33995
(gdb) x/16xw 0x2a155b00
0x2a155b00:     0x87329d00      0xa0afa8ee      0x000000c4      0x00000000
0x2a155b10:     0x000000b0      0x00000000      0xffffffe0      0x00000000
0x2a155b20:     0x00000001      0x00000000      0x00000003      0x00000000
0x2a155b30:     0xfffffffa      0x00000000      0x00000006      0x00000000

ふむ、メモリー上では、ちゃんとヘッダー情報から並んでいるのね。次は、9762行目から始まる、 dumpに制御が遷るんだな。渡されたpure式をそれぞれ判別して、形式に合ったBlob内の dumpに身を任せるとな。ちょいと調べてみると、Cふらふらの構造体はクラス相当とか。 深入りするのは止そう。

pureのオブジェクトがどうなってるかと思ったら、expr.hhに定義されてた。まずはtag関係

211   // special type tags:
 212   enum {
 213     VAR         = 0,    // locally bound variable
 214     FVAR        = -1,   // locally bound function
 215     APP         = -2,   // function application
 216     // built-in (C) types:
 217     INT         = -3,   // 32 bit signed integer
 218     BIGINT      = -4,   // bigint (mpz_t)
 219     DBL         = -5,   // double precision floating point number
 220     STR         = -6,   // utf-8 string (char*)
 221     PTR         = -7,   // generic pointer (void*)
 222     // conditionals and binding expressions:
 223     COND        = -8,   // conditional expression (if-then-else)
 224     COND1       = -9,   // one-way conditional (guarded expression)
 225     LAMBDA      = -10,  // lambda expression
 226     CASE        = -11,  // case expression
 227     WHEN        = -12,  // when expression
 228     WITH        = -13,  // with expression
 229     // wrapper for embedded runtime expressions
 230     WRAP        = -14,  // pointer to variable binding (GlobalVar*)
 231     // GSL-compatible matrix types:
 232     MATRIX      = -32,  // generic GSL matrix, symbolic matrices
 233     DMATRIX     = -31,  // double matrix
 234     CMATRIX     = -30,  // complex matrix
 235     IMATRIX     = -29,  // integer matrix

そして、データの格納場所は、以下のように定義された。

 258   // data:
 259   union {
 260     int32_t i;    // INT
 261     mpz_t   z;    // BIGINT
 262     double  d;    // DBL
 263     char   *s;    // STR
 264     void   *p;    // PTR
 265     struct {      // VAR, FVAR
 266       int32_t vtag; // real symbol
 267       path   *p;    // subterm path (VAR)
 268       uint8_t idx;  // de Bruin index
 269     } v;
 270     EXPR   *x[3]; // APP, COND, COND1
 271     exprll *xs;   // MATRIX
 272     struct {      // LAMBDA
 273       exprl *xs;  // arguments
 274       rule  *r;   // rule (r->rhs is body)
 275     } l;
 276     struct {      // CASE, WHEN, WITH
 277       EXPR  *x;   // expression
 278       union {
 279         rulel *r; // rule list (CASE, WHEN)
 280         env   *e; // function environment (WITH)
 281       };
 282     } c;
 283   } data;

これらを念頭に、dumpの中のコードを見ていけば良いんだな。

9809   case EXPR::INT:
9810     b.dump(EXPR::INT, x->data.i);
9811     ref[x] = key++;
9812     return true;
9813   case EXPR::BIGINT:
9814     b.dump(EXPR::BIGINT, x->data.z);
9815     ref[x] = key++;
9816     return true;
9817   case EXPR::DBL:
9818     b.dump(EXPR::DBL, x->data.d);
9819     ref[x] = key++;
9820     return true;
9821   case EXPR::STR:
9822     b.dump(EXPR::STR, x->data.s);
9823     ref[x] = key++;

refって配列は、オブジェクトをごみ回収に出すかどうどの判定に使う、リファレンスカウンター なんだろうな。それぞれの形式のオブジェクトは、それ専用のdumpが用意されている。 たとえば、intなら

9383   void dump(int32_t tag, int32_t x)
9384   {
9385     assert(tag == EXPR::INT);
9386     write(sizeof(int32_t), &tag);
9387     write(sizeof(int32_t), &x);
9388   }

文字列なら

9406   void dump(int32_t tag, const char *x)
9407   {
9408     assert(tag == EXPR::STR);
9409     size_t n = strlen(x)+1;
9410     data1 d = {tag, n};
9411     write(sizeof(data1), &d);
9412     write(n, x);
9413   }

それぞれ、tagに続いて、オブジェクトが書き込まれるんだな。

そんじゃ、前回やったパケットを飛ばすやつで、キャプチャしてみる。

[sakae@pcbsd ~]$ sudo tcpdump -i lo0 -X -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 65535 bytes
14:11:58.369565 IP 127.0.0.1.5002 > 127.0.0.1.5001: UDP, length 100
        0x0000:  4500 0080 0244 0000 4011 0000 7f00 0001  E....D..@.......
        0x0010:  7f00 0001 138a 1389 006c 79cc 009d 3287  .........ly...2.
        0x0020:  2b6b be37 6400 0000 0000 0000 5000 0000  +k.7d.......P...
        0x0030:  0000 0000 faff ffff 0000 0000 2600 0000  ............&...
        0x0040:  0000 0000 7b22 5354 4152 5422 2c20 5b22  ....{"START",.["
        0x0050:  6161 222c 2031 3032 332c 2022 6262 225d  aa",.1023,."bb"]
        0x0060:  2c20 2245 4e44 227d 3b00 432b 0000 0000  ,."END"};.C+....
        0x0070:  0000 0000 0000 0000 0000 0000 99ed ffff  ................
14:11:58.371072 IP 127.0.0.1.5001 > 127.0.0.1.5002: UDP, length 196
        0x0000:  4500 00e0 0245 0000 4011 0000 7f00 0001  E....E..@.......
        0x0010:  7f00 0001 1389 138a 00cc 06f2 009d 3287  ..............2.
        0x0020:  12b4 93a0 c400 0000 0000 0000 b000 0000  ................
        0x0030:  0000 0000 e0ff ffff 0000 0000 0100 0000  ................
        0x0040:  0000 0000 0300 0000 0000 0000 faff ffff  ................
        0x0050:  0000 0000 0600 0000 0000 0000 5354 4152  ............STAR
        0x0060:  5400 ffff d0ff ffff 0000 0000 0300 0000  T...............
        0x0070:  0000 0000 faff ffff 0000 0000 0300 0000  ................
        0x0080:  0000 0000 6161 002a 0100 0000 fdff ffff  ....aa.*........
        0x0090:  ffff ffff ff03 0000 0000 0000 faff ffff  ................
        0x00a0:  0000 0000 0300 0000 0000 0000 6262 0000  ............bb..
        0x00b0:  0200 0000 faff ffff 0000 0000 0400 0000  ................
        0x00c0:  0000 0000 454e 4400 0000 0000 0000 0000  ....END.........
        0x00d0:  0000 0000 0000 0000 0000 0000 99ed ffff  ................

うーん、対応させるのって微妙だな。blobと対になるvalから攻めてみるのも一興かと。 でも、Cフラフラ語って追い憎いな。同じ関数名がわんさか定義してあって、何が違う かと言うと、引数の型式やアリティーが違うだけだもの。こんなの人間が読むもの じゃ無くて、コンピュータが読むものだよ。奴は疲れないからね。

今年の更新はこれで終わりです。来年も http://space.geocities.jp/hamesspam/を 宜しくおねがいします。