ecl (3)

今年もアプルのWWDCが開催された。WWDCって、ワールドワイド開発者会議の事だ。 アプルに取って大事な大事な開発者に新製品をお披露目して、開発ヨロってお願いする 大事な会議。

大方の予想を裏切って、大事なものは何も無かった。アプル信者はがっかりして、 うらみつらみを、 WWDC 2014でアップルが発表しなかった7つの製品 にまとめている。

iOS7がいOS8になるぐらいの事じゃ、驚きませんよ。改修工事なんだから。新しい器を 出してこそのアプルでしょう。ジョブズの遺産を食い潰してしまったら、もう新しい物は 有りませんじゃ、停滞モードに停滞中ですな。それとも衰退モードか?

唯一注目されたのが、Swiftって言語。邪すれば、これもジョブズの遺産だな。ちゃんと 資料まで公開してんだから、開発は大分前から始まっていたはず。仕様決めは鶴の声が 有ったに違いない。

Swiftはすごいで考察されてるように、 アプルの信者獲得作戦に違いない。なんたって、『コンピュータ、ソフト無ければ、ただの箱』 ですから、Appを量産するシンパを大事にするのは道理ですよ。

でもね、この言語、箱を選ぶんだろうな。ああ、箱と言うより、動くOSを選ぶんだろうな。 OS Xでしか動きません。ipadとかじゃ駄目ですから、箱も買ってね。

かつて、SunのJava、Googleのgo、エリクソンのerlangみたいに、どこでも動くって 事で、世界に広まった事をアプルは学習して欲しいぞ。

Appleが新言語、Swiftを発表するも、すでに閉鎖的すぎて絶望しかない 同じ事を危惧してる方がおられました。そう、アプルの甘言に騙されてはいけません。

おいらもちらっと解説書を読んでみましたよ。甘い誘惑が待ってますねぇ。i電話の センサーとかGPSはどうやってコントロールするの? どうやって使うの? そういう事が何も書かれていない、ただの誘惑書でした。 これじゃ、なんにもならんぞ。まあ、大体、厳しい所は隠すのが世の慣わし。 厳しい目で眺めておきましょ。

もう少しslime

前回に引き続いて、もう少しslimeの探求。

別マシンで動かしているecl上のswankサーバーに、ローカルから接続する方法は有るのか? 実際に別マシン(名はfb10)で、eclを起動して、replから start-swank.lispし、4005番で 待機させる。

そして、ローカル機(名はuB)から、つついてみる。

sakae@uB:~$ telnet fb10 4005
Trying xxx,xxx,xxx,xxx ...
telnet: Unable to connect to remote host: Connection refused

接続が拒否された。そりゃそうだよな。ループバックアドレスで起動してるんで、接続出来る 訳が無い。だったら、グローバルアドレスで起動させればいいんでないかい。 探してみたら、swank.lispに

(defparameter *loopback-interface* "127.0.0.1")

なんてのが有って、こやつを使ってソケットを開いてる。ここをグローバルIPに書き換えちゃう手 があるな。でも、swankサーバーには、認証機構が全く無いですぜ。CL資源を世界に公開しちゃって いいものか、悩む所だ。

で、slimeのマニュアルを見てた所、sshでトンネルを掘って、接続する方法が紹介されてた。

sakae@uB:~$ ssh -N -f -L 4005:localhost:4005 fb10
Password for sakae@fb10:
sakae@uB:

これで、トンネルが掘れた。(-fでプロセスをBGに追いやり、かつ、-Nでリモート側では 静かにしてるって言う仕様です) そして、このトンネルは、-Lで、uB側の4005番が、リモート側の127.0.0.1:4005番に 転送されるっていう限定的な物だ。 後は普通にuB側でemacsを立ち上げ、slime-connectし、ローカルの4005番に接続すればOK。

お遊びで、複数台のlocalhostからリモートのswankに接続出来るかやってみたら、2台目は こんなエラーが出て、接続を拒否されたぞ。

channel 2: open failed: administratively prohibited: open failed

だめじゃんswank君、君はまだ修行が足りんぞ。そういうおまえはアホか。老人性誇大妄想症かに かかってますなあ。

