Edwin 又は mit-scheme

emacsもどき、色々有ったなと考えていると、mit-schemeにemacsもどきが付属してる事をを思い出した。早速行ってみると、 schemeで書いたものらしい。mit-schemeに組み込まれているようで、分離は出来ないようだ。

これって、昔M$がIEはWindowsと一体になってますから分離出来ないって言ってたのと一緒の 構図だな。

8 Edwin によると、

sakae@uB:~$ mit-scheme --edit

こんな風に起動するか

sakae@uB:~$ mit-scheme
MIT/GNU Scheme running under GNU/Linux
Type `^C' (control-C) followed by `H' to obtain information about interrupts.

Copyright (C) 2011 Massachusetts Institute of Technology
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Image saved on Tuesday October 22, 2013 at 12:31:09 PM
  Release 9.1.1 || Microcode 15.3 || Runtime 15.7 || SF 4.41 || LIAR/i386 4.118
  Edwin 3.116

1 ]=> (edit)

replから呼び出すみたい。例によって、statusライン。それっぽい。

--**-Edwin: *scheme*               (REPL: listen)----All----------------------

うぶに入れたんだけど、一式は下記の場所に鎮座してた。

sakae@uB:/usr/lib/i386-linux-gnu/mit-scheme$ ls
all.com  edwin  imail  optiondb.scm  sf   ssp          xml
cref     ffi    lib    runtime       sos  star-parser

edwinなんてのが有るんで中を覗いてみたら、哀れな事にバイナリーコードだった。 これはいかんって事で、ソースを取ってきたら、本当にschemeで書かれていた。

edwinの本体と言うかtopは、edwin/editor.scmだ。ここから見て行くとよかろう。

Windowsでもmit-scheme

これを機会にWindowsにも入れてみた。だって、マニュアルが同梱されてると思ったから。 (その期待は見事に裏切られたけどね)

DeskTopに出来るアイコンの中身は下記のようだった。ついでに、作業フォルダーを設定しておくと吉。 それぞれがフルパスで登録してあるって事は、PATHの設定をサボったのかな。その割には、 コンパネのプログラムの所を見ると、しっかり登録されてる。まあ、そういう事もあらーな ぐらいにしておこう。 使うにはEdwinを強制される。

"C:\Program Files\MIT-GNU Scheme\bin\mit-scheme.exe" 
     --library "C:\Program Files\MIT-GNU Scheme\lib" 
     --edit

しょうがないので、サイトにあるマニュアルを見てたら、コンパイル出来るよなんて書いてあった。 schemeでコンパイルなんて珍しいな。使った事はないけど、一つだけ知ってる。 (chez (chez scheme))。インタプリター版は無料だけど、 コンパイラー版は有料。

ソースに付属してたREADMEを見ると

* "sf" contains a program that translates Scheme source code to an
  internal binary format called SCode.  SCode is the internal
  representation used by the MIT/GNU Scheme interpreter.  The "sf"
  program also performs a handful of optimizations, such as
  user-directed beta substitution and early binding of known variables
  such as CAR.

* "compiler" contains the native-code compiler.  This program
  translates SCode to machine-language instructions.

コンパイラーの威力ってどのぐらい?

無料で使えるなら、いいねボタンを押しちゃうぞ。

こういう時は、例のやつですな。 フィボナッチで各種言語をベンチマーク

(define (fib n)
  (if (< n 2)
    n
    (+ (fib (- n 2)) (fib (- n 1)))))

(write (fib 38))
(newline)

Lisp/Scheme系なら、竹内さんちのたらい回しの方が、日本的情緒に溢れていた良いかも。

sakae@uB:~/mit$ time mit-scheme --load fib.scm --eval '(quit)'
MIT/GNU Scheme running under GNU/Linux
 :
;Loading "fib.scm"...39088169
;... done

[1]+  Stopped                 mit-scheme --load fib.scm --eval '(quit)'

real    1m38.176s
user    0m0.000s
sys     0m0.004s

mit-schemeはスクリプトをロード(実行)後、replに落ちるんだけど、そこで(exit)すると 終了してもいいかいなんて聞いてくる。これじゃ、時間測定には向かないので(quit)で、 バックグランドに廻るようにしてる。よって、測定終了後、fgしてから手動で終了の事。

