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();
}

etc