こういう機能を使って、CLで立ち上げたWebサーバにslimeで接続するって離れ業を やってる方がおられた。 Clack & Swank によるインタラクティブなWebシステム開発

上記は、start-swankって事で、身も蓋もなくswankサーバーを建てちゃったけど、この スクリプトを使わない、いわゆる、emacsがこっそりやってる事を、晒してみよう。目を皿に してコードを眺めていたら、こんなのが出来るのね。

sakae@uB:~/.emacs.d/slime$ cat z.lisp
   (load "./swank-loader.lisp")
   (setq swank-loader::*fasl-directory* "/tmp/fasl/")
   (swank-loader:init)
(load "./swank-backend.lisp")
(load "./swank.lisp")
(swank:start-server "zPORTSNO")

ちょっとしたローダーを書いておいて、起動する。

sakae@uB:~/.emacs.d/slime$ echo '(load "z.lisp")' | ecl
   :
;;; Loading "/tmp/fasl/swank.fas"
;;; Warning: These Swank interfaces are unimplemented:
 (ACTIVATE-STEPPING ADD-FD-HANDLER ADD-SIGIO-HANDLER BACKGROUND-SAVE-IMAGE DUP
  EXEC-IMAGE FRAME-CALL LIST-CALLEES LIST-CALLERS MACROEXPAND-ALL
  MAKE-FD-STREAM REMOVE-FD-HANDLERS REMOVE-SIGIO-HANDLERS RESTART-FRAME
  RETURN-FROM-FRAME SAVE-IMAGE SLDB-BREAK-AT-START SLDB-BREAK-ON-RETURN
  SLDB-STEP-INTO SLDB-STEP-NEXT SLDB-STEP-OUT TOGGLE-TRACE)
;;; Loading "/home/sakae/.emacs.d/slime/swank-backend.lisp"
;;; Loading "/home/sakae/.emacs.d/slime/swank.lisp"
;;; Loading "/home/sakae/.emacs.d/slime/contrib/swank-repl.lisp"
;; Swank started at port: 49538.
CL-USER> ;; swank:close-connection: NIL

一応立ち上がったみたい。別端末からslime-connectすると、接続はされるんだけど、 直ぐに遮断されちゃった。注意書きにあるやつを丁寧に消していけば、旨く繋がるのかな。

今後の実験の為に、telnet接続の例を載せておく。slimeのプロトコルって、先頭に 6byteのHEXで、メッセージ長を表す事になってた。

