gaucheでも

『タコの教科書』なんて本を読んだ。決してLinuxの本ではない。だってさ、古い人なら 超ど級の素人さんをタコともてはやして、Linux陣営へ引きすり込むために、某仕掛け人が 大事にしてたから。

正真正銘のオクトパスの本である。たこ足配線とか、オクタルなんて用語は、たこの足が 8本ある事が由来になってる。

たこは原生期には、身を守る甲羅があったそうな。それが甲羅を棄て、完全な軟体生物に 進化。身を守る方法は、体色を素早く変化させて、回りに溶け込む。タコの墨、足を 切って、敵がそれにくらいついている間にスタコラ逃げる作戦。足は再生するそうだ。

子孫を残すと、雄も雌も死んでしまう。メスは、卵を生むと、餌を食べずにかいがいしく 卵の世話をする。そして死んでしまう。オスも交尾の疲れで、2ヶ月ぐらいで死んでしまう とか。

サッカーの優勝を見事に予想して有名になったタコは、3年程で死んでしまったけど、 大体寿命はそんなものらしい。

食べるたこでは、マダコが上位を占めているけど、養殖は難しいらしい。卵から孵化した ものが、海中に漂うプランクトンの生活を送る為、その時期の管理が難しいとか。 頑張って、三重大とか近大あたりが、養殖にチャレンジしませんかね。ああ、マグロの方が 儲かるから、だめか。

日本では淡路島あたりの物が有名らしいけど、地元で消費されちゃって、中々市場には 出回らない。大体、口に出来るのはアフリカあたりからやって来るやつだな。

西洋人は、デビル・フィッシュと言って毛嫌いしてたようだけど、寿司とかの文化で 段々と食べる人が多くなってるそうな。タコも国際争奪戦ですかね。

大阪で1935年に遠藤留吉さんと言う人が、たこ焼きを発明したそうな。もっと古い歴史が あるものと思っていたけど、意外に新しいのね。知らんかったよ。

タコはエロスと死の象徴らしい。葛飾北斎の 蛸と海女 と言う版画は、その道の人には余りにも有名だそうだ。 ゴッホとかが大いに刺激されたとか。知らんかったぞ。

読書メーター 7冊 / 2159頁 / 12200円

gaucheの環境作り

新しい版が出たと言うのに入れてなかった。ウブなマシンは余裕があるので、ソースから 自前で入れた。そしてその残骸を残しておいた。後でgdbにかけられるようにね。万次郎Linuxの方は、 素直にパッケージを入れておいた。(おまけでslibとguileも付いてきた。slibは有り難いけど、 guileはいらない子。まてまて、RMSの壮大な夢に投資してみるか)

これを機会にgaucheでも補完と思って、どなたかからのを頂いてきた。

