ecl, gambit
plot by openlisp
前回OpenLispを見つけて、その中にgnuplotと仲良くなるコードが寄贈されてるのを見た。gnuplotに向けて、plotコマンドを丸投げしてた。それじゃ、あんまりだろう。折角lispを使っているんだから、協調動作をさせたい。
計算はlisp側で行い、その結果をgnuplotに転送してグラフ描いてよね。
(defun plot () (with-open-output-pipe (gp "gnuplot -persistent") (format gp "plot '-' with line~%") (do ((i 1 (1+ i))) ((> i 300)) (format gp "~A ~A~%" i (sqrt i))) (format gp "end~%")))
素数夜曲では、gaucheを使って計算結果をファイルに落とす。それをgnuplotのスクリプトでグラフにしてる。
今回のやつは、gnuplotへ標準入力から計算結果を送り込んで、グラフにしてる。do式の中で、1から300までの数値とその平方根を作り出している。この部分を切り出せば汎用的になる。
plot by gauche
どうもLispは苦手なので、同じ事をschemeでやってみた。
(use gauche.process) (define (calc n) (dotimes (i n #f) (format #t "~a ~a~%" i (sqrt i)))) (define-macro (plot form) `(with-output-to-process "gnuplot -p" (lambda () (format #t "plot '-' w l~%") ,form (format #t "end~%")))) (plot (calc 300))
plot手続きをLisp風なマクロにした。とっさにscheme風なマクロが書けないのよねぇ。 ちゃんとした、読書用メガネが有るんで、細かい字も平気。昔の本を引っ張り出してみるかな。
まて、with-output-to-processなんて、いかにもマクロの匂いがするな。shiroさんは、どういう実装をしてるか盗み見しよう。
(define (call-with-output-process command proc :key (output *nulldev*) ((:error err) #f) (host #f) (on-abnormal-exit :error) :allow-other-keys rest) (let* ((p (%apply-run-process command :pipe output err host rest)) (o (wrap-output-process-port p rest))) (unwind-protect (proc o) (begin (close-output-port o) (process-wait p) (handle-abnormal-exit on-abnormal-exit p))))) (define (with-output-to-process command thunk . opts) (apply call-with-output-process command (cut with-output-to-port <> thunk) opts))
残念ながら?普通の手続きになってました。味噌はapplyの使い方に有りだな。むやみやたらにマクロは使いなと。
ecl
前回eclなんてのが出て来た。これ、OpenBSDに入れているmaximaを下支えしてるlispだ。
ob$ maxima ;;; Loading #P"/usr/local/lib/ecl/sb-bsd-sockets.fas" ;;; Loading #P"/usr/local/lib/ecl/sockets.fas" Maxima 5.44.0 http://maxima.sourceforge.net using Lisp ECL 20.4.24 Distributed under the GNU Public License. See the file COPYING. Dedicated to the memory of William Schelter. The function bug_report() provides bug reporting information. (%i1) diff(x^3+4*x^2-6*x, x); 2 (%o1) 3 x + 8 x - 6
こんな、シンボリックな数式処理プログラム。pythonにも似たようなやつがあるけど、各段にこちらの方が使い易い。こういうシステムはlispのフットプリントが小さい方がありがたいって事で、ecl 組み込み用の顧問リスプが使われている。
https://common-lisp.net/project/ecl/index.html
Qt5を使ってアンドドロイド版のアプリが提供されてる。面白いな。
本格的に使おうと思ったらQuickLispとか言うパッケージマネージャを入れて、色々やるんだろうね。オイラーにそんなのはカバーできないよ。
gambit
CLよりschemeの方が、やはり馴染みが有る。schemeでコンパイル出来るやつって言ったら、chickenとかgambitが頭に浮かんでくる。今回はgambitを選んでみるか。ipadにも入っているしね。eclのあんどろどいどには負けられませんからね。
https://github.com/gambit/gambit
へ行って、落としてきてインストール。
そして Manual(html) を横目で睨みながら。。基本的にR5RSな仕様。マニュアルには、拡張機能が列挙されてる。replから適当に入力してTABを叩けば、使えるやつが次々に出てくるので、慌てる事は無い。
vbox$ gsc Gambit v4.9.3 > (load "fib.scm") "/tmp/fib.scm" > (time (fib 32)) (time (fib 32)) 8.106928 secs real time 8.030000 secs cpu time (7.870000 user, 0.160000 system) 226 collections accounting for 0.258144 secs real time (0.160000 user, 0.090000 system) 139411744 bytes allocated 25 minor faults no major faults 2178309
gsiと言うインタープリターとgscと言うコンパイラーが付いているのが有る。
> (compile-file "fib.scm") "/tmp/fib.o1" > (load "fib.o1") "/tmp/fib.o1" > (time (fib 32)) (time (fib 32)) 0.152433 secs real time 0.150000 secs cpu time (0.150000 user, 0.000000 system) no collections no bytes allocated 1 minor fault no major faults 2178309
爆速になった(OpenBSD 32Bit)
vbox$ gsc -exe fib.scm vbox$ ./fib (time (fib 32)) 0.143867 secs real time 0.140000 secs cpu time (0.140000 user, 0.000000 system) no collections no bytes allocated 2 minor faults 1 major fault
簡単にシングルバイナリーが作成出来る。但しとても太っている(5.6M)
vbox$ ldd fib fib: Start End Type Open Ref GrpRef Name 144f1000 34620000 exe 1 0 0 fib 08b96000 28b98000 rlib 0 1 0 /usr/lib/libutil.so.15.0 09f37000 29f39000 rlib 0 1 0 /usr/lib/libm.so.10.1 0e693000 2e698000 rlib 0 1 0 /usr/lib/libssl.so.48.1 0c771000 2c788000 rlib 0 2 0 /usr/lib/libcrypto.so.46.1 08f15000 28f26000 rlib 0 1 0 /usr/lib/libc.so.96.0 0d9e9000 0d9e9000 ld.so 0 1 0 /usr/libexec/ld.so
pkgから入れたやつは、sslまで組み込まれているよ。
plot by gambit
どうもgambitはschemeでアプリを作成する土台っぽい。とは言え、何かなら何までschemeでやる必要は全く無いはず。既にあるアプリと協調動作出来れば良いわけだ。上で挙げた、gnuplotと仲良くなる事が出来るか、確認してみる。
gambitには、便利なformatが無いので、displayで代用。
(define (calc n) (for-each (lambda (i) (display i ) (display " ") (display (log i)) (newline)) (iota n 1))) (define-macro (plot form) `(with-output-to-process (list path: "gnuplot" arguments: '("-p")) (lambda () (display "plot '-' w l\n") ,form (display "end\n")))) (plot (calc 300))
with-output-to-processへの引数の与え方が若干面倒な点を除いてgoshと同様だな。
format by gambit (slib)
確かformatってのが、slibに入っていたような。で、どうgambit用にsetupしたらよいの? ggしても、出て来るのがgauche用ばかり。もう手探りする鹿。
ob$ pwd /usr/local/share/slib ob$ doas gsi > (load "gambit.init") *** ERROR IN "gambit.init"@456.1 -- No such file or directory (load "/usr/local/lib/slib/require") ;;;; change lib to share > (load "gambit.init") "/usr/local/share/slib/gambit.init" > (require 'format) *** ERROR IN "mklibcat"@22.3 -- No such file or directory (call-with-output-file "/usr/local/share/gambc/slibcat" '#<procedure #2>) ;;;; so mkdir /usr/local/share/gambc
認識させて、カタログを作成させるのが第一歩。slibとかの配置が合わないので、出て来るエラーを手掛かりに、調整
ob$ cat /usr/local/share/gambc/slibcat ;"slibcat" SLIB catalog for gambitv4.9.3. -*-scheme-*- ; ; DO NOT EDIT THIS FILE -- it is automagically generated ( (schelog . "/usr/local/share/slib/schelog/schelog") (portable-scheme-debugger . "/usr/local/share/slib/psd/psd-slib") (jfilter . "/usr/local/share/slib/jfilter/jfilter") (fluid-let defmacro "/usr/local/share/slib/fluidlet") (null source "/usr/local/share/slib/null") : (new-catalog source "/usr/local/share/slib/mklibcat") (*slib-version* . "3b4") )
カタログ出来た。
ob$ gsi Gambit v4.9.3 > (load "/usr/local/share/slib/gambit.init") "/usr/local/share/slib/gambit.init" > (require 'format) "/usr/local/share/slib/format.scm" > (format #t "hello~%") hello #t
指導書では、gsi起動時にgambit.initを読み込むように指示されてたけど、そうすると意味不なエラーになったので、自力でロードした。取り合えず動いてるっぽい。
small format (printf)
上のformatは本式過ぎる。CommonLispのインプリメンターは、途轍もなく苦労するな。 それを嫌って、超簡単なformat似なものが公開されてたので、取り込んでみた。
(define dofmt (lambda (p cntl args) (let ((nmax (- (string-length cntl) 1))) (let loop ((n 0) (a args)) (if (<= n nmax) (let ((c (string-ref cntl n))) (if (and (char=? c #\~) (< n nmax)) (case (string-ref cntl (+ n 1)) ((#\a #\A) (display (car a) p) (loop (+ n 2) (cdr a))) ((#\s #\S) (write (car a) p) (loop (+ n 2) (cdr a))) ((#\%) (newline p) (loop (+ n 2) a)) ((#\~) (display #\~ p) (loop (+ n 2) a)) (else (display c p) (loop (+ n 1) a))) (begin (display c p) (loop (+ n 1) a))))))))) (define printf (lambda (control . args) (dofmt (current-output-port) control args)))
使い方の例。大体これが出来れば移植時に困る事は無いだろう。
> (load "printf.scm") "/tmp/printf.scm" > (define i 123) > (printf "~a ~a~%" i (log i)) 123 4.812184355372417
簡易版もちゃんと動いた。それじゃ、こんなのはどうよ?
vbox$ cat format.scm (load "/usr/local/share/slib/gambit.init") (require 'format) (define hoge 123) (format #t "hello ~A~%" (log hoge)) vbox$ gsc -exe format.scm vbox$ ./format hello 4.812184355372417
これも、ちゃんとアプリになって、動いた。少なくともgolangよりは扱い易いかな。
まて、口の悪い人に言わせると、今のgolang人気は、メモリー管理をちゃんと出来ない軟弱人向けらしいです。lisp/schemeは、元祖のgcで、メモリー管理を最初から放棄してるな。ゆえに、オイラーも軟弱者です。