ecl

『はんだ付けの職人技』(技術評論社)なんて本が有ったので読んでみた。今時この種の本が 技評から出て来るのはラズ・パイとかArduinoのからみで、こてを振り回す人を増やそうと言う 魂胆なのかしら。更に言えば、電子工作本が沢山売れますようにと、市場拡大作戦の一環 なんだな。こういうのは、老舗のCQ出版だけかと思っていたぞ。

ハンダ付け職人のはんだ付け講座 が、著者の会社。本の後付けによると、NECで修行を積んで、会社を興されたようだ。細かい Tipsを読んでいると、どうも衛星関係で基板をやっておられたように思えるんだけど、どうだろう?

はんだ付け職人の仕事風景 なんてのも公開されてて、日本の匠の感じがするな。何たってはんだ付けは、基本中の基本 ですもの。

QFPな石のはんだ付けなんてのも出てきて、世の中様変わりしてるのをひしひしと感じますよ。 もうおいらの老眼では太刀打ち出来ないな。

おいらがひたすらはんだ付けをやってた時代は、ST菅やGT菅やMT菅という球(真空管)の時代 (よき青春時代)、社会に出て石(半導体)になったな。トランジスタやらRTL(抵抗とトランジスタを 集積したIC)、そのうちにTTLになって、最後はECL だったよ。DIP型をしたICだったから、多少手が震えぎみでも、何とかなった。ICがQFPと言う 足の間隔がハーフピッチになる頃、はんだ付けから足を洗えたのは幸いと申すべきですな。

ANSI Common Lisp

前回は突然 Common Lispに目覚めてしまって、ポール・グレアムの書いた本を引っ張り 出してしまった。けど、膨大な関数の説明が載っている訳でも無し、ちゃんとした資料が 欲しいな、それには、HyperSpecだろう。えと、どう設定するんだっけな?

HyperSpecの設定を 参考にしましたよ。原稿は、 Common Lisp Documentation あたりに公開されてる。中々太っ腹だなあ。

Welcome to CLISPにもLispのマニュアルと実装の説明が 載ってるぞ。そして、 ポール本に掲載されてるコードも、取れるぞ。 なんて言っても、この本、あのブランド版Lisp(ACL)を買うと、梱包の中に忍ばせて あるぞうだから、定評あるんだろうな。

で、これを機会に、Lisp本の虫干しと言うか、再参照と言うかをやってみたよ。 そしたら、どんなLispがお勧めってのが、各本に載ってて、著者の見解が分かれていて 面白かったよ。

LoLの方が言及してたやつにECLってのがあった。フットプリントが非常に軽い。CLISPに 比べて1/3ぐらいとの事。これはもう手を出してみる鹿。

スピードはTTLに比べてECLは速かったんで、どんなもんかね?

ECL

ECLが総本山。コンパイラーも付いてるよ。独立した アプリも作れるよ。ポータブルだよって看板が出てた。

ウブに入れてみたぞ。パッケージで入るけど、ソースから入れるのがデフォですだ。

sakae@uB:~$ ecl
ECL (Embeddable Common-Lisp) 13.5.1 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.
Top level in: #<process TOP-LEVEL>.
> *FEATURES*

(:LINUX :FORMATTER :ECL-WEAK-HASH :LITTLE-ENDIAN :ECL-READ-WRITE-LOCK
 :LONG-LONG :UINT64-T :UINT32-T :UINT16-T :RELATIVE-PACKAGE-NAMES :LONG-FLOAT
 :UNICODE :DFFI :CLOS-STREAMS :CMU-FORMAT :UNIX :ECL-PDE :DLOPEN :CLOS :THREADS
 :BOEHM-GC :ANSI-CL :COMMON-LISP :IEEE-FLOATING-POINT :PREFIXED-API :FFI
 :PENTIUM3 :COMMON :ECL)

おいらのマシンでは、8分程で出来上がった。起動時に出て来るバナーには歴史が 息づいていますなあ。何やら、おいらの知ってる人の名前があるよ。manを引いたら

AUTHORS
       The  original  version  was developed by Giuseppe Attardi starting from
       the Kyoto Common  Lisp  implementation  by  Taiichi  Yuasa  and  Masami
       Hagiya.   The current maintainer of ECL is Juan Jose Garcia Ripoll, who
       can be reached at the ECL mailing list.

