gaucheでも(2)

いわゆる海外物の小説は敬遠してるんだ。だって登場人物がアチャラ製の名前のものだから、 頭にすっと入ってこない、定着しないから。そんなオイラーが、翻訳物の棚をなにげに みていたら、気になる本が目に飛び込んできた。

『リトル・ブラザー』(早川書房)、出版社名から察しが付く人はSFかぶれの人認定。 おいらはリトルの反語である、ビッグからの連想で引かれたね。未来は監視社会になるって 言う有名なSF『1984年』ってのが有ったはずだか。。。

パラパラしてみると、XboxだとかXネットだとか言うのが踊っていたよ。読んでみた。 時は、Windows Vistaの時代って、もう過去形だよな。2011年の出版でした。

サンプランシスコでテロによる橋の爆破事件が発生。たまたま近くに居た高校生が、 国家安全保障局にしょっぴかれる。そして厳しい尋問、要監視処分で放免。

自宅に戻ってみると、自分のパソコンにキーロガーが仕掛けられていた。社会は監視カメラの 増強。地下鉄やバスのSUICA相当から行動が監視され、怪しい行動をする人間は、容赦なく 尋問。怪しいかどうかは、ベイズ統計による検出ですってさ。そうか、スパムメールの検出の 極意を応用したんだな。

で、その高校生は、ちょっとばかりハッカーだった。盗聴されていないパソコンやネットを 使って、保証局に対抗しようと模索を始める。目を付けたのが、街で配れていたXbox。 勿論、Microsoftが自分の所のゲームを沢山やって貰えるように、Xboxをただで配って いたって設定。

こういうのをアメリカでは、かみそり刃商法と言うそうな。髭剃りのかみそり本体を ただで配って、刃でもうけようと、ジレットが始めたんで、この名前があるらしい。 同じようなパターン、おいらも知ってるぞと、それはプリンターとそのインクだ。 著者も同じ事を書いていた。

まだまだ有るぞ。0円携帯ね。ロックインさせちゃったら、後は楽だからねぇ。Xboxを配るってのも、 モデムに置き換えれば、昔はやった、怪しい服でたむろしてた某キャリァーにそっくりだな。

兎も角、どこにも転がっているXboxを土台にパラノイアLinuxを動かし、Python語で アプリを開発。ふむふむ、OpenBSDとかCommonLispとか通のものを出さなかったのね。

とか思って読み進めると、telnetを使っていきなりSMTPサーバーを叩いたり、DNSサーバーを 経由してビデオデータを取り込んだりって場面が出てきて、SFを楽しむって事より、そっち 方面に興味が行ってしまいましたよ。

でも、全体を通して、監視社会の恐怖がよく出ていました。この本とは別に、FBI秘録って いう分厚い本も読んだけど、FBIは盗聴の専門家として暴露されてましたねぇ。いやな世界に 向かっている事。

読書メーター 6冊 / 1984頁 / 13100円

icewm

前回からgaucheでグラフを表示させるのをやってる。確認は、万次郎Linux上なんだけど、 こやつ起動に時間がかかる。

ウブの14.04版は起動時からWindowが上がらないようにしてるんで、すっと上がってくる。 必要な時、twmを上げるって寸法なんだけど、無闇やたらにグラフ画面を出すと、消すのが ちょっと面倒。

そこで、軽いウィンドウ・マネージャーを見繕ってみた。いろいろ試してみたけど、昔使ってた icewmで十分って結論になった。必要な時にstartxすればよい。

設定は、/usr/sheare/icewm を参考に必要最低限。

sakae@uB:~$ cat .xinitrc
#xterm -fn 8x16 -g 80x40-0+0 &
#exec twm
exec icewm

sakae@uB:~$ cat .icewm/preferences
WorkspaceNames=" 1 ", " 2 "
TaskBarAutoHide=1
TaskBarAtTop = 1

sakae@uB:~$ cat .icewm/toolbar
prog "Emacs" emacs emacs24 -r -g 80x40
prog "Terminal" xterm  xterm -fn 8x16 -g 80x40