sakae@uB:~$ telnet localhost 4005
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
00002d(:emacs-rex (swank:connection-info) nil t 1)
000447(:indentation-update (("with-output-to-cdb" 1 ("ECL-CDB")) .......

所で、接続する度にport番号が変わる。emacsはこの番号をどうやって知るのか。 ちょいと疑問になったので、前回やったトレース結果を探ってみる。

vfork(Process 1425 attached
 <unfinished ...>
    :
pipe([4, 5])                            = 0
pipe([6, 7])                            = 0
 :
[pid  1425] close(0)                    = 0
[pid  1425] close(1)                    = 0
[pid  1425] close(2)                    = 0
[pid  1425] dup2(6, 0)                  = 0
[pid  1425] dup2(5, 1)                  = 1
[pid  1425] dup2(5, 2)                  = 2
[pid  1425] close(6)                    = 0
[pid  1425] close(5)                    = 0
  :
[pid  1425] execve("/usr/local/bin/ecl", ["/usr/local/bin/ecl"], [/* 27 vars */]) = 0
  :
waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED) = 1425
waitpid(-1, 0xbfe723cc, WNOHANG|WSTOPPED) = -1 ECHILD (No child processes)

pid1425は、生まれた子供であるecl。子供の標準入出力を、親側で用意したパイプに すげ替えるという工事をやってる。これで、親子の絆が深く結ばれましたね。 サーバーのport番号も、親側からは丸見得。

[pid  1424] write(7, "(progn (load \"/usr/share/common-"..., 200) = 200
[pid  1424] write(3, "\33[?25lPolling \"/tmp/slime.1424\" "..., 101) = 101

どんな指令が親から子へ伝わってるか調べてみると、どこもこれっぽい。prognで、命令を まとめているのが認められるけど、残念ながら途中で切れてしまっている。その後、ポーリングして、 子が起動したか確認してんだな。

子供がどんなTCPポートで待機してるかemacsは分かっているんで、後は涼しい顔して TCPでやり取りすればいいんだ。ユーザーが何かヘマをやらかして、緊急で子共に連絡 したいような場合も、この絆チャネルが使えるとな。このテクニックって、ftpのコントロールポートと データポートの考え方だな。納得しましたよ。

ftpの場合、20と21が使われていたはずだけど、どうだったかな? /etc/servicesを見ろ

ftp-data        20/tcp
ftp             21/tcp

20番ポートがウニャムニャって話があって、盛んにpassiveなんて事騒いでいたな。全ては、 遠い過去になりつつあるのう。

profileをreplから

前回はprofileをreplから使おうとして、あえなく失敗した。profileなんてパッケージが 見つからなかったんだ。そこでCL界のgemことASDFが悪さをしてるんじゃないかと、予想を 立てたんだ。

eclの場合contribの中にasdfが収納されてた。ファイルの冒頭をみたら、使い方は、 ASDF Manualを見ろとな。 ぐぐったら、有り難い事に勇士の方が ASDF 日本語を公開して下さっていた。 これが有れば、鬼に金棒!

ASDFって、Another System Definition Facilityの4文字熟語じゃなくて略語らしいけど、 これは後からのこじつけ。キーボードの左手系ホームポジションを思い出してみろ。 指をあちこちに移動せずとも素直に打てるではないか。Lisperは、本来ものぐさ太郎なのさ。

> (require :asdf)         ;; ASDFを使えるようにする

;;; Loading #P"/usr/local/lib/ecl-13.5.1/asdf.fas"
("ASDF" "CMP")
> (require :profile)       ;; ASDFの力を借りてPROFILEを使えるようにする

NIL
> (use-package :profile)   ;; profileの定義が見えるようにする

T
> (report)                 ;; 測定器のキャリブレーション

measuring PROFILE overhead..done
  seconds  | consed | calls |  sec/call  |  name
----------------------------------------------------
----------------------------------------------------
     0.000 |      0 |     0 |            | Total

estimated total profiling overhead: 0.00 seconds
overhead estimation parameters:
  5.6e-8s/call, 1.3760001e-6s total profiling, 5.8399996e-7s internal profiling
> :ld cup.lisp

("cup.lisp")                ;; 被測定物をロード
> (profile tak fact)        ;; 測定対象を登録

> (tak 12 6 0)              ;; 監視下で実行

1
> (fact 100)                ;; 他のやつも実行

93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
> (report)                  ;; 結果表示

  seconds  |   consed  |  calls |  sec/call  |  name
--------------------------------------------------------
     0.000 | 4,579,352 | 63,609 |   0.000000 | TAK
     0.000 |     7,360 |    101 |   0.000000 | FACT
--------------------------------------------------------
     0.000 | 4,586,712 | 63,710 |            | Total

estimated total profiling overhead: 0.09 seconds
overhead estimation parameters:
  5.6e-8s/call, 1.3760001e-6s total profiling, 5.8399996e-7s internal profiling

本業の時間取得はおかしいのに、余計な時間報告だけは旨くいってるっぽい。

> (reset)

NIL
> (tak 18 9 0)

9
> (report)

  seconds  |     consed    |    calls   |  sec/call  |  name
----------------------------------------------------------------
     0.000 | 2,501,121,775 | 15,829,689 |   0.000000 | TAK
----------------------------------------------------------------
     0.000 | 2,501,121,775 | 15,829,689 |            | Total

estimated total profiling overhead: 21.78 seconds
overhead estimation parameters:
  5.6e-8s/call, 1.3760001e-6s total profiling, 5.8399996e-7s internal profiling

These functions were not called:
 FACT

随分待たされた挙句に結果が出てきた。オーバーヘッドが、とんでもない時間に なってるな。callの度に余計なルーチンが走るのだろうから、無理ないか。

abcl

家にあるCommonLisp本を見てたら、abclこと Armed Bear Common Lisp (ABCL)が紹介されてた。熊さんの強さは 兵器なみって事で、あやかったのでしょうか。土台がJavaって所に越えられない一線、 shiroさん風に言うと、『ガラスの天井』ってのがあるのかも知れません。

あれ、この言い回し、苦労じゃ本に出てたのかな。最近はclojureが人気みたいだけど、 最初から苦労じゃと名乗っているんじゃ、そりゃ大変だわな。

そう言えば、人気の一端は、出版された書籍と相関があると思うんだ。最近、 日本国内で出版されたLisp系の書籍なんてサイトを 見つけて、しげしげと眺めております。ああ、同HPの処理系の年譜も感慨深いものが あるなあ。gaucheも息が長いな。

何故、人はJavaでLispを実装するか? それは、昔のJavaにLAMBDAが無かったからさ。 一種の憧れ、ミーハー族に違いない。Javaなら、素直にオブジェクト嗜好してればいいのに。 世の中、一定のはみ出し者が居るって事です。

冗談はさておき、オイラーも変な人になってみる。

[sakae@manjaro .abcl]$ java -jar abcl.jar
Armed Bear Common Lisp 1.3.1
Java 1.7.0_51 Oracle Corporation
OpenJDK Client VM
Low-level initialization completed in 1.294 seconds.
Startup completed in 5.842 seconds.
Type ":help" for a list of available commands.
CL-USER(1): :help

  COMMAND     ABBR DESCRIPTION
  apropos     ap   apropos
  bt               backtrace n stack frames (default 8)
  cd               change default directory
  cf               compile file(s)
  cload       cl   compile and load file(s)
  continue    cont invoke restart n
  describe    de   describe an object
  error       err  print the current error message
  exit        ex   exit lisp
  frame       fr   set the value of cl:* to be frame n (default 0)
  help        he   print this help
  inspect     in   inspect an object
  istep       i    navigate within inspection of an object
  ld               load a file
  ls               list directory
  macroexpand ma   macroexpand an expression
  package     pa   change *PACKAGE*
  pwd         pw   print current directory
  reset       res  return to top level
  rq               require a module
  trace       tr   trace function(s)
  untrace     untr untrace function(s)

Commands must be prefixed by the command character, which is ':' by default.

7秒ぐらいかけて、もっさりと起き上がってきました。(ゆったりしてるから、熊さんなのね。 納得させられましたよ。) そして、ACLみたいに、replで愛嬌を 振りまくのも熊さんだな。嘘を言うな。山菜取りに行った人が熊さんに襲われて瀕死の 重傷を負ったとか、熊さんが通学路に現れて、厳戒態勢で猟友会の人が見張っていたとか、 この地ではよくニュースになるから、童話の世界とは違うんよ。

eclにも、このコロンコマンドが有って 、便利に使ってますよ。作者のにくい心使い、今風に言うと、お・も・て・な・し だな。

このabclはtar玉に入れられていたのを捕まえてきて、万次郎Linuxの中へ放牧したけど、 おまけで、abcl-contrib.jarなんてのが付いていた。これって一体何? jarファイルって zipなんで

[sakae@manjaro .abcl]$ unzip -l abcl-contrib.jar
Archive:  abcl-contrib.jar
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2014-04-29 22:50   META-INF/
      101  2014-04-29 22:50   META-INF/MANIFEST.MF
     2279  2014-04-17 12:48   README.markdown
        0  2014-04-29 22:50   abcl-asdf/
     6176  2014-04-17 12:48   abcl-asdf/README.markdown
     1550  2014-04-29 22:50   abcl-asdf/abcl-asdf.asd
     5936  2014-04-17 12:48   abcl-asdf/abcl-asdf.lisp
       :
        0  2014-04-17 12:48   quicklisp/
     1326  2014-04-17 12:48   quicklisp/quicklisp-abcl.asd
---------                     -------
   236528                     49 files

この他にも、jfliとかjssとか言う、謎の餌が付いてきた。これらは、どうやって使うのだろう? javaのマナーとCLのマナーを習得しないと、餌に有り付けなさそう。

CLのマナーは、統一インターフェースslimeに任せてしまえばいいのか。

slimeを便利に使う

slimeで起動するCLは、inferior-lisp-program に設定してたけど、CLが大集合してくると いちいち設定し直すのが面倒。複数CLを切り替える便利な技がきっとあるはずと思って 調べてみると、ありましたねぇ。その前に、abclを起動する薄いラッパーを用意しておく事にする。 ええ、、aliasに登録したものじゃ、emacsが認識してくれないものですから。

[sakae@manjaro ~]$ cat .abcl/abcl
#!/bin/sh
cd /home/sakae/.abcl
exec java -jar abcl.jar

熊さんは、人目に付かないように、ドットの中に隠してしまいましたよ。そして、slimeの 設定も変更。

(add-to-list 'load-path (expand-file-name "~/.emacs.d/slime"))
(setq inferior-lisp-program "ecl")   ;; default
(setq slime-lisp-implementations
           '((ecl ("ecl"))
             (abcl  ("/home/sakae/.abcl/abcl"))
             (clisp ("clisp"))
             (sbcl ("sbcl") :coding-system utf-8-unix)))
(global-set-key "\C-cs" 'slime-selector)
(require 'slime)
(slime-setup '(slime-repl slime-fancy slime-banner))
;; Hyperspec C-c C-d h
(require 'hyperspec)
(setq common-lisp-hyperspec-root
        (concat "file://" (expand-file-name "~/.emacs.d/HyperSpec/"))
      common-lisp-hyperspec-symbol-table
        (expand-file-name "~/.emacs.d/HyperSpec/Data/Map_Sym.txt"))
(setq browse-url-browser-function 'w3m-browse-url)

普通に、M-x slime すると、eclが上がってくる設定。M-- M-x slime か C-u M-x slime すると 、起動するCLを聞いてくる設定だ。

起動した後、slime関係のbufferの切り替えは、C-c s で出来る。このslime-selectorを 起動すると、一文字で各種bufferを切り替え・閲覧出来る。詳しくは?で。cコマンドを 選択してみた。

 Nr  Name        Port               Pid     Type
 --  ----        ----               ---     ----
  1  ecl         (127.0.0.1 46982)  10626   ECL
  3  clisp       (127.0.0.1 57351)  10628   CLISP
  5  sbcl        (127.0.0.1 47608)  10629   SBCL
* 7  abcl        (127.0.0.1 45478)  10641   Armed Bear Common Lisp

こうしておけば、性能比べも楽になるぞ。おいおいやってみよう。所で、フットプリントが 小さいって事で、eclをデフォにしたけど、その点よろ。topしてみっか。

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
10629 sakae     20   0  571468  58844  16092 S  3.3  5.7   0:27.22 sbcl
10641 sakae     20   0  670048  91192   8780 S  3.3  8.9   0:59.07 java
10626 sakae     20   0   41068  27556   3980 S  0.7  2.7   0:06.10 ecl
10628 sakae     20   0   13600   5580   1988 S  0.3  0.5   0:02.72 lisp.run
10619 sakae     20   0   49604  17036   7176 S  0.0  1.7   0:05.55 emacs

ふーん、clispが一番軽いのね。まあ、これ起動しただけだから、負荷をかけていったら どうなるかは、疑問ですけどね。

遅いのも良いものだ

前の方で、もう少しslimeでemacsがどうやってCLを起動しているかを調べた。残念ながら、 肝心な所がstraceの制限から闇になってたんだ。

でも、abclの起動が遅いものだから、ちらっと起動のやり取りがemacsのbufferに表示されちゃったぞ。 新幹線は速くて便利だけど、鈍行でのんびり行くと、見えなかったものが見えてきて、いい ものだな。

(progn (load "/home/sakae/.emacs.d/slime/swank-loader.lisp" :verbose t) 
       (funcall (read-from-string "swank-loader:init")) 
       (funcall (read-from-string "swank:start-server") "/tmp/slime.10619"))

Armed Bear Common Lisp 1.3.1
Java 1.7.0_51 Oracle Corporation
OpenJDK Client VM
  :

あれれ、おいらがやったのと同じ方法で起動してるよ。