1 ]=> (cf "fib.scm")

;Generating SCode for file: "fib.scm" => "fib.bin"...
;  This program does not have a USUAL-INTEGRATIONS declaration.
;  Without this declaration, the compiler will be unable to perform
;  many optimizations, and as a result the compiled program will be
;  slower and perhaps larger than it could be.  Please read the MIT
;  Scheme User's Guide for more information about USUAL-INTEGRATIONS.
;... done
;Compiling file: "fib.bin" => "fib.com"... done
;Unspecified return value

翻訳したぞ。ちゃんと指定すると、小さく速くなるとな。 下記の成果物が出来た。

sakae@uB:~/mit$ ls
fib.bci  fib.bin  fib.com  fib.scm

fib.bciはdebug用の情報。fib.binはSCodeと呼ばれるもの。CLで言うfaslだかの高速ロード用 中間コード。そしてfib.comがネイティブコードらしい。

sakae@uB:~/mit$ time mit-scheme --load fib.com --eval '(quit)'
MIT/GNU Scheme running under GNU/Linux
 :
;Loading "fib.com"...39088169
;... done

[2]+  Stopped                 mit-scheme --load fib.com --eval '(quit)'

real    0m3.661s
user    0m0.000s
sys     0m0.004s

インタープリタに比べて劇速になった。先ほど指示が有った宣言

(declare (usual-integrations))

を追加して、再コンパイルしてみる。

1 ]=> (cf "fib.scm")

;Generating SCode for file: "fib.scm" => "fib.bin"... done
;Compiling file: "fib.bin" => "fib.com"... done
;Unspecified return value

今度は、文句を言われなかった。で、御利益は有るのか?

sakae@uB:~/mit$ time mit-scheme --load fib.com --eval '(quit)'
  :
;Loading "fib.com"...39088169
;... done
  :
real    0m2.207s
user    0m0.000s
sys     0m0.000s

Oh! 大分速くなったな。こんなに良いものなら、デフォで採用してよね。最適化が 不都合なら、その時は最適化不要って宣言の方がよくね?

参考までにgaucheでも。

sakae@uB:~/mit$ time gosh fib.scm
39088169

real    0m9.879s
user    0m9.516s
sys     0m0.068s

そして、自分自身で、実行時間を測るやつ