仮想画面は沢山必要ないので2つ。タスクバーはfedoraに習って、上部に表示、自動で 隠れるように設定。タスクバーと言うかツールバー(名称が混乱してるぞ)には、emacsと xtermの起動ボタンだけを設定。たったこれだけでも、随分と楽になったぞ。

delay force

gnuplotを呼び出す時に、delayとforceが使われていた。gaucheの説明書では遅延評価って 説明されたけど、一体どういう動きをするの?

遅延評価 とか Rubyで遅延評価 - delay, force, lazy に説明が有った。Haskellは遅延評価がデフォになってる。そんなに良いものなのか。

gosh> (define aa (delay (+ 1 2)))
aa
gosh> aa
#<promise 0x87f4b30>
gosh> (force aa)
3
gosh> (force aa)
3

(+ 1 2)って計算を後で使いますよって、delayで包んでいる。後で使うにはそれを 呼び出せないといけないので、defineで名前を付けた。実行はforceで、指定した名前を 指定すれば良い。promiseって、あのプロミス、契約とか約束って事。

池井戸潤風に言うと、支払い遅延の一つの方法。約束手形とかクレジットとか、 飲み屋のつけも支払い遅延方法。 手形の振り出しがdelayだな。後で金払うから証文を出しとくよ。期日が来たら、銀行へ 行って証文と引き換えに金を貰ってね。これが、force。期日まで待てない場合は、 銀行にペナルティーを払えば、お金に換える事が出来たりする。これを手形(の)割引って 言うそうだ。経済用語、銀行の裏表は、元銀行員上がりの作家、池井戸さんの小説から 知ったよ。

delayは特殊形式ですって。

gosh> delay
#<syntax delay>
gosh> force
#<subr force>

普通の評価方法とは違うんだな。SICPを見たら説明が有った。実装例は、ケントさんのscheme 本に出ていた。そして、 遅延評価はマクロで、多値は継続とマクロで実装できるんですって とかもあるぞ。

この遅延評価が何故嬉しいか? それは、必要になった時点で評価を強制する事が出来るから。 普通の関数だと、呼び出し時に、引数が評価されちゃう。たとえその値をつかわなくてもだ。 でも、delay/forceだと、必要になった時に評価できる。ご利用は計画的にってやつだ。

ひょっとして、プロミスとかご利用は計画的にってキャッチを考えた人は、Schemeの素養が あったりしませんか?

OnLispにも、delay/forceの実装例が載ってた。delayは特殊形式。引数をその場で評価 する訳にはいかないから、マクロを使わないと実現出来ない。って事で、良いマクロの例に なってる。

伝統マクロで、素朴なやつをSICPから引いてみると

(define-macro (my-delay exp)
  `(lambda () ,exp))

(define (my-force exp)
  (exp))

なんか、凄い仕掛けがあるかと思うと、こんなに単純。詐欺にあった気分ですよ。 実用の版だと、一度forceしたものは、キャッシュしておくとか、評価は一度だけだよ とか、まっとうな機能が実装されてて複雑だけどね。 gaucheでは、どうなってるかと足を踏み入れた途端に、r5rsとr7rsの狭間で迷子になりました。

前回のplotにおけるdelay/forceの使い方を見ると、遅延評価と言うより、式の挿入と言うか 注入に主眼が置かれている。こういう使い方も出来るんだという事を、頭の隅に置いて おこう。

もくもくとpythonから移植

clojure語をpython語に翻訳した。そして今python語をscheme語に翻訳中。

schemeでのデータ表現はリストなんだ。一つ困った事が起こりそう。pythonの時は、配列の 尻尾からn個のデータを拾ってくるのに、配列のスライスを使った。リストでこれをやるのは、 うんざりしそう。

で、我らがshiroさんが、素敵な機能を実現してくれていないか、マニュアルのリストの 項を眺めてみましたよ。

そしたら、頭からn個を取ってくるtakeと尻尾からn個を取ってくるtake-rightなんてのが 提供されてるのね。

takeが用意出来たら、後はtake-rightを作るのは簡単そうだな。リストの長さからnを 引いたものをdropすれば良い。take/dropは、取るか棄てるかの違いしかないから差は 誤差のうち。

もう一つ、take-rightの実現方法を思い付いた。リストを反転。それのn個をtake、そして それをもう一度反転。効率悪そ。あの人は、どうやってる?

src/liblist.scmに有った。

(define (take-right lis k)
  (let loop ([p0 (list-tail lis k)] [p1 lis])
    (if (pair? p0) (loop (cdr p0) (cdr p1)) p1)))

どうやら、list-tailってのが元ねたになってるみたい。あちこちで使っていたぞ。

浮動小数点の整形出力はどうする? そりゃformatに任せます。取説がちょっと 抽象的すぎたんで、ネットに頼ってみた。そしたら、 Gauche練習帳 format関数完全マスター という、素敵なのが出てきた。そして、slibに逃げる楽チンコースを辿ってしまった。 我ながら堕落してるなあ。

後は、グラフだな。グラフの中に文字列をどう書く? これもネットに頼って、 グラフに文字を書く を参考にさせてもらいました。ありがたい事です。

マクロは難しい

最後は、マクロを使った、パイプもどきなんだけど、オリジナルはclojureのそれ、

(defmacro ->>
  ([x form] (if (seq? form)
              (with-meta `(~(first form) ~@(next form)  ~x) (meta form))
              (list form x)))
  ([x form & more] `(->> (->> ~x ~form) ~@more)))

これをぱっと見、伝統マクロでは実現できんだろうと諦め。Scheme流を試してみるか。 パターンによる場合分けをサポートしてるし、、、

(define-syntax th
  (syntax-rules ()
    [(_ x form)
       ((car form) (append (cdr form) x))]
    [(_ x form more ...)
       (th (th x form) more ...)]))

そんでもって、展開してみると

gosh> (macroexpand '(th (am BLD) (pp)))
((#<identifier user#car> (pp)) (#<identifier user#append> (#<identifier user#cdr> (pp)) (am BLD)))

実行してみると

gosh> (th (am BLD) (pp))
*** ERROR: wrong number of arguments for #<closure pretty-print> (required 1, got 0)
Stack Trace:
_______________________________________
  0  (pp)
    :

ppに引数が渡っていないって怒られた。マクロの展開が失敗してんだな。 展開を妄想してみると

((car (pp)) (append (cdr (pp)) (am BLD)))
(pp (append () (am BLD)))
(pp (am BLD))

と、なりそうなのだけど、これって希望的観測? そんな事より決定的にだめな事がある。 カンマアットで、括弧を取り払う機能が健全なマクロには無いんだった。

gaucheには、関数合成 .$ なんてのも あるけど、この場合には使えないし。。。 最大に譲歩して、Haskellから輸入された、関数適用を使う事にしよう。頭の中で パイプの出口から遡るかっこうでね。

gosh> ($ pp $ tl 3 $ utl 1308 $ am BLD)
((13082903 117 60 55)
 (13083003 117 65 59)
 (13083103 138 74 60))

そんなに深いパイプを使う訳ではないので、慣れれば快適だな。 このハスケルゆずりの$も、マクロで実現されるんで、マクロは書くもんじゃなくて、 有り難く使わせて貰うものって、事にしておきましょ!!

一晩寝たら、clojureでやってた複数パターンのマクロ、伝統マクロでも出来るじゃんと 閃いた。

(define-macro (th x form . more)
  (if (null? more)
      `(,(car form) ,@(cdr form) ,x)
      `(th (th ,x ,form) ,@more)))

可変長変数が空かどうかで場合分けすれば、解決する事を。後は写経するだけ。

gosh> (th (am BLD) (utl 1308) (tl 3) (pp))
((13082903 117 60 55)
 (13083003 117 65 59)
 (13083103 138 74 60))

でも、Haskell流は括弧を省略出来るし、どちらが便利なんだろう? 好きな方を使っとけ。

Code for gauche + gnuplot

;;;; bld for gauche
(use text.csv)
(use gauche.process)

(use slib)
(require 'format)
(require 'pretty-print)
(define pp pretty-print)

(define hi 1) (define low 2) (define pls 3)  ; index for rows

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

(define BLD (mycvsr "2013.csv"))              ; must be change file-name

;; below 2 func are sample of gnuplot I/F
(define (calc n)
  (dotimes (i n)
    (display (format "~a ~a\n" i (sqrt i))))
  (display "\n")
  (dotimes (i n)
    (display (format "~a ~a\n" i (log (+ 1 i))))))

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

(define (am ds)
  (filter (^v (< (mod (car v) 100) 12)) ds))

(define (pm ds)
  (filter (^v (>= (mod (car v) 100) 12)) ds))

(define (hd n ds)
  (take ds n))

(define (tl n ds)
  (take-right ds n))

(define (frm ym ds)
  (let ((lmt (* ym 10000)))
    (filter (^v (> (car v) lmt)) ds)))

(define (utl ym ds)
  (let ((lmt (+ (* ym 10000) 3124)))
    (filter (^v (< (car v) lmt)) ds)))

(define (w i ds)
  (map (^v (list-ref v i)) ds))

(define (mk-hlp ds)
  (cons (w hi ds) (cons (w low ds) (cons (w pls ds) '()))))

(define (ssf fn hlp)
  (string-join
   (map (^v (format "~6,1f" (apply fn v))) hlp)
   ""))

(define (mean . lis)
  (/. (fold + 0 lis) (length lis)))

(define (std . lis)
  (let* ([n (length lis)]
         [m (apply mean lis)]
         [s2 (map (^v (expt (- v m) 2)) lis)])
    (sqrt (/ (fold + 0.0 s2) n))))

(define (ss ds)
  (let ([hlp (mk-hlp ds)])
    (print (string-append "size: " (number->string (length ds))))
    (print (string-append "min:  " (ssf min hlp)))
    (print (string-append "mean: " (ssf mean hlp)))
    (print (string-append "max:  " (ssf max hlp)))
    (print (string-append "std:  " (ssf std hlp)))))

(define (lg ds)
  (define (aline lis n)
    (dotimes (i n)
      (display (format "~a ~a\n" i (list-ref lis i))))
    (display "\n"))
  (let* ([n (length ds)]
         [hlp (mk-hlp ds)]
         [sm (string-append "mean: " (ssf mean hlp))]
         [ss (string-append "std:  " (ssf std hlp))])
    (with-output-to-process "gnuplot -p"
      (lambda ()
        (print #"set label 1 at first 10,90 '~sm' font 'monospace'")
        (print #"set label 2 at first 10,85 '~ss' font 'monospace'")
        (print "plot '-' w l")
        (for-each (^v (aline v n)) hlp)
        (print "end")))))

;; example:  (th (frm 1310 BLD) (am) (hd 3) (pp))
;; or use gauche native func ($ pp $ hd 3 $ am $ frm 1310 BLD)
(define-macro (th x form . more)
  (if (null? more)
      `(,(car form) ,@(cdr form) ,x)
      `(th (th ,x ,form) ,@more)))

マニュアルを見てて、#" を使うと、チルダ文字と組み合わせて、変数(関数の値も)を その場展開出来る事を知った。これでわざわざformatってしなくても良いのね。

グラフを書く手続き中で、list-refを安易に使っちゃったけどSchemerとしてはどうよ。 お前、listを配列と思ってるんか?

グラフ中に埋め込む文字列をset labelしてるけど、この時点でグラフデータははまだ 届いていない。それにも関わらず、文字列の表示位置まで指定してる。表示位置は、 X軸は、データの10個目、Y軸は、左軸(first)基準で90のあたり。(右Y軸で指定する場合、 二番目の軸って事でsecondを指定するんだった)

これって、遅延評価(描画)だよね。上でやったdelay相当。forceは、plotでグラフ表示が 終わって、表示場所が決まってからだな。って事で、gnuplotもschemeの影響をプチ受けて います。

今回gnuplotの解説を眺めてみたけど、データの統計を計算出来ますとか、平準化出来ますとか、 段々とRっぽくなってきたな。オイラーとしてはグラフ職人に徹して、小気味よく動いて 欲しいぞ。データの加工なんて、schemeとかrubyとかに任せてしまえばいいんですから。

おまけ

秋だから真きっき、gauche版の現代風Webサーバー

https://github.com/shirok/Gauche-makiki