;; complete
(when (require 'auto-complete nil t)
  (global-auto-complete-mode 1)
  (defun ac-next-or-next-line (arg)
    (interactive "p")
    (if (/= (length ac-candidates) 1)
        (ac-next)
      (ac-abort)
      (next-line arg)))
  (defun ac-previous-or-previous-line (arg)
    (interactive "p")
    (if (/= (length ac-candidates) 1)
        (ac-previous)
      (ac-abort)
      (previous-line arg)))
  (define-key ac-complete-mode-map "\C-n" 'ac-next)
  (define-key ac-complete-mode-map "\C-p" 'ac-previous)
  (custom-set-faces
   '(ac-candidate-face ((t (:background "dark gray" :foreground "blue"))))
   '(ac-selection-face ((t (:background "blue" :foreground "white"))))))

;; eldoc
(require 'eldoc-extension)
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'lisp-inteeraction-mode-hook 'turn-on-eldoc-mode)
(setq eldoc-idle-delay 0.2)
(setq eldoc-minor-mode-string "")
;; scheme-mode-hook
(defvar ac-source-scheme
  '((candidates
     . (lambda ()
         (require 'scheme-complete)
         (all-completions ac-target (car (scheme-current-env))))))
  "Source for scheme keywords.")
;; Auto-complete-mode config
(add-hook 'scheme-mode-hook
          '(lambda ()
             (make-local-variable 'ac-sources)
             (setq ac-sources (append ac-sources '(ac-source-scheme)))))
(autoload 'scheme-smart-complete "scheme-complete" nil t)
(eval-after-load 'scheme
  '(progn (define-key scheme-mode-map "\t" 'scheme-complete-or-indent)))
(autoload 'scheme-get-current-symbol-info "scheme-complete" nil t)
(add-hook 'scheme-mode-hook
          (lambda ()
            (make-local-variable 'eldoc-documentation-function)
            (setq eldoc-documentation-function 'scheme-get-current-symbol-info)
            (eldoc-mode t)
            (setq lisp-indent-function 'scheme-smart-indent-function)))

コピペ野郎なんで、初回にemacsに怒られた。eldoc-extensionが無いとな。これはパッケージに なってたぞ。それから、scheme-completeも無いとな。こちらは、http://www.emacswiki.org/emacs-ru/download/scheme-complete.el あたりを貰ってくれば、取り合えず動く。mini-bufferに関数の引数の説明も出てきた。 (補完用データが更新されていないのが残念)

今まで使ってたgoshを起動するルーチンが、emacsの時代にそぐわなくなっていたので、 下記のようなシンプルなやつにした。最近のrun-schemeって、自動で窓を分割してくれるのね。

(setq scheme-program-name "/usr/local/bin/gosh -i")
(defun runme ()
        (interactive)
        (run-scheme scheme-program-name))

(define-key global-map
  "\C-cG" 'runme)

万次郎Linuxの方は、以前racketがらみで入れたgeiserが幅を利かせていて、上記が動かなかった。 しょうがないので、racketとは縁を切る事にしたよ。

emacsからgoshを使うんじゃなくて、ターミナルから使う場合、rlwrapと連携させておくと便利。 それ用の設定が、 改めてGaucheとrlwrapの連携についてとか、 REPLでヒストリと補完を使う (rlwrap) に載っているので、設定しておくと吉。

gaucheでcsvファイルの読み書き

しようと思って使い方をinfoしてみた。性懲りもなく、前回からやってる血圧アプリを schemeで書いたらどうなるかと思ってね。schemeも色々あるけど、日本語充実って事で、 gaucheなんです。

 -- Function: make-csv-reader separator :optional (quote-char #\")
     入力ポートを省略可能引数として取る手続きを返します。 手続きが呼ばれ
     ると、ポート(省略された場合は現在の入力ポート)からレコードを1つ読み
     込み、 フィールドのリストを返します。入力ポートが EOF に達すると、
     EOF を返します。

 -- Function: make-csv-writer separator :optional newline (quote-char
          #\")
     出力ポートとフィールドのリストの2つの引数を取る手続きを返します。 手
     続きが呼ばれると、SEPARATOR で区切られたフィールドを 正しくエスケー
     プして出力ポートに出力します。レコードの区切り文字列を NEWLINE で指
     定することもできます。例えば、ファイルが Windows の プログラムでも
     読めるように、‘"\r\n"’ を渡すことができます。

これしか無くて、高レベルの手続きはいつか、、、って説明だった。 何となくpythonのそれと似てるんで、使い方は想像出来るんだけど具体例を思い付かない。 こういう時は、あの人に聞いてみるしかないな。

直交世界の地図 に、schemeの精神が表明されてた。またgaucheに特化した、 Gaucheクックブックにも説明が 出てた。

まずは基本を押さえておけって事です。csv-readerなんて名前が付いているぐらいだから、 schemeの一般手続き read を置き換えるものと推測。その裏を取ってみる。

gosh> (use text.csv)
#<undef>
gosh> (define in (open-input-file "2013.csv"))
in
gosh> in
#<iport 2013.csv 0x86158c0>
gosh> ((make-csv-reader #\,) in)
("13010103" "118" "80" "60")
gosh> ((make-csv-reader #\,) in)
("13010121" "107" "67" "72")

csvデータだと思ってポートから一行読み出してくれ! で、その読み出し仕様は、なんちゃら、 かんたらってのをmake-csv-readerに指定すると供に、読み出しを実施。

CSVファイルにヘッダーが付いていたりして、それをどうするってとかあるけど、一気読み かつ、余り色々なライブラリーを必要としないのがいいな。 こちらが、そのオイラーの我がまま仕様を満たすやつ。

gosh> (call-with-input-file "2013.csv"
  (lambda (in)
    (port->list (make-csv-reader #\,) in)))
(("13010103" "118" "80" "60") ("13010121" "107" "67" "72") .....)

我がままついでに言わせて貰うと、返ってきたデータが文字列になってるのが気にくわん。 これじゃ、使う前に数値にしないといけないな。面倒な事は読み取り時にやっておくれよぅ。 お願いだからさ。

所で、port-listってどう実現されてる? オイラーなら、読み込んだデータをどんどんと cons してって、最後に反転するかな。ソースがあるから、こっそり覗いてみる。これが隠れた 楽しみだったりします。

求めるものは、lib/gauche/portuil.scm に有った。

(define (port->list reader port)
  (with-port-locking port
    (^[]
      (let loop ([obj (reader port)]
                 [result '()])
        (if (eof-object? obj)
          (reverse! result)
          (loop (reader port) (cons obj result)))))))

正に予想した通り。予想が外れたのは、reverse! て、びっくりマークが付いている事だった。 で、良いものにお目にかかったので、これを少々改造させて貰おう。ついでに、csvなファイルを 与えて、それを数値に変換して読み込んでくれるのも定義しとく。

(use text.csv)

(define (csv-num->list reader port)
  (with-port-locking port
    (^[]
      (let loop ([obj (reader port)]
                 [result '()])
        (if (eof-object? obj)
          (reverse! result)
          (loop (reader port) (cons (map string->number obj) result)))))))

(define (myload cfile)
  (call-with-input-file cfile
    (lambda (in)
      (csv-num->list (make-csv-reader #\,) in))))

結果の確認は、slibにあるpretty-printを使ってみた。便利なやつ、traceとかも あるので、積極的に使うべし。

gosh> (define aa (myload "2013.csv"))
aa
gosh> (use slib)
#<undef>
gosh> (require 'pretty-print)
#t
gosh> (pretty-print aa)
((13010103 118 80 60)
 (13010121 107 67 72)
    :
 (13011003 121 79 60)
 (13011021 112 68 65))
23

でも、何となく釈然としない。CSVファイルの内容を数値にしたい事は煩雑にあるはず。 それなのに、おいらみたいに新しく手続きを定義しなければならないなんて。。。

port->listの説明をinfoで見た時、そのすぐ近くにport-mapってのがある事に気が付いて いたんだ。それの引数を見ると、読んだデータを加工する為の関数と、readerを指定する ようだ。これならぴったりだろう。但しreaderは、current-input-portを利用するとな。

ファイルポートを一時的にcurrent-input-portに出来れば事が済みそう。久しぶりに、 Gauche本を引っ張り出してみましたよ。そして、書いたのが下記。

(define (mycvsr cfile)
  (with-input-from-file cfile
    (lambda ()
      (port-map (cut map string->number <>)
             (make-csv-reader #\,)))))

Gauche本をパラパラしてる時に、部分適用 cut なんてのが目に入ったので使ってみた。 これ、伝統のlambda式の代替えになるのね。程よく使うと便利だなあ。

そんじゃ、ファイルへの書き出しはどうするかと言うと

(define DAT (mycvsr "2013.csv"))

(define (to-csv-file cfile)
  (call-with-output-file cfile
    (lambda (out)
      (for-each
         (^(e) ((make-csv-writer #\,) out (map number->string e)))
       DAT))))

こんな具合に、csvファイルを読み込んで出来たリスト(であるDAT)を、ファイルに 書き出せば良い。make-csv-writerがwriteの代わりを務めてくれる。

CSVファイルの扱いはこれぐらいでいいか。面倒なヘッダーは無い事にしたし、文字列ー数値 変換も、全フィールド対象って事でmap一発で済ませたから、手抜き感はありますけど。

現実は、こうも単純にはいかないんだろうね。亡社の健康診断データには、男の場合、身長、体重、 腹回りのデータが、女子の場合は、身長、それにBWHのデータが付いてます。

この特製CSVファイルで、読み込み時に、男子の場合は、BMIとビア腹係数を、女子の場合は ボイン係数を計算して下さい。こういう無理っぽい要求が、クライアントから有る訳ですよ。 ね、なかなか複雑でしょ。

ああ、複雑と言えば、、、某携帯電話メーカーD社では、契約内容が 複雑になり過ぎて、人間では矛盾をチャック出来ないらしい。そこでPROLOGを導入して コンピュータにチェックを任せたとか。自慢げにニュースになってた。

そんなの何か間違ってると思うぞ。単純にせんかい。総務省と公正取引委員会が、契約は これだけって、単純版を行政指導せんかね。 複雑にして、ユーザーを欺き、メーカー丸儲け、競争相手を蹴散らす手段を取り上げられる んで、メーカーはぶーーーーーいんぐだな。裏で天下り受け入れとか献金とか必死等々。

gaucheでグラフ

で、グラフ表示はどうする? 入力系の目処がついたので今度は出力の見える化。

そりゃ、グラフ書き職人に任せちゃうのが、正しいunix屋さん ってもんです。racketみたいに抱え込んで太るのは御免って訳。 以前ちょいとネットから拾っておいたコードを見ると、謎のマークが有った。

(define (plot d)
  (display "plot '-' w l\n")
  (force d)
  (display "end\n"))

(define (calc n)
  (dotimes (i n #f)
    (display (format "~a ~a\n" i (sqrt i)))))

(plot (delay (calc 100)))
;;sakae@uB:~/src$ gosh plot.scm | gnuplot -persist

何これって 調べてみたら、特別なファイル名 (special-filenames) だそうだ。ついでに、wはwithの、lはlineplotの省略系らしい。丁寧にwithとかやると、 職人さんが臍を曲げてしまうので、注意。

gnuplotとはパイプで接続して、データを送りこんでいる。この方法は、データ受け渡しの ファイルを作らないので、すっきりしてるんだけど(不測の事態でファイルが残る心配無し) 、残念ながら using 1:3 とかやって、複数のグラフを一度に書けない。gnuplotが新しくなると(?) 名前付きインラインデータ (inline data) が使えるらしいけど、おいらの所では、エラーになってしまった。(gplot 4.6.5) 5系 からのサポートになるんかな。

そこで、少し冗長になるけど、インラインデータの区切りを空行で行うようにした。 上記のcalcなら

(define (calc n)
  (dotimes (i n #f)
    (display (format "~a ~a\n" i (sqrt i) )))
  (display "\n")
  (dotimes (i n #f)
    (display (format "~a ~a\n" i (log (+ 1 i))))))

最初に平方根の計算値を吐き出し、空行を置いてから対数の計算値を出力してる。plot関数の 方は、全くいじる必要が無いので、こういう仕様も有りかな。

後は、スクリプトの中にgnuplotを呼び出すパイプを格納すればいいな。えと、どうやって やったかな? 昔、同じような事をやったな。 そして、こんなのも出てきた。pngに落とすやつばっかだな。

 (use gauche.process)

 (define plot-command "
 set terminal png color
 set grid
 set ylabel \"Output \" 
 set yrange [0:20]
 set xlabel \"Date\"
 set xdata time
 set timefmt \"%Y%m%d%H%M%S\"
 plot \"data.txt\" usi 2:1 ti \"output\"  with impulses
 ")

 (display plot-command 
   (open-output-process-port "gnuplot > out.png" ))

これが参考になりそうなので、ちょっと実験

gosh> (use gauche.process)
#<undef>
gosh> (display "plot 'data.txt' u 1:2 w l, '' u 1:3 w l\n"
          (open-output-process-port "gnuplot -p"))
#<undef>
gosh> (call-with-output-process "gnuplot -p" (lambda (out)
         (display "plot 'data.txt'\n" out)))
#<undef>

ここまでは、ちゃんとgnuplot画面が出てきた。そして今回の本命、、、

(with-output-to-process "gnuplot -p"
  (lambda ()
    (display "plot 'data.txt'\n")))

これも大丈夫。今度は

(with-output-to-process "gnuplot -p"
  (lambda ()
    (plot (delay(calc 50)))))

を、スクリプトとして実行すると、何もplotされずに終了。はてはて、delayとかが悪さ してるんかな?

よく考えたら、goshの起動の仕方が悪かった。

[sakae@manjaro ~]$ gosh -l ./plot.scm

としてやらないと、goshが終了しちゃうものだから、起動されたgnuplotも殺されちゃうのね。 だから、表示されないように見えたとな。スクリプトを実行してからreplに入って もらえば、生きながらえるとな。ちょっとした盲点でしたな。

マクロだよ

グラフを書かせる度にwith... うんたらと書かせられるなんてウンザリ。そこでマクロに 登場して貰おう。

gaucheのマクロ と言うかSchemeのマクロには2大潮流がある。一つはSchemeの優位性を自慢するために、 世界のSchemerが知恵を絞った健全なマクロ。もう一つはコモンリスプから脈々と受け継がれて いる伝統のマクロ。

健全なマクロは、変数名の衝突を未然に防ぐ対策が施されたもの。車の衝突防止機能と 一緒だな。車と違ってリコールとかないのかね。設計の苦労の一端が、ビューテフル・コード って本に解説されてる。何度か読んだけど、難しくて途中で挫折してんだよな。もう一度 読んでみるか。

一方、伝統マクロは、変数の衝突を逆手に取った技が開発されてて、伝道師ポール・グレアムが、 『On Lisp』なんて惑う書を配布してたりする。Gauche本のマクロの章には、両方のマクロの 解説が出てたから、再読してみれ。

前書きが長くなったけど、伝統マクロにしてみる。delay/forceは無しね。

(define-macro (gp form)
  `(with-output-to-process "gnuplot -p"
     (lambda ()
       (display "plot '-' w l\n")
       ,form
       (display "end\n"))))

リコールされない事を祈りつつ、、ですな。 使い方は、

gosh> (gp (calc 200))
#<undef>

たったこれだけ。省タイプに役立っています。

これが、どう展開されるかは、

gosh> (pretty-print (macroexpand-1 '(gp (calc 123))))
(with-output-to-process
  "gnuplot -p"
  (lambda ()
    (display "plot '-' w l
")
    (calc 123)
    (display "end
")))

綺麗に清書しようとしたら、文字列内も綺麗になっちゃったぞ。抑止方法って有るの かしらん。/usr/shere/slibをミロ。

おまけ

gnuplotで開かれた窓にグラフの結果保存メニューが有った。ふむふむPDFに落とせるか。 さすがデフォになったな。

そしてもう一つは、SVGフォーマットでも保存出来るとな。このフォーマットって不遇の ものらしい。 いまさら聞けないSVG、なぜ知られていないのか? とか ベクター形式のグラフィックを扱うSVGの基本 が出てきた。どこの世にも覇権争いが。

ファイルはテキスト形式なんで、読もうと思えば読めます。XMLやだやだですが。 ブラウザの画面にファイルをドロップすれば、表示されます。見るにはいいかも。