1 ]=> (with-timings
           (lambda () (fib 38))
           (lambda (run-time gc-time real-time)
             (write (internal-time/ticks->seconds run-time))
             (write-char #\space)
             (write (internal-time/ticks->seconds gc-time))
             (write-char #\space)
             (write (internal-time/ticks->seconds real-time))
             (newline)))
97.67 1.97 99.898
;Value: 39088169

ここまでは良い。でも、コンパイルして出来上がったfib.comって、ネイティブ言語になってますって事は、 Windowsへ持って行ったら、そのまま実行出来るかもよ。なんたって、*.com は *.exe の 仲間と思ってますから。

やってみた。16bit MS-DOS Subsystemって窓が出てきて言う事にゃ、The NTVDM CPU has encountered an illegal instraction. CS:05d4 IP:01cc OP:fo 4c c5 a0 d8 ... との事でした。Windowsって、サフィックスでころっと 騙かされるのね。

しょうがないので、mit-schemeの窓から、(load "fib.com")したら、ちゃんと結果を返して くれた。mitの人達よ。紛らわしいサフィックスは勘弁してよね。まあ、mit-scheme上なら、 バイナリーでも互換性が有るって事が分かったから、今回は許したる。(と、偉そうに言ってみた)

debug環境

mit-schemeは、きっとRMSの弟子達が作ったに違いない。結構debug環境が充実してる。

sakae@uB:~/mit$ cat bug.scm
;; bug
(define (f n)
  (if (zero? n)
      "0"
      (* n (f (- n 1)))))

(f 5)

こんなbug入りのやつを、edwinから実行してみる。

(load "bug")
;Loading "bug.scm"...
;The object "0", passed as the second argument to integer-multiply, is not the \
correct type.
;To continue, call RESTART with an option number:
; (RESTART 2) => Specify an argument to use in its place.
; (RESTART 1) => Return to read-eval-print level 1.
;Start debugger? (y or n): y
;Starting debugger...

エラーを検出すると、debuggerを使うか聞いてくる。はいと答えると、画面が反転して (Windows上だと、別窓が出現)debug用画面になる。上面の状態を下に示す。

The buffer below shows the current subproblem or reduction.
-----------
The error that started the debugger is:

  The object "0", passed as the second argument to integer-multiply, is not the\
 correct type.

>S0  (integer-multiply 1 "0")
    R0  (* n (f (- n 1)))
    R1  (if (zero? n) "0" (* n (f (- n 1))))
    R2  (f (- n 1))
 S1  (f (- n 1))
    R0  (* n (f (- n 1)))
    R1  (if (zero? n) "0" (* n (f (- n 1))))
    R2  (f (- n 1))
  :

いわゆる、スタックフレームだ。Ctl-n, Ctl-p で、このフレーム内を登ったり下ったり 出来る。

下画面は、そのフレームでの細かい内容が表示される。debug画面からの脱出はqだ。元のscheme画面に 戻れる。

最初の画面にも出てるけど、(RESTART 2)すると、エラーしてる式の値を新しいものに置き換えて、 継続出来る。

(restart 2)
;... done
;Value: 120

(restart 2) Ctl-x Ctl-e で式を評価すると、mini-bufferにNew Argumentって聞いてくるので、 この場合なら、1って入力((integer-multiply 1 "0")の実行結果が1でしたって宣言)。 これで、エラーが無くなって、計算が継続。最終結果が得られた。

Edwinからのオペレーションだったけど、普通にREPLを起動して、bugでエラーが起こった時は、 (debug)って叩けば、テキスト版のdebuggerが起動するよ。Schemeマシーンだな。

mitからの依頼

Windows上のEdwinで、find-fileした時に、読み込んだはずのファイル内容が、なにも表示されない。 バッファーリストで確認すると、サイズがゼロになってる。メッセージバッファーに何か 出てるかと思ったけど、そんなバッファーは存在しない。

しょうがないので、開いたバッファー上で編集し、save-bufferとかwrite-fileすればちゃんと、 ファイルが作成される。

また、dired-modeからファイルをオープンしても、画面には何も表示されない。dired-mode では、ちゃんとファイルサイズを認識してるにも関わらずにね。不思議な現象。

リナの方では全く問題ないので、Window上のedwinがおかしいと思われ。何が原因か? mitからの 宿題と言うか調査と言うか挑戦状を突きつけられました。

こういう時は、ソース嫁。それはいいんですが、相手はschemeですぜ。すいすいとソースの 海を泳ぐのどうします?

cscopeはこの場合使えんな。etagsも駄目、となると、オイラーのかすかな記憶では、 GNU GLOBALかな。プラグインを入れると、 色々な言語も扱えるらしい。色々な言語のうちの一つにschemeが挙がってた。

実際はGNU GLOBALの対応言語を大幅に増やすPygmentsパーサーを導入する って事をしないと駄目みたい。世の中甘くないね。

諦めて、grepで代用するか。で、edwinのソースの中に入って、まずはどんなファイルが 有るか調査。rf822.scmってSMAP解散との一部報道でSMTPが話題に? じゃないですか。(元の鞘に収まったけど、次の危機は9月だかの契約終了時?)他にもnntp.scmなんてのが有ったり、vc-*.scmなんてので、バージョン管理は お任せあれ(*には、gitとかcvsとかがマッチする)とか、emacsに一歩でも近づきたい気持ちが 現れてますなあ。おかげで、調査人は大変だけど。。。

で、そんな中、Tags.shなんてのに気付いた。中身は

#!/bin/sh
etags *.scm

えっ!! 早速、man etags そこには驚愕な事実が。。。

DESCRIPTION
       The  etags  program is used to create a tag table file, in a format un‐
       derstood by emacs(1); the ctags program is used to create a similar ta‐
       ble  in a format understood by vi(1).  Both forms of the program under‐
       stand the syntax of C, Objective C, C++, Java, Fortran, Ada, Cobol, Er‐
       lang,  Forth,  HTML, LaTeX, Emacs Lisp/Common Lisp, Lua, Makefile, Pas‐
       cal, Perl, PHP, PostScript, Python,  Prolog,  Scheme  and  most  assem‐
       bler-like syntaxes. 

思い込みは悪ですなあ。そしてオイラーの知らないtagsの使い方が、こちらで紹介されてた。 Emacs の tags 機能の使い方

Dive into edwin source

ソースの海に潜ってみます。まずは、find-fileがどうなってるかだな。

(define (find-file filename)
  (select-buffer (find-file-noselect filename #t)))

(define-command find-file
  "Visit a file in its own buffer.
If the file is already in some buffer, select that buffer.
Otherwise, visit the file in a buffer named after the file."
  "FFind file"
  find-file)

define-commandって、edwinに特有なのかなあ。次はfind-file-noselect

(define (find-file-noselect filename warn?)
  :
              (let ((buffer (new-buffer (pathname->buffer-name pathname))))
                (let ((error?
                       (not
                        (catch-file-errors
                         (lambda (condition) condition #f)
                         (lambda () (read-buffer buffer pathname #t))))))
  :

次は、read-buffer かな。

(define (read-buffer buffer pathname visit?)
  :
         (lambda ()
           (set! truename (->truename pathname))
           (%insert-file (buffer-start buffer) truename visit?)
  :
    (if visit?
        (begin
          (set-buffer-pathname! buffer pathname)
          (set-buffer-truename! buffer truename)
          (set-buffer-save-length! buffer)
          (buffer-not-modified! buffer)
          (undo-done! (buffer-point buffer))))
  :

次は、%insert-file あたりかなあ?後ろの方に、set-buffer-save-length! なんてのが 有るからね。

(define (%insert-file mark truename visit?)
  (let ((method (read-file-method (mark-group mark) truename)))
    (if method
        (method truename mark visit?)
        (let ((do-it
               (lambda ()
                 (group-insert-file! (mark-group mark)
                                     (mark-index mark)
                                     truename))))
          (if (ref-variable read-file-message mark)
              (let ((msg
                     (string-append "Reading file \""
                                    (->namestring truename)
                                    "\"...")))
                (temporary-message msg)
                (let ((value (do-it)))
                  (temporary-message msg "done")
                  value))
              (do-it))))))

やっと、それっぽいのに辿り付いたな。read-file-methodの方を追って行ったら、gzipが どうのとか暗号ファイルがどうのと言われたので、バックトラック。もう一方の道、 group-insert-file! を追う。

(define (group-insert-file! group start truename)
  (call-with-input-file truename
    (lambda (port)
      (if (not (ref-variable translate-file-data-on-input group))
          (port/set-line-ending port 'NEWLINE))
      (let ((length ((port/operation port 'LENGTH) port)))
        (bind-condition-handler (list condition-type:allocation-failure)
            (lambda (condition)
              condition
              (error "File too large to fit in memory:"
                     (->namestring truename)))
          (lambda ()
            (without-interrupts
              (lambda ()
                (prepare-gap-for-insert! group start length)))))
        (let ((n
               (let ((text (group-text group))
                     (end (fix:+ start length)))
                 (let loop ((i start))
                   (if (fix:< i end)
                       (let ((n (input-port/read-substring! port text i end)))
                         (if (fix:> n 0)
                             (loop (fix:+ i n))
                             (fix:- i start)))
                       length)))))
          (if (fix:> n 0)
              (without-interrupts
                (lambda ()
                  (let ((gap-start* (fix:+ start n)))
                    (undo-record-insertion! group start gap-start*)
                    (finish-group-insert! group start n)))))
          n)))))

やっと辿り付いたって感じ。なお、現在地は、fileio.scmです。

本当にここへ寄ってくれているか、debugプリントしたいんだけど、 どうしたら良いの? プリントしなくても、 bkpt でも、いいんだけど。

もう一つ、難問。Windowsのそれには、*.bci ファイルしか置いてない。これって、そのまま 使えるの? ちょっと実験。

1 ]=> (load "fib.bci")

;Loading "fib.bci"...
;Unbound variable: compressed-b1-1.00
;To continue, call RESTART with an option number:
; (RESTART 3) => Specify a value to use instead of compressed-b1-1.00.
; (RESTART 2) => Define compressed-b1-1.00 to a given value.
; (RESTART 1) => Return to read-eval-print level 1.

単独では無理のようだ。edwin-dirの中に、edwin.ldrとかedwin.pkgとかが付いてて、 これでパッケージに仕立て上げているけど、その中の何処かで定義されてるのかな?

試しにウブ上で作ったfileio.bciをWindows側へ持って行った。サイズが異なっていたけど、 取り合えず気にしない。で、Windows側で試すも、相変わらずfind-fileで読み込んだサイズは ゼロ。勿論表示せず。

ひょっとして、libの下にあるやつはおまけかと思い、そろりそろりと、libの下のedwinを ディレクトリィー毎削除。ちゃんとedwinは起動する。やっぱりおまけだ。動作に寄与してない。

どんどんlibの下を削除していったら、all.comとかが無いとmit-schemeは起動しない事が 判明。もうわけわかめなので、入れていた9.2版を9.1版に変更。やっぱりfind-fileは 動作せず。もうさじを投げ出そう。

ふと悪あがきで、fileio.scmをロードして、オリジナルの物を上書きしたらいいんでないかいと 思った。実際にやってみると、同ファイル以外で定義されたものが必要と言われ、動作せず。 どうやら、パッケージとして出来上がっているようで、そいつらにちょっかいを出すってのは、 無理みたい。残念至極であります。

うぶ上で使えばよい。それも不自由なedwin経由じゃなくて本物のemacs経由でね。 M-x run-schemeすると、入れているgaucheに競り勝って、mit-schemeが起動してくるよ。

bugを踏んだ時の対処法

1 ]=> (f 10)

;The object "0", passed as the second argument to integer-multiply, is not the \
correct type.
;To continue, call RESTART with an option number:
; (RESTART 2) => Specify an argument to use in its place.
; (RESTART 1) => Return to read-eval-print level 1.

2 error> (debug)

There are 14 subproblems on the stack.

Subproblem level: 0 (this is the lowest subproblem level)
Expression (from stack):
    (integer-multiply 1 "0")
There is no current environment.
The execution history for this subproblem contains 3 reductions.
You are now in the debugger.  Type q to quit, ? for commands.

3 debug> z 1
z
Cannot evaluate in current environment;
using the read-eval-print environment instead.

Expression to EVALUATE and CONTINUE with ($ to retry):
;Value: 3628800

gaucheとmit-schemeが競り合って、mit-schemeが勝つのは、オイラーが軟弱で、debug環境を こよなく愛しているのを知ってるからだな。gaucheのdebug環境が充実するのは何時だろう?

readme

Gauche で IE を制御する (ことが出来たらよかったのだけど)

Gauche-OLE を使って IE に内トロコイド曲線を書く

gosh-mode.el

Scheme処理系の選び方

CentOS 7のシステム管理「systemd」をイチから理解する

Haskell初心者がFizzBuzzに挑戦してみました

この人、本当にHaskell初心者かなあ? Schemeとかガンガン使ってて、Haskellにも手を 出してみましたって構図かな。Haskellな人はDebugどうしてるんだろう? せめてmit-scheme ぐらいなdebug環境が欲しいと思うのは、オイラーだけ? 違うって、Haskellでやる事は、 ひたすらオイラーの100問を解くだけですから、debugなんて関係ないのです。既に脳内debugが 完了してますから。

でも、最近FinTechとかいう造語をよく聞くぞ。ファイナンスとITが融合したとかいう あおり。前に人参をぶら下げられれば、誰もが目の色を変えますよ。そんな人はHaskellだろうと ocamlだろうと、根性debugしてるに違いありません。

ウォール街がこの手の関数型言語に侵されたのは、有り余る頭脳がNASAから放出されて、 数学が得意な連中がなだれ込んだと言われていますよ。