京都に源を発するんですなあ。

で、早速、

1.6. Compiler examples とか eclについて を参考に、スタンドアローンな物を作ってみる。

> (compile-file "hello.lisp" :SYSTEM-P T :C-FILE T :DATA-FILE T :H-FILE T)
;;; Loading #P"/usr/local/lib/ecl-13.5.1/cmp.fas"
;;;
;;; Compiling hello.lisp.
;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=0
;;;
;;; End of Pass 1.
;;; Finished compiling hello.lisp.
;;;
#P"/home/sakae/ecl-13.5.1/examples/z/hello.o"
NIL
NIL
> (c:build-program "myecl" :lisp-files '("hello.o"))

#P"myecl"

コンパイルとビルドを一遍にやってしまわないと、変なエラーを喰らうので注意。また、 コンパイル時に上記のようにキーワード変数を与えると、中間ファイル(C語用のソース)が得られる。

出来上がったやつは、こんなの

sakae@uB:~/ecl-13.5.1/examples/z$ ldd ./myecl
        linux-gate.so.1 =>  (0xb7701000)
        libecl.so.13.5 => /usr/local/lib/libecl.so.13.5 (0xb747a000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb72c1000)
        libgmp.so.10 => /usr/lib/i386-linux-gnu/libgmp.so.10 (0xb723b000)
        libgc.so.1 => /usr/lib/i386-linux-gnu/libgc.so.1 (0xb71eb000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb71cf000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb71ca000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7184000)
        /lib/ld-linux.so.2 (0xb7702000)

当然だけどeclの環境を内蔵してるんで、バイナリーだけを配布しても動かない。それから、 libgmpって事で、スピードを要求される計算はそちらの方に丸投げ。ガベコレも外部に 丸投げして、責任転嫁。これが正しい、組み込み道です。

sbclみたいに、Lisp語から一気にバイナリーを生成しちゃうんじゃ、移植性が犠牲になるし 、世の中の知恵を利用できにくくなる。その点、ECLにみたいに一端C語にすれば、後はgcc なりclangの力を借りるのは容易い。これ、KCLを開発する時にまっさきに決めた方針だった そうです。

How to build?

所で、マニュアルの例に有った、gccを起動する表示が無かったのは何故? eclを作る時のログを 取っておいたので、見てみると、

if [ -f CROSS-COMPILER ]; then \
                ./CROSS-COMPILER compile; \
        else \
                ECLDIR=`pwd`/ ./ecl_min compile; \
        fi
;*** Lisp core booted ****
ECL (Embeddable Common Lisp)

;;;
;;; Welcome to bare.lsp. Let's bring this instance up!
;;;
;;; About to load lsp/load.lsp
;;;
;;; Loading src:lsp;export.lsp
;;; Loading src:lsp;defmacro.lsp
     :

こんな具合に小さいlispを作り、その基で、lispコードを動かし

;;; System features: (32-BIT GCC GCC-COMPILER ELF I386)
;;;
;;; Now we are in shape to do something useful.
;;; End of bare.lsp
(compile-file "src:lsp;export.lsp" :output-file #P"BUILD:LSP;EXPORT.O.NEWEST" :SYSTEM-P T :C-FILE T :DATA-FILE T :H-FILE T)
;;;
;;; Compiling SRC:LSP;EXPORT.LSP.
;;; OPTIMIZE levels: Safety=2, Space=1, Speed=1, Debug=1
;;;
;;; Compiling (DEFUN EVAL-FEATURE ...).
;;; Compiling (DEFUN DO-READ-FEATURE ...).
;;; Compiling (DEFUN SHARP-+-READER ...).
;;; Compiling (DEFUN SHARP---READER ...).
;;; End of Pass 1.
;;; Emitting code for DOLIST.
;;; Emitting code for DOTIMES.
;;; Emitting code for DO/DO*-EXPAND.
;;; Emitting code for EVAL-FEATURE.
;;; Emitting code for DO-READ-FEATURE.
;;; Emitting code for SHARP-+-READER.
;;; Emitting code for SHARP---READER.
;;; Note:
;;;   Invoking external command:
;;;   gcc -I. -I/home/sakae/ecl-13.5.1/build/ -DECL_API -I/home/sakae/ecl-13.5.1/build/c -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -g -O2 -fPIC -D_THREAD_SAFE -Dlinux -I/home/sakae/ecl-13.5.1/src/c -c lsp/export.c -o lsp/export.o
;;; Finished compiling SRC:LSP;EXPORT.LSP.

こんな具合に進んで行く。もう、一般ユーザーには裏側は見せないよって事なんだろうか?

eclを使う

いつもの定番です。

> (defun fact (n) (if (zerop n) 1 (* n (fact (- n 1)))))

FACT
> (disassemble 'fact)

#(FACT N ZEROP - * #<bytecompiled-function FACT> SI:FSET)
Name:           FACT
   0    POP     REQ
   1    BIND    N
   3    NOMORE
   4    VAR     0
   6    CALLG1  ZEROP
   8    JNIL    15
  10    QUOTE   1
  12    SET     VALUES(0),REG0
  13    JMP     32
  15    PUSHV   0
  17    PUSHV   0
  19    PUSH    1
  21    CALLG   2,-
  24    PUSH    VALUES(0)
  25    CALLG   1,FACT
  28    PUSH    VALUES(0)
  29    CALLG   2,*
  32    EXIT
NIL

こんなのを実行するのね。次は

(defun tak (x y z)
  (declare (fixnum x y z))
  (if (not (< y x))
      z
      (tak (tak (1- x) y z)
           (tak (1- y) z x)
           (tak (1- z) x y))))

TAK
> (time (tak 18 9 0))

real time : 8.550 secs
run time  : 8.336 secs
gc count  : 60 times
consed    : 92051155312 bytes
9
> ^[[A

あれ、矢印で前に戻ろうとしたら、無視された。こういう時は、ReadLine wrapでeclを 覆ってしまいましょう。一行編集とか履歴とかが使えるようになるぞ。

alias ecl='rlwrap ecl'

ああ、上記は、.bashrc にでも登録しておくと便利です。

help

replと言うかTop-Levelから使える便利はマニュアルとして :doc とか :apropos とかが 用意されている。一体どんな項目がどれぐらい登録されているんだろう? HyperSpecに 対抗しようって、いい根性してると思うぞってんで、調べてみた。

sakae@uB:~/ecl-13.5.1/src/doc$ grep '^(doc' help.lsp | cut -f 1 -d ' ' | sort | uniq -c
    571 (docfun
     30 (doctype
     96 (docvar

関数が571項、typeが30項、変数が96項目。

(doctype array "
(doctype bignum "
(doctype character "
  :
(doctype string-char "
(doctype symbol "
(doctype t "

そのうちのタイプを調べてみた。結構有ってびっくりですよ。項目の例も引いてみるか。 今後お世話になりそうな。traceを選んでみた。

> :doc trace

-----------------------------------------------------------------------------
TRACE                                                                 [Macro]
Syntax: (trace ({function-name | ({function-name}+)} {keyword [form]}*)
Begins tracing the specified functions.  With no FUNCTION-NAMEs, returns a
list of functions currently being traced. The printed information consists of
the name of function followed at entry by its arguments and on exit by its
return values.
The keywords allow to control when and how tracing is performed.
The possible keywords are:

 :BREAK         a breakpoint is entered after printing the entry trace
                information, but before applying the traced function to its
                arguments, if form evaluates to non-nil
 :BREAK-AFTER   like :BREAK but the breakpoint is entered after the function
                has been executed and the exit trace information has been
                printed and before control returns
 :COND-BEFORE   information is printed upon entry if form evaluates to non-nil
 :COND-AFTER    information is printed upon exit if form evaluates to non-nil
 :COND          specifies a single condition for both entry and exit
 :PRINT         prints the values of the forms in the list upon entry.
                They are preceeded by a backslash (\)
 :PRINT-AFTER   prints the values of the forms in the list upon exit from the
                function. They are preceeded by a backslash (\)
 :STEP          turns on the stepping facility

Forms can refer to the list of arguments of the function through the variable
SI::ARGS.
-----------------------------------------------------------------------------

HyperSpecではどうかと思って、C-c C-d h trace してみたら

Macro TRACE, UNTRACE

Syntax:
trace function-name* => trace-result

Arguments and Values:
function-name---a function name.
trace-result---implementation-dependent, unless no function-names are
supplied, in which case trace-result is a list of function names.
  :

ってな具合に説明されてた。eclは拡張しているんだなあ。 それはさておき、普通のtraceをやってみる。

> (trace fact)

(FACT)
> (fact 4)

1> (FACT 4)
| 2> (FACT 3)
|   3> (FACT 2)
|   | 4> (FACT 1)
|   |   5> (FACT 0)
|   |   <5 (FACT 1)
|   | <4 (FACT 1)
|   <3 (FACT 2)
| <2 (FACT 6)
<1 (FACT 24)
24
> :untr

(FACT)

普通に動いているな。それじゃ、traceしたらbreakしてdebuggerに落ちてみるか。

> (trace fact :break t)
;;; Warning: The function :BREAK is not defined.
;;; Warning: The function T is not defined.

(FACT :BREAK T)

ありゃ、変な警告。そして :breakなんてのもtraceの対象になっちゃったぞ。これはきっと、 使い方を間違えているに違いない。こんなんでどうだ。

> (trace (fact :break t))

Condition of type: SIMPLE-ERROR
Not a valid argument to TRACE: (FACT :BREAK ...)

Available restarts:

1. (RESTART-TOPLEVEL) Go back to Top-Level REPL.

Broken at SI:BYTECODES. [Evaluation of: (TRACE (FACT :BREAK ...))] In: #<process TOP-LEVEL>.

>> :q

> (trace (fact) :break t)

Condition of type: SIMPLE-ERROR
Not a valid argument to TRACE: (FACT)
 :

traceする前に、debuggerに落ちてくれるとは、手回しよさ杉! もう諦めて、ソース嫁だな。 なんてったって、そこにソースが有るんだから。場所は、#P"src/lsp/trace.lsp" だな。 この、#P"..." って、言い回し、lisp語で、ファイル・パスの意味ね。こいつに出会ったら、 Lispのリーダーは、WinかUnixかMacか判定して、ファイルパスの表現をその環境に合わせて 調整してくれるんか。

(in-package "SYSTEM")

(defmacro trace (&rest r)
  `(trace* ',r))

(defun trace* (r)
  (if (null r)
      (mapcar #'first *trace-list*)
      (mapc #'trace-one r)))

(defun trace-one (spec)
  (let* (break exitbreak (entrycond t) (exitcond t) entry exit
               step (barfp t) fname oldf)
    (cond ((si::valid-function-name-p spec)
           (setq fname spec))
          ((si::proper-list-p spec)
           (error "Not a valid argument to TRACE: ~S" spec))
          ((si::valid-function-name-p (first spec))
           (setq fname (first spec))
           (do ((specs (cdr spec) (cdr specs)))
               ((null specs))
             (case (car specs)
               (:break (setq barfp specs specs (cdr specs) break (car specs)))
               (:break-after (setq barfp specs specs (cdr specs) exitbreak (car specs)))
               (:step (setq step t))
                  :

声に出して読みたいLisp語って事で、国語の時間ですよ。

traceは、SYSTEM族に属してる大事な関数ですって宣言がありました。ユーザーとのI/Fは マクロが受け持ってて、引数を評価しないんだな。すぐに下請けを呼び出してる。 trace*では、引数の有る・無しで場合分け。引数有りの場合は、一つづつ、登録してくんだな。

trace-oneの中では、やってきたやつが、有効な関数名か確認、もしそうなら名前を登録。 次は、与えられた引数が、正統なリストかproper-list-pで、確認。そうで無かったら、 errorで、debuggerに落ちておしまい。上で出てたのは、ここで引っかかったんか。

ここまでの検査を通ったら、引数のfirst(== car)が、正統な関数名か確認し、それを登録、 引数のcdrを取りながら、キーワード変数に適した処理を続行 とな。

そうすると、どうもproper-list-pの判定が壁を作っていて、キーワードの処理まで下りて こないんだな。ええい、壁を取り払ってしまえ。2行コメントアウトしてあげた。

そして、trace.lspをロードしようとしたら、哀れ、大事な物にはロックがかけられていて 再読み込みはしてくれなかった。しょうがないので、makeし直してから再インストールしたよ。

> (defun fact (n) (if (zerop n) 1 (* n (fact (- n 1)))))

FACT
> (trace (fact :break t))

((FACT :BREAK T))
1> (FACT 3)

Condition of type: SIMPLE-CONDITION
tracing FACT
Available restarts:

1. (CONTINUE) Return from BREAK.
2. (RESTART-TOPLEVEL) Go back to Top-Level REPL.

Broken at FACT. In: #<process TOP-LEVEL>.
>> :c

| 2> (FACT 2)

Condition of type: SIMPLE-CONDITION
tracing FACT
Available restarts:

1. (CONTINUE) Return from BREAK.
2. (RESTART-TOPLEVEL) Go back to Top-Level REPL.

Broken at FACT. In: #<process TOP-LEVEL>.
>> :b

Backtrace:
  > FACT
  > fact
  > fact
  > si:bytecodes [Evaluation of: (fact 3)]
  > si:bytecodes [Evaluation of: (si:top-level t)]
 
>> :i args

Inspection mode: Type ? followed by #\Newline for help.

(0) - cons
 nth 0:
    0  >> q

(0)
>> :c

|   | <4 (FACT 1)
|   <3 (FACT 1)
| <2 (FACT 2)
<1 (FACT 6)
6

やっと動いたわい。もう一つおまけで、条件が成立した時traceと言うかbreakする例。

(defun xxx (n) (1+ n))

XXX
> (trace (xxx :break (eql (first si::args) 2)))

((XXX :BREAK (EQL (FIRST SI::ARGS) 2)))
> :tr

(XXX)
> (xxx 5)

1> (XXX 5)
<1 (XXX 6)
6
> (xxx 2)

1> (XXX 2)

Condition of type: SIMPLE-CONDITION
tracing XXX
Available restarts:

1. (CONTINUE) Return from BREAK.
2. (RESTART-TOPLEVEL) Go back to Top-Level REPL.

Broken at XXX. In: #<process TOP-LEVEL>.
>> :b

Backtrace:
  > XXX
  > si:bytecodes [Evaluation of: (xxx 2)]
  > si:bytecodes [Evaluation of: (si:top-level t)]

うん、なかなかいいぞ。それにstepperなんてのも有るしなあ。

所で、 si::proper-list-p の正体って何よ? ソース内をくまなく探してみたら cmp/proclamations.lsp に見つかった。宣言書って大変なものだな。

(proclamation si::proper-list-p (t) gen-bool :predicate)

これが意味する所は? 大体、想像つくけど念の為に、ファイルの冒頭を見たら

;;; PROCLAMATIONS of ECL and ANSI Common Lisp functions
;;;
;;; The function proclamations are created with PROCLAIM-FUNCTION, as in
;;;
;;;     (PROCLAMATION function-name ([arg-type]*) return-type
;;;             &rest {:no-sp-change|:pure|:reader|:no-side-effects})
;;;
;;; with the following interpretation: ARG-TYPE and RETURN-TYPE denote the most
;;; general types for the input and output values of this function. If the
;;; compiler detects that some of the values passed to this function does not
;;; match these types, it will generate an error. In addition to this, ECL
;;; contemplates different function properties:

とか、書いてある。要するに、入出力の型や、影響範囲とかをチェックしてくれる、 お目付け役なんだな。正統な使い方をすれば最適化もできますぜってコンパイラーの 資料にもなるんか。これを一望すればecl拡張も一発で判明する便利な閻魔帳でした。

(proclamation cons (t t) cons :no-side-effects)
(proclamation consp (t) gen-bool :pure)
(proclamation car (list) t :reader)

consは任意の引数を2つ取って、cons(と言う型)を返す、副作用無しな関数。 conspは、任意の引数を一つ取って、論理値を 返す厳格な関数で、副作用無し。carはリストを引数に取って、返値は任意型、副作用は無いとは 言えない。引数依存だから。

Haskellは、こういうのを動的にやるのかな。何となく裏で繋がっていそう。