ちまた(っても Squeak 界隈限定)で流行りの Coke で遊んでみました


アラン・ケイSmalltalkSqueak)を卒業して次を目指すための秘密兵器と目される“黄金の箱”あるいは「イアンのやつ」 と呼ばれている Pepsi & Coke 。地球の重力(暫定ダイナブックSmalltalk )に魂を縛られた「オールドタイプ」な私には明らかに新たな「脅威」(^_^;)なわけですが、こいつがどの程度動くものなのか、はたして本当に Smalltalk(や LISP)を凌駕する「ニュータイプ」なのかも興味があるところなので、ちょっとだけいじってみました。


とは申しましても、「オールドタイプ」である以前に、私は言語処理系内部や仕組みことがよくわからないので(ぉぃぉぃ…)、サンプルに見つけて面白そうだなーと感じた PythonJavaScript もどきのコードを比較的高速に走らせるってのをチョしてみただけです(実質はたらいまわし大会?w)。けっきょく“コイツ”がかろうじて“動くぞ…”ってことは分かったのですが、どの程度「ニュータイプ」であるかどうかについては、まだ実感できていません(それって、LISP のマクロや SmalltalkSmaCC 使うのとどう違うの?…状態)。なので、もっとちゃんと知りたい人は、id:squeaker さんや id:propella さん、id:nqthm さんもこの「イアンのやつ」がらみの記事をエントリーされているので、とっかかりにはそちらのほうからどうぞ。


念のため、Pepsi & Coke のメーリングリストにおける Ian Piumarta 氏(名字の発音がワカンネ〜)の投稿によれば、

  • Id …… オブジェクトモデル。
  • idst …… Id なオブジェクトをハンドリングするための Smalltalk ライクな文法の言語。
  • idc …… idst のコンパイラ。idst 自身で記述されている。
  • Pepsi …… idc の別名。
  • Jolt …… idst で記述された AST(abstract syntax tree、抽象構文木コンパイラ。コード生成や動的パースの実験用。
  • Coke …… Jolt 上に構築された、idst のメッセージ式(Smalltalk)と AST のS式(LISP)を混在させられるスクリプト言語処理系。言語処理系の“すべて”をコントロール可能!?

とのことですが、私にはさっぱり…。w 例によってよく分からない例えをすれば、SELF のようにシンプルなオブジェクトモデルをベースに、CLOS MOP(あるいは Smalltalk)がごとく自己記述された、しかしそれらとは違って、エンドユーザーがアドホックにオブジェクトモデルの拡張、言語の文法やセマンティックスを改変することが可能であるような“開かれた”オブジェクトシステムによって構成される処理系を実現した…といったかんじでしょうか。


▼Coke のインストール

Coke のビルドには idc が必要です。idc については、各種環境用のバイナリ配布も行なわれているようですが、私はソースを make しました。

  1. idst-5.7-20061002.tar.gz を入手して展開
  2. idst-5.7 に移動して make; sudo make install 。環境別設定について、くわしくは INSTALL を参照のこと。
  3. examples/jolt に移動して make 。

▼Coke の起動と終了

example/jolt から移動していなければ、./main で対話環境が起動します。プロンプトは「.」(ドット)です。

$ ./main
Welcome to Jolt 0.1 [VPU 4.0.1 ppc darwin 2004-05-03 14:46:24]
.(+ 3 4)
 => 7


Objective-C のように [ ] で括ってメッセージ式(Smalltalk 式)を書くことができますが。いろいろと便利な関数を使ったりオブジェクトを参照するのには起動時に boot.k を読み込んでおくほうがよいようです(boot.k の中味を読むと、何が追加されるのか分かります)。boot.k の他にも読み込ませたい(評価したいコードを含む)ファイルがあれば、そのファイル名も続けて書きます。なお、ファイル群(boot.k だけの場合も含む)を評価したあと終了せずに対話環境にとどまりたい場合には最後に「-」を付ける必要があります。

$ ./main boot.k -
Welcome to Jolt 0.1 [VPU 4.0.1 ppc darwin 2004-05-03 14:46:24]
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'syntax.k
; loading: 'object.k
.(printf "Hello, World!\n")
Hello, World!
 => 14
.[StdOut nextPutAll: '"Hello, World!\n"]
Hello, World!
 => 29


終了は boot.k を読み込んでいれば、(exit) が使えますが、そうでなければ ctrl-D で。



▼Coke で Python もどき

exampes/jolt/python に test.py というフィボナッチ数列を使ったサンプルがあります。make; make run か、いったん make したあとは、「../main boot.k monty.k meta-repl.k monty.mk test.py」で起動可能です。

$ make run
../main boot.k monty.k meta-repl.k monty.mk test.py
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'match.k
; loading: 'sugar.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling Monty compiler... done.
Entering Monty read-eval-print loop.
nfibs( 0 ) = 1 took 0 ms
nfibs( 1 ) = 1 took 0 ms
nfibs( 2 ) = 3 took 0 ms
nfibs( 3 ) = 5 took 0 ms
<snip>
nfibs( 25 ) = 242785 took 109 ms
nfibs( 26 ) = 392835 took 170 ms
nfibs( 27 ) = 635621 took 280 ms
nfibs( 28 ) = 1028457 took 449 ms
nfibs( 29 ) = 1664079 took 730 ms
nfibs( 30 ) = 2692537 took 1169 ms


test.py は Python でも実行できます。

$ python test.py
nfibs( 0 ) = 1 took 0 ms
nfibs( 1 ) = 1 took 0 ms
nfibs( 2 ) = 3 took 0 ms
nfibs( 3 ) = 5 took 0 ms
nfibs( 4 ) = 9 took 0 ms
nfibs( 5 ) = 15 took 0 ms
<snip>
nfibs( 25 ) = 242785 took 364 ms
nfibs( 26 ) = 392835 took 615 ms
nfibs( 27 ) = 635621 took 973 ms
nfibs( 28 ) = 1028457 took 1511 ms
nfibs( 29 ) = 1664079 took 2470 ms
nfibs( 30 ) = 2692537 took 4467 ms


test.py の代わりに - で対話環境にとどまれば(プロンプトは「:」)、任意の Python コードをインタラクティブに評価できます。っても、関数定義とその実行以外、肝心なことはクラス定義も配列もぜんぜんダメみたいです(いちおうそれぞれに対応する文法の定義っぽいのは monty.mk に書いてあるらしいのですが…)。

$ ../main boot.k monty.k meta-repl.k monty.mk -
Welcome to Jolt 0.1 [VPU 4.0.1 ppc darwin 2004-05-03 14:46:24]
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'match.k
; loading: 'sugar.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling Monty compiler... done.
Entering Monty read-eval-print loop.
:class Foo:
:  pass
:

syntax error: ()
Abort trap
Entering Monty read-eval-print loop.
:[1,2,3]

undefined: MontyList
Abort trap


ここいらへんの不都合をふまえて、Coke でも Python でも動く、たらい回し関数(Tak y)を taky.py として書いてみました。

from time import time

def taky(x, y, z):
  if x<=y:
    return y
  else:
    return taky(taky(x-1, y, z), taky(y-1, z, x), taky(z-1, x, y))

x = 12;
y = 6;
z = 0;

t1 = time()
ans = taky(x, y, z)
t2 = time()
print "tarai(", x, ",", y, ",", z, ") =", ans, "took", int((t2 - t1) * 1000), "ms"


結果はこんな感じ。

$ python taky.py
tarai( 12 , 6 , 0 ) = 12 took 17886 ms

$ ../main boot.k monty.k meta-repl.k monty.mk taky.py
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'match.k
; loading: 'sugar.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling Monty compiler... done.
Entering Monty read-eval-print loop.
tarai( 12 , 6 , 0 ) = 12 took 7359 ms

▼ Coke で JavaScript もどき

examples/jolt/javascript に移動してから make run すれば、Python もどきと同様のフィボナッチ数列を用いたベンチが走ります。

$ cd ../javascript
$ make run
../main boot.k meta-repl.k js.mk test.js
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling JavaScript compiler... done.
Entering JavaScript read-eval-print loop.
nfibs(0) = 1 took 0 ms
nfibs(1) = 1 took 0 ms
nfibs(2) = 3 took 0 ms
nfibs(3) = 5 took 0 ms
nfibs(4) = 9 took 0 ms
nfibs(5) = 15 took 0 ms
<snip>
nfibs(25) = 242785 took 110 ms
nfibs(26) = 392835 took 170 ms
nfibs(27) = 635621 took 280 ms
nfibs(28) = 1028457 took 440 ms
nfibs(29) = 1664079 took 730 ms


前後をタグで括った test.html を WWW ブラウザで開けば、ちゃんとした JavaScript での結果も得られます。

nfibs(0) = 1 took 0 ms
nfibs(1) = 1 took 0 ms
nfibs(2) = 3 took 0 ms
nfibs(3) = 5 took 0 ms
nfibs(4) = 9 took 0 ms
nfibs(5) = 15 took 0 ms
<snip>
nfibs(25) = 242785 took 380 ms
nfibs(26) = 392835 took 633 ms
nfibs(27) = 635621 took 1048 ms
nfibs(28) = 1028457 took 1643 ms
nfibs(29) = 1664079 took 2681 ms


Python と同じく、たらいまわし関数も定義してみました。Coke の JavaScript もどきでは、Date.getTime() が使えないので、一部、妙なことをやっていますがあしからず。

function taky(x, y, z){
  if (x <= y) return y;
  else return taky(taky(x-1, y, z), taky(y-1, z, x), taky(z-1, x, y));
}

var x = 12, y = 6, z = 0;

var d = new Date(), t0 = d.getSeconds() * 1000 + d.getMilliseconds();
var ans = taky(x, y, z);
var d = new Date(), t1 = d.getSeconds() * 1000 + d.getMilliseconds();
var delta = (60000 + t1 - t0);
if (delta > 60000) { delta = delta - 60000; }
document.write("tarai( " + x + " , " + y + " , " + z + " ) = " + ans + " took " + delta + " ms\n");
$ ../main boot.k meta-repl.k js.mk taky.js
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'syntax.k
; loading: 'object.k
; loading: 'meta.k
; loading: 'meta-coke.k
; loading: 'meta-meta.k
; loading: 'CheckpointStream.k
Parsing and compiling JavaScript compiler... done.
Entering JavaScript read-eval-print loop.
tarai( 12 , 6 , 0 ) = 12 took 7100 ms


これは手元の環境では遅すぎて WWW ブラウザではダメなので、document.write を print に書き換えたものを SpiderMonkey (JavaScript-C) に喰わせた結果を次に。

$ js -v
JavaScript-C 1.5 2004-09-24

$ js taky.print.js
tarai( 12 , 6 , 0 ) = 12 took 41970 ms

▼Coke で たらいまわし

ふと、素の Coke だとどうなのかな…と思ったので、これも書いてみました。

(define taky (lambda (x y z)
  (if (<= x y) y
    (taky (taky (- x 1) y z) (taky (- y 1) z x) (taky (- z 1) x y)))))

(define Time (import "Time"))

(define x 12)
(define y 6)
(define z 0)

(define t0 [Time millisecondClockValue])
(define ans (taky x y z))
(define t1 [Time millisecondClockValue])
(printf "tarai( %i , %i , %i ) = %i took %i ms\n" x y z ans [t1 - t0])
$ ./main boot.k taky.k
; loading: 'boot.k
; loading: 'quasiquote.k
; loading: 'match.k
; loading: 'sugar.k
; loading: 'syntax.k
; loading: 'object.k
tarai( 12 , 6 , 0 ) = 12 took 1013 ms

こやつ、なかなかやります。


ちなみに Squeak Smalltalk で試すと Win 環境では拮抗していますが、OS X では 4 秒弱で負けます。

Object subclass: #TakY

TakY class >> x: x y: y z: z
    x <= y ifTrue: [^ y].
    ^ self x: (self x: x-1 y: y z: z)
           y: (self x: y-1 y: z z: x)
           z: (self x: z-1 y: x z: y)
[TakY x: 12 y: 6 z: 0] timeToRun   "=>  3689 "

むっきー!


もちろん VisualWorks Smalltalk(Cincom Smalltalk)なら負けません。

Time millisecondsToRun: [TakY x: 12 y: 6 z: 0]   "=> 628 "

あー、でもちょっとヤバイ?



あと、比較のため、LISP 系の言語でもやってみました。っても、手元で動かせるのは CLISP だけなのですが…。

$ cat taky.lisp
(defun taky (x y z)
  (if (<= x y)
      y
      (taky (taky (1- x) y z)
            (taky (1- y) z x)
            (taky (1- z) x y))))

$ clisp
  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2006

[1]> (load "taky.lisp")
;; Loading file taky.lisp ...
;; Loaded file taky.lisp
T

[2]> (time (taky 12 6 0))
Real time: 84.4484 sec.
Run time: 80.32884 sec.
Space: 0 Bytes
12

[3]> (compile-file "taky.lisp")
;; Compiling file /Users/sumim/taky.lisp ...
;; Wrote file /Users/sumim/taky.fas
0 errors, 0 warnings
#P"/Users/sumim/taky.fas" ;
NIL ;
NIL

[4]> (load "taky.fas")
;; Loading file taky.fas ...
;; Loaded file taky.fas
T

[5]> (time (taky 12 6 0))
Real time: 8.330962 sec.
Run time: 7.981652 sec.
Space: 0 Bytes
12

コンパイルしたコードでようやく勝負になる感じ。CLISPたらいまわしが苦手?


Scheme でも。

gosh> (define (taky x y z)
        (if (<= x y) y
          (taky (taky (- x 1) y z)
                (taky (- y 1) z x)
                (taky (- z 1) x y))))
taky

gosh> (use gauche.time)
#<undef>

gosh> (time (taky 12 6 0))
;(time (taky 12 6 0))
; real   4.730
; user   4.450
; sys    0.020
12

Gauche はえー。←すでに当初の目的は忘れさられている(^_^;)。



id:sumim:20070307:p1 に続く…