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で、メモリー管理を最初から放棄してるな。ゆえに、オイラーも軟弱者です。


This year's Index

Home