Gauche と可視化


はじめに - Gauche とデータ可視化 -

この記事は、Lisp Advent Calendar 2015 ( http://qiita.com/advent-calendar/2015/lisp ) の6日目して書かれました。

Emacs での Gauche プログラミング環境の改善として試作した、Gauche で生成した画像を Emacs 側に自動で表示する仕組みについて説明します。

プログラムを書き、実行し、よりよいプログラミングとするためデータを可視化し、検討する。Lisp のもつ REPL (Read-Eval-Print Loop) のおかげでプログラムを書いて評価するサイクルは素早く回すことができます。しかし、わたしの環境ではデータを作って可視化した時にそれを「わざわざ」手で開く必要があります。これは、GNU R のようなプログラミングとデータの可視化が統合された環境と比べると、貧弱で効率が悪いと言わざるを得ません。

Lisp/Scheme の利用者はよく知っていると思われますが、REPL から離れずにプログラミングを行えるというのは非常に効率が良いものです。データの可視化とその利用も同じように、余計なアクションをしなくてはならないとなると、途端におっくうになってしまいます。

これを少しでも改善できないか、というのがわたしのモチベーションです。

Emacs daemon モードによる Scheme 側から Emacs の操作

scheme-mode

Emacs で scheme-mode を利用すると *scheme* バッファで対話的に Gauche のプロセスと通信することができます。ちゃんと調査した訳ではありませんが、一般的な Scheme プログラミングの開発環境の一つ、と思われます。scheme-mode はバッファでコードを編集できる点で十分有用ですが、機能的には例えば Eclipse のような IDE に比べればシンプルです。

scheme-mode は内部的には comint モード(comint.el)をベースとしています。comint は Emacs の組込みのプロセスに文字列を送る機能を使っています(process-send-string 関数)。

Emacs daemon モードと emacsclient --eval

scheme-mode では Emacsから Gauche の REPL へ一方的にS式を送り、評価させることしかできません。しかし、少し工夫すれば、Gauche 側からEmacs 側に影響を与えることが可能です。

Emacs 23.1 以降には daemon モードと呼ばれる実行形態があります。これは daemon のように Emacs プロセスを起動しておいて、emacsclient というコマンドで接続して利用する、という形態です。

この emacsclient には --eval というオプションがあり、任意のS式を与えて Emacs 側で実行させることができます。セキュリティホールになる機能と思われますが特に制限は掛かっていないようです。この機能を使うと、コマンドラインから Emacs 側に影響を与えることができます。

# emacs -daemon
# emacsclient -c
=> Emacs の Window が開く
# emacsclient --eval "(message \"hi\")"
=> Emacs のモードライン行に「hi」と表示される
# emacsclient --eval "(switch-to-buffer \"*scratch*\")"
=> *scratch* バッファが表示される

eval-in-emacs の実装と可視化

これで Gauche 側から Emacs 側に影響を与える関数 eval-in-emacs を実装することができます。じゃっかん迂遠ですが、Emacs から実行した gosh から emacsclient を経由して Emacs 側に影響を与えます。


;; eval-in-emacs
;; 引数のS式 exp を Emacs 側で評価する。
(use gauche.process)
(define (eval-in-emacs exp)
  (run-process
    `(emacsclient --eval) :redirects `((<<< 0 ,exp)) :wait #f))

run-process はサブプロセスでコマンドを実行する Gauche の高レベルAPIです。見慣れない「<<<」記号は、任意のSchemeオブジェクトをwrite-to-stringした結果の文字列を子プロセスに渡すことを意味します。これは、クオートした文字列ではなくS式を渡すために使用しています。run-process の詳細については、Gauche リファレンスマニュアル 9.23.1 Running Subprocess を参照してください。

例: PostScript による可視化

準備ができたので、PostScript で可視化してみます。実装方法は幾つか考えられますが、ここでは、Gauche で PostScript ファイルを手書きすることにします。

;; 点列をプロットし一時ファイル名を返す
(define (plot xs)
  (let1 f (sys-tmpnam)
    (with-output-to-file f
      (lambda ()
        (format #t "%!PS-Adobe-3.0~%")
        (format #t "~a ~a moveto~%" 50 700)
        (for-each
         (lambda (x)
           (format #t "~a ~a lineto~%" (car x) (cdr x))) xs)
        (format #t "stroke~%")
        (format #t "showpage~%")))
    f))
;; 点列をプロットし Emacs に表示する
(define (plot&show xs)
  (eval-in-emacs
   `(progn (find-file-other-window ,(plot xs)) (doc-view-mode))))

ここで plot&show に渡している find-file-other-window は別ウィンドウでファイルを開く Emacs の関数です。plot&show は scheme バッファで入力されていることを想定しているので、同じウィンドウでは都合が悪いのです。また、doc-view-mode は PostScript ファイルを図として表示するためのモードです。

この plot&show 関数を Gauche から実行することで、REPL バッファから離れずに可視化しプログラミングを続けることができます。

拡張など

上記はまだおもちゃレベルにすぎません。SLIME のようなきちんとした統合環境が待たれます。

特に満足できないのは、描画したデータを細かく触る手段が Scheme 側に無い点です。描画結果が何かの Scheme から触れるオブジェクトになるとよいのですが、実現できていません。

また PostScript を手書きする、というのは可視化としてあまりに素朴にすぎるとも思われます。gnuplot のような外部ツールに PostScript を生成させることも可能です。graphviz のようなツールとも連携すべきでしょう。

参考: SLIME

Common Lisp 用の開発環境としては、SLIME (https://common-lisp.net/project/slime/) が知られています。SWANK サーバと呼ばれる Common Lisp などのプロセスとソケット通信します。通信はS式で行われます。SWANK サーバはEmacsとは独立して起動させておくことができます。例えばサーバで起動させているSWANKサーバに別のクライアントで実行している SLIME モードから接続する、ということも可能となっています。

SLIME では、Emacs 側から Emacs 側に任意の式を評価させることができます。セキュリティ上問題があるため、この機能を利用するためには Emacs 側で変数 slime-enable-evaluate-in-emacs を nil 以外に設定しておく必要があります。

おわりに

以下の記事に、Lisp をとても良く表現していると思うセンテンスがあるので引用しておきます。インタラクティブ性は Lisp の力の一つなのです。

But Lisp was intended to be interactive (because it was invented to support AI research), whereas C was not (because it was invented for writing operating systems).
Interactivity is native to Lisp whereas it is foreign to C, just as building standalone executable is native to C but foreign to Lisp.

プログラミングがより高級になって行くと、プログラミングはデータとの戦いになると思われます。データの可視化も必要不可欠な要素となるでしょう。
恐らく一つの方向は、R や MATLAB が実現している専用言語と専用環境、という組み合わせだと思われます。もう一つの方向が、汎用言語で何とかする方向であるなら、Lisp/Scheme の開発環境はもっとデータ可視化のための機能を整備する必要があるのではないでしょうか。また、そうすることができれば、Lisp/Scheme はこれからも有用な道具として、生き延びるのではないでしょうか。

参考