scm
postscript
ちょいと前回やったforallが分からなかったので、調べてみた。
GS>[1 2 3 4] {dup mul} forall GS<4>stack 16 9 4 1
これってschemeのmapを後置法にした物なのね。{}で括ったやつは、無名関数だな。そう思うと、schemeとpostscriptって相性が非常に良さそう。あのこよなくschemeを愛でておられる和田先生もpostscriptを常用されてるそうだから。
きっと、他にもscheme好きがpostscriptを使っているに違い無い。探してみたら、 GaucheでPostScriptを扱う なんて人が居たよ。
scm
slibはscmのライブラリィーです、って気づいてしまったので、懐かしいscmをやってみる。 本拠地は、 The SCM Implementation of Scheme になる。
そして、オイラーの所にはホコリを被っていた、『入門Scheme』なんて本が所蔵されたので、引っ張り出してきた。1999年の発刊だ。古いねぇ。あの頃のオイラーは、なかなか理解出来なかったけど、今ならスラスラ読める。人間が進化したんだな。
OpenBSDでもパッケージになってた。歴史的だな。
install scm
リナでもaptになってるけど、自前で入れてみるか。手順は下記だ。
bash$ (cd slib; ./configure --prefix=/usr/local/) bash$ (cd scm > ./configure --prefix=/usr/local/ > make scmlit > sudo make all > sudo make install) bash$ (cd slib; sudo make install)
slibとscmはセットになってる。上記のように、カッコで括ってコマンドを発行しろ? 幾らカッコ好きなschemerでも、ちとやりすぎでは? そんなサブシェルに任せなくてもいいしょ。
先にslibをインストールしてから、scmの製作にかかる事。そうでないと、scmのインストールでエラーになる。なお、slibもscmも /usr/local/lib/の下に主要部分がインストールされる。
パッケージから入れると、slibもscmも、/usr/local/shareの下に入るようなんで、ちょっと注意が必要かも。
それから、おじさん感丸出しのemacs使いはinfoもemacsで閲覧するけど、Xのインターフェースを説明した書が、インストールしたままだと、見れない。インストール時は、下記のようになる。
sakae@pen:/tmp/m/scm$ ls *info hobbit.info scm-5f3.info scm.info Xlibscm.info sakae@pen:/tmp/m/scm$ sudo ginstall-info /usr/local/share/info/Xlibscm.info /usr/local/share/info/dir
ついでにdirも更新される。
sakae@pen:/tmp/m/scm$ cat /usr/local/share/info/dir : * XlibScm: (XlibScm). SCM Language X Interface. * hobbit: (hobbit). SCM Compiler.
が、ファイル名が一致していない。ファイル名を、X11libScm.infoに変更しておこう。細かい事だけど、プチ焦ったぞ。
それからscmの作成は、ちょっと複雑な工程を経ている。まずslibのソースが必須という事。これは、scm作成中にslibのソースを必要になるんだ。そのあたりを見ていく。
make scmlit
scm作成の前段階として、lightなscmを作成する。軽いって事は、最低限のscmって事。種だな。
echo "#ifndef IMPLINIT" > newflags.h echo "#define IMPLINIT \"/tmp/m/scm/Init5f3.scm\"" >> newflags.h echo "#endif" >> newflags.h echo "#define CHEAP_CONTINUATIONS" >> newflags.h echo "#define CAUTIOUS" >> newflags.h if (cmp -s newflags.h scmflags.h) then rm newflags.h; \ else mv newflags.h scmflags.h; fi cc -c scm.c -o scm.o : cc -o scmlit scm.o time.o repl.o scl.o sys.o eval.o subr.o unif.o rope.o continue.o findexec.o script.o debug.o scmmain.o
ヘッダーファイルを新たに作って、その元でコンパイルしてる。
事前にconfigureしとく事は当然なんだけど、それで新たにMakefileが作成は行われない。config.statusを既存のMakefileの中へ取り込むと言う、常識外れの事をやっている。いや、これは発想の転換ですよ。この技を覚えておいたら、何処かで使い道が有る鴨。
make all
次は、備え付けのbuildって言うshellスクリプト(実は、scmlitで動かす、コンパイラーシステム)を起動。slib側のbatch機能を使ってscm用のMakefileもどきを作り、それを走らせて、soファイル群を作成。
make[2]: Entering directory '/tmp/m/scm' ./build -hsystem -t dll -f dlls.opt -F rev2-procedures #-c sc2.c ; Scheme (linux) script created by SLIB/batch ; [-p linux] ; used options from: dlls.opt ; ================ Write file with C defines (delete-file "scmflags.h") (call-with-output-file "scmflags.h" (lambda (fp) (for-each (lambda (string) (write-line string fp)) '("#define IMPLINIT \"Init5f3.scm\"" "#define INITS init_sc2();" "#define DLL")))) ; ================ Compile C source files (system "gcc -fpic -c -Wall sc2.c") (system "gcc -shared -o sc2.so sc2.o -lc") (delete-file "sc2.o") ; ================ Link C object files (delete-file "slibcat")
上のログは一例だけど、他には、こんな物がsoファイル化される。
debian:scm$ grep '#-c' LOGALL ./build -hsystem -t dll -f dlls.opt -F rev2-procedures #-c sc2.c ./build -hsystem -t dll -f dlls.opt -F byte #-c byte.c ./build -hsystem -t dll -f dlls.opt -F array-for-each #-c ramap.c ./build -hsystem -t dll -f dlls.opt -F differ #-c differ.c ./build -hsystem -t dll -f dlls.opt -F generalized-c-arguments #-c gsubr.c ./build -hsystem -t dll -f dlls.opt -F record #-c record.c ./build -hsystem -t dll -f dlls.opt -F i/o-extensions #-c ioext.c ./build -hsystem -t dll -f dlls.opt -F posix #-c posix.c ./build -hsystem -t dll -f dlls.opt -F socket #-c socket.c ./build -hsystem -t dll -f dlls.opt -F unix #-c unix.c ./build -hsystem -t dll -f dlls.opt -F regex #-c rgx.c
そして最終目的物であるscmの作成だ。
make[2]: Entering directory '/tmp/m/scm' ./build -hsystem -f scm5.opt -o scm -s /tmp/m/scm/ ; Scheme (linux) script created by SLIB/batch ; [-p linux] ; used options from: scm5.opt ; ================ Write file with C defines (delete-file "scmflags.h") (call-with-output-file "scmflags.h" (lambda (fp) (for-each (lambda (string) (write-line string fp)) '("#define IMPLINIT \"/tmp/m/scm/Init5f3.scm\"" "#define INITS init_sc2();" "#define COMPILED_INITS init_record();" "#define CCLO" "#define CAUTIOUS" "#define BIGNUMS" "#define ARRAYS" "#define FLOATS" "#define ENGNOT" "#define MACRO")))) ; ================ Compile C source files (system "gcc -c -DSUN_DL sc2.c record.c dynl.c continue.c scm.c scmmain.c findex ec.c script.c time.c repl.c scl.c eval.c sys.c subr.c debug.c unif.c rope.c") ; ================ Link C object files (system "gcc -rdynamic -o scm sc2.o record.o dynl.o continue.o scm.o scmmain.o f indexec.o script.o time.o repl.o scl.o eval.o sys.o subr.o debug.o unif.o rope.o -lm -ldl -lc")
無事にscmが作成出来ると、それを使って試験が行われる。
make checkmacro make[3]: Entering directory '/tmp/m/scm' ./scm -rmacro -fsyntest1.scm -fsyntest2.scm \ -fr4rstest.scm -e'(test-sc4)(test-cont)(test-delay)' -fsyntest1 \ -e '(or (null? errs) (quit 1))' < /dev/null (let ((x (quote outer))) (let-syntax ((m (syntax-rules () ((m) x)))) (let ((x (q uote inner))) (m)))) ==> outer (let-syntax ((when (syntax-rules () ((when ?test ?stmt1 ?stmt2 ...) (if ?test (b egin ?stmt1 ?stmt2 ...)))))) (let ((if #t)) (when if (set! if (quote now))) if)) ==> now : Passed all tests
これで、長い作成工程がやっと終了した。
何で、こんな複雑な事をしてるんだろう? 幾ら20世紀に基本が作成されたとしても、ちと複雑過ぎると思うぞ。 その答えの片鱗がREADMEに有りそう。
まず、多様なOSに対応したい。要するにポータビリティを確保して、色々な所で動いて欲しいぞ。それをMakefile一本に任せてしまっては、複雑になり過ぎて破綻するだろう。
目の前にschemeが有るなら、それを使っちゃえって作戦だな。ruby語で書かれたMakefileみたいなものか。それなら、どんなシステムに載っても、勝手知ったる我が家だからね。いかようにもなる。
共通部分ってか、軽いschemeそうscmlitが動けばしめたもの、だな。
これ、バッチに渡すオプションの例。
debian:scm$ cat dlls.opt --compiler-options=-Wall --linker-options=-Wall debian:scm$ cat udscm5.opt -F cautious bignums arrays inexact -F engineering-notation dynamic-linking -F macro -F dump
dllなんて語句なんで、一瞬Windowsかと思ったら、ダイナミックリンクの一般名なのね。unix系では、so ファイルね。それを作る時のオプションを列挙してる。
一方、もう一つの方は、scmに組み込む、機能を指定してる。dumpが付いているので、組み込んで作ったやつがダンプされる。こんな風に、手軽に機能を変えたscmを作成出来る。これをMakefileをしこしこ書き換えて、、なんてやってられないね。
尚、既に有るslibを使いたい場合、下記のようなファイルを作ってしまえば、(多分)おk。
vbox$ cat require.scm (define (library-vicinity) "/usr/local/share/slib/") (define (implementation-vicinity) "/usr/local/share/scm/") (load (in-vicinity (library-vicinity) "require"))
scm -v
> sakae@pen:/tmp/t$ scm SCM version 5f3, Copyright (C) 1990-2006 Free Software Foundation. SCM comes with ABSOLUTELY NO WARRANTY; for details type `(terms)'. This is free software, and you are welcome to redistribute it under certain conditions; type `(terms)' for details. ;loading /usr/local/lib/slib/require ;done loading /usr/local/lib/slib/require.scm ;loading /usr/local/lib/scm/Link ;done loading /usr/local/lib/scm/Link.scm ;loading /usr/local/lib/scm/Transcen ;done loading /usr/local/lib/scm/Transcen.scm
scmを普通に起動すると、上記のようになる。何をロードしてるか報告が有るんだな。slibをロードしてるのは分かる。じゃLinkは? 調べたら、
(cond ((defined? dyn:link) (define link:able-suffix (cond ((provided? 'shl) ".sl") ((provided? 'sun-dl) ".so") ((provided? 'mac-dl) ".shlb") ((provided? 'win32-dl) ".dll") (else ".o"))) :
いわゆるダイナミックリンク類を取り込むコードだな。これで、動的に機能拡張出来るとな。
Transcenの方は、数学関係の関数取り込みだな。こうして、分離してあると、特殊な数学関数を増設したくなった時、見通しが良くなると思うぞ。これも一つの見識だな。
(define $pi (* 4 (real-atan 1))) (define pi $pi)
例えば、こんなのが定義されてる。一般的なやつだ。もっと精度の良いのが欲しかったら、例に載ってるpi.scmあたりから引っ張ってきて、組み込んじゃえばおk。
scmの起動オプションはmanなりinfoに結弦、ああ、これは女房の趣味だな、譲るとして、一つだけ挙げておく。
sakae@pen:/tmp/t$ scm -v ;Evaluation took 10.ms (0.ms in gc) 29556 cells work, 2414 env, 55663.B other > (load "eps.scm") ;loading eps.scm ; loading /usr/local/lib/slib/grapheps ; linking /usr/local/lib/scm/ramap.so ; done linking /usr/local/lib/scm/ramap.so ; linking /usr/local/lib/scm/ioext.so ; done linking /usr/local/lib/scm/ioext.so ; loading /usr/local/lib/slib/color ; loading /usr/local/lib/slib/colorspc ; grew number of heaps to 2 segments ; ; grew heap to 75000 cells ; ; done loading /usr/local/lib/slib/colorspc.scm ; loading /usr/local/lib/slib/scanf ; done loading /usr/local/lib/slib/scanf.scm ; loading /usr/local/lib/slib/printf ; done loading /usr/local/lib/slib/printf.scm ; done loading /usr/local/lib/slib/color.scm ; loading /usr/local/lib/slib/glob ; done loading /usr/local/lib/slib/glob.scm ; done loading /usr/local/lib/slib/grapheps.scm ;done loading eps.scm ;Evaluation took 10.ms (0.ms in gc) 30259 cells work, 11863 env, 52679.B other #<unspecified>
こんな具合に、実行時間とgc具合が報告される。-vは、より細かく報告出来る-pn(n: 0 ~ 5)の単なるaliasだ。 -p5で起動すると、
;WARNING: load-string: redefining file-exists? ; done linking /usr/local/lib/scm/ioext.so ; loading /usr/local/lib/slib/color ; loading /usr/local/lib/slib/colorspc ;In file loaded from /usr/local/lib/slib/color.scm:21, ; loaded from /usr/local/lib/slib/grapheps.scm:23, ; loaded from eps.scm:1: ;WARNING: "/usr/local/lib/slib/colorspc.scm": redefining pi ;GC(cells) 0.ms cpu, 5695 cells, 6640 malloc, 8 syms, 3 ports collected ; grew number of heaps to 2 segments ; ; grew heap to 75000 cells; heap segments: ; 0x7fbdb03ef010 - 0x7fbdb04b2510; 50000 cells; 390.kiB ; 0x7fbdb04b3010 - 0x7fbdb0514a90; 25000 cells; 195.kiB ; ; done loading /usr/local/lib/slib/colorspc.scm
こんな細かい事も出て来る。チューニングのお供にどうぞ、だな。
他のオプションで嬉しいのは、-rだ。起動時にslibの機能を取り込めるぞ。下記ぐらいは、aliasに登録しておいても良いかな。
sakae@pen:/tmp/t$ scm -rpretty-print -r format -i > (pp *catalog*) : (ssax . xml-parse) (xml-parse source "/usr/local/lib/slib/xml-parse")
my scm on OpenBSD
OpenBSDのパッケージになってるscmは、ちと版が古い。ならば最新版を取ってきて自前でコンパイルしてみるか。出来たらその時、新しい機能を入れてしまいたい。ってな事で始めてみたんだけど、初っ端の軽い奴を作る所でエラーですよ。
パッケージの作り方を見たら、何個かパッチが用意してあった。その中にtime.c向けの物が有ったので、適用。
ob$ patch < ../patch-time_c Hmm... Looks like a unified diff to me... The text leading up to this was: -------------------------- |$OpenBSD: patch-time_c,v 1.4 2013/12/06 19:55:39 espie Exp $ |--- time.c.orig Thu Jan 31 04:33:06 2008 |+++ time.c Fri Dec 6 20:54:39 2013 -------------------------- Patching file time.c using Plan A... Hunk #1 succeeded at 20. Hunk #2 succeeded at 98 with fuzz 1 (offset -11 lines). Hunk #3 succeeded at 183 (offset 10 lines). Hunk #4 succeeded at 170 (offset -11 lines). Hunk #5 failed at 303. Hunk #6 failed at 363. 2 out of 6 hunks failed--saving rejects to time.c.rej done
最後の2つが失敗してる。それに他のパッチも未適用だけど、取り合えず先に進めてみるか。
require.scm は、手製の物を使った。implementation-vicinityをきちんと設定しておかないと、slibcatが書き込み禁止の所に行ってしまう。
上では、require.scmを強制的にcpしちゃったけど、普通は、
export SCHEME_LIBRARY_PATH=/usr/local/share/slib/
なんてやってから、コンパイルすればおk。
それから、重大な問題としてsoファイルが作成されていない。ログを見ると
(system "cc -fPIC -c -Wall ramap.c") (system "gcc -shared -fPIC ramap.o") (system "mv a.out ramap.o")
sharedを作る時に、-o ramap.soが抜けている。build.scmのopenbsd部分を改修すれば、何とかなるだろう。けど、パッケージのメンテナの方もそこまで気を回しておられないようなので、オイラーもすごすごと引き下がる事にした。もっと面白い事が有るからね。
hobbit
scmには、scheme語をC語に変換するトランスレーター hobbitって言うのが付属してる。こいつを使ってC語に変換し、gccでそれをコンパイル。出来上がったsoファイルを所定の場所に置くと、requireで呼び出して使えるようになるらしい。試してみる。
(define (fib n) (cond ((= n 1) 1) ((= n 2) 1) (else (+ (fib (- n 1)) (fib (- n 2))))))
何の変哲もないfib.scmだ。これをコンパイルする。
> (require 'compile) ;loading /home/sakae/MINE/lib/scm/compile ;done loading /home/sakae/MINE/lib/scm/compile.scm #<unspecified> > (hobbit "fib.scm") ;loading /home/sakae/MINE/lib/scm/hobbit ;done loading /home/sakae/MINE/lib/scm/hobbit.scm ;loading /home/sakae/MINE/lib/slib/defmacex ;done loading /home/sakae/MINE/lib/slib/defmacex.scm ;loading /home/sakae/MINE/lib/slib/ppfile ; loading /home/sakae/MINE/lib/slib/pp ; loading /home/sakae/MINE/lib/slib/genwrite ; done loading /home/sakae/MINE/lib/slib/genwrite.scm ; done loading /home/sakae/MINE/lib/slib/pp.scm ;done loading /home/sakae/MINE/lib/slib/ppfile.scm Starting to read fib.scm Bounded integer (fast) arithmetic assumed. ** Pass 1 completed ** ** Pass 2 completed ** ** Pass 3 completed ** ** Pass 4 completed ** ** Pass 5 completed ** ** Pass 6 completed ** C source file fib.c is built. C header file fib.h is built. #<unspecified>
これで、ヘッダーファイルと本体が作成された。後はコンパイルしてsoファイルを作るだけ、なんだけど、一つ注意。
debian:scm$ cat fib.h #include "scmhob.h" SCM fib(); SCM top_actions_1_fib(); SCM top_actions_fib(); SCM init_1_fib(); SCM init_fib();
ヘッダーファイルの中で、scm由来のヘッダーファイルを取り込んでいる。よって、ちょっと鬱陶しいけど、scmのソースの中で、開発するのが良いだろう。
debian:scm$ cc -shared -fPIC -o fib.so fib.c
soファイルは、普通に作ればよい。そして、出来上がったfib.soは、scm/lib/の中に移動させる。 それから、slibcatの最後の方に一行追加。
: (array-for-each compiled "/home/sakae/MINE/lib/scm/ramap.so") (byte-number compiled "/home/sakae/MINE/lib/scm/bytenumb.so") (fib compiled "/home/sakae/MINE/lib/scm/fib.so") )
これで、走らせてみる。
> (require 'fib) ;linking /home/sakae/MINE/lib/scm/fib.so ;done linking /home/sakae/MINE/lib/scm/fib.so ;Evaluation took 0.ms (0.ms in gc) 403 cells work, 168 env, 186.B other #<unspecified> > (fib 35) ;Evaluation took 200.ms (0.ms in gc) 8 cells work, 0 env, 31.B other 9227465
速いのか遅いのか、分からないので、オリジナルのscm版でも実行。
> (load "fib.scm") ;loading fib.scm ;done loading fib.scm ;Evaluation took 0.ms (0.ms in gc) 107 cells work, 22 env, 129.B other #<unspecified> > (fib 35) ;Evaluation took 6590.ms (90.ms in gc) 389691 cells work, 36909858 env, 691.B other 9227465
爆速になったな。
後で気が付いたんだけど、scmhob.hはインストールしたscmの中に入っていた。だから、そいつを持って来るだけで良い。
amd64なdebianで (fib 40) を実行したら
gambit 3.58s chez 0.80s scm 0.64s ;; cc -shared -fPIC -o fib.so fib.c scm(-O3) 0.48s ;; cc -O3 -shared -fPIC -o fib.so fib.c C (-O0) 0.60s C (-O3) 0.20s gauche 7.78s ;; 参考出品
今まで、コンパイル系では、chez scheme が一番速いと思っていたけど、hobbitさん、なかなかやるな。
強引に
OpenBSDでfib.soを作って走らせてみると
ob$ /home/sakae/MINE/bin/scm -r fib scm:/home/sakae/MINE/lib/scm/fib.so: undefined symbol 'no_symhash_gc' scm:/home/sakae/MINE/lib/scm/fib.so: undefined symbol 'make_subr' ;ERROR: Couldn't link files ("/home/sakae/MINE/lib/scm/fib.so") ;STACK TRACE 1; (#@define ((cep (#@current-error-port))) (#@if (#@acro-call d ... 2; ((#@thunk) (set! complete #t)) 3; ((#@case #@option #(#<unspecified> #f #\? #\: #\n #\u #\m #\s ... 4; ((#@cond ((#@not #@*argv*) (#@set! #@*argv* (#@program-argumen ... * Breakpoint established: (continue <val>) to return. "BREAKPOINT:" "Couldn't link files " ("/home/sakae/MINE/lib/scm/fib.so") 1 ; program args: ("/home/sakae/MINE/bin/scm" "-r" "fib")
本体側との親和性が取れなくて、エラーになってしまいました。これはもうあきらめろ。ちなみに、作成されてたfib.cは、こんなコードだった。
#include "fib.h" SCM fib(n) SCM n; { if (n==MAKINUM(1)) return MAKINUM(1); else if (n==MAKINUM(2)) return MAKINUM(1); else return MAKINUM(INUM(fib(MAKINUM(INUM(n)-1)))+INUM(fib(MAKINUM(INUM(n)-2)))); } SCM top_actions_1_fib() { return make_subr((char *)"fib",tc7_subr_1,fib); } SCM top_actions_fib() { return top_actions_1_fib(); } SCM init_1_fib() { top_actions_fib(); } SCM init_fib() { no_symhash_gc=1; init_1_fib(); }