Gauche を update.

昨日の、

Compile Error: [internal error] stray local variable:

の件で shiro さんからコメントを頂きました。ありがとうございます。
0.8.6 以前では、コンパイラの最適化ルーチンのバグで、このエラーが発生することがあるとのことです。
http://www.shiro.dreamhost.com/scheme/gauche/index-j.html を拝見したところ、

  • ローカル関数のインライン展開が重なった場合にInternal compiler error が出るバグがコンパイラにありました (Thanks to Jun Inoue for tracking this down)。他にもいくつかInternal compiler errorとなり得るバグを修正しています。

これらしいですね。
ちゃんと確認して update していれば、自分で解決できたかもしれないというのに、shiro さん直々にコメント頂くことになり申し訳ありませんでした。

早速、update して確認致しました。

(use gauche.config)
#<undef>
(gauche-version)
"0.8.7"

となった状態で、

(let ()
  (define (make-counter init incremental)
    (let ((x init)
          (y incremental))
      (lambda () (set! x (+ x y)))))

  (define c1 (make-counter 0 1))
  (define c2 (make-counter 0 2))

  (let loop ((n 10))
    (if (< n 0)
        #f
        (begin
          (if (even? n)
              (print "(c1) => " (c1))
              (print "(c2) => " (c2)))
          (loop (- n 1))))))
(c1) => 1
(c2) => 2
(c1) => 2
(c2) => 4
(c1) => 3
(c2) => 6
(c1) => 4
(c2) => 8
(c1) => 5
(c2) => 10
(c1) => 6
#f

となり、意図した通りの動作をしてくれました。

一応、昨日作っておいたスクリプト

$ cat closure-5.scm
#!/usr/bin/env gosh
;
(define (main args)
  (define (make-counter init incremental)
    (let ((x init)
          (y incremental))
      (lambda () (set! x (+ x y)))))

  (define c1 (make-counter 0 1))
  (define c2 (make-counter 0 2))

  (let loop ((n 10))
    (if (< n 0)
        #f
        (begin
          (if (even? n)
              (print "(c1) => " (c1))
              (print "(c2) => " (c2)))
          (loop (- n 1))))))

;; Ends here.

も実行してみました。
インタラクティブに動かしたものと内容は同じですが、`main' 手続きで包んでいます。

$ ./closure-5.scm
(c1) => 1
(c2) => 2
(c1) => 2
(c2) => 4
(c1) => 3
(c2) => 6
(c1) => 4
(c2) => 8
(c1) => 5
(c2) => 10
(c1) => 6

こちらも問題無し、意図した通りに動作してくれている様です。

斯くして問題は無事解決致しました。
しかし、ML や BTS などに報告した訳でもないのに開発者の方から直々にコメントを頂けて問題を解決できてしまうとは、何とも幸せな時代になったものですね。これまでにも何度もコメントを頂いていて、あっと言う間に疑問が氷解していますし、誤解についてもすぐに訂正して頂けたりで、本当にありがたい事です。
それも shiro さんが常にアンテナを張っておられて、訳の判らん者が勝手なことを書いている blog にまで目を通しておられるからで、利用者としては嬉しい限りです。ありがとうございました。

良く判らない動作です。

Gaucheクロージャを作っていて、ちょっと良く判らない動作に遭遇しています。

先ず、以下のコードをトップレベルで評価すると意図した通りに動作してくれます。

(1) トップレベルで評価すると意図した通りに動作する。

(define (make-counter init incremental)
  (let ((x init)
        (y incremental))
    (lambda () (set! x (+ x y)))))
=> make-counter

(define c1 (make-counter 0 1))
=> c1
(define c2 (make-counter 0 2))
=> c2

(let loop ((n 10))
  (if (< n 0)
      #f
       (begin
         (if (even? n)
             (print "(c1) => " (c1))
             (print "(c2) => " (c2)))
         (loop (- n 1)))))
=> (c1) => 1
(c2) => 2
(c1) => 2
(c2) => 4
(c1) => 3
(c2) => 6
(c1) => 4
(c2) => 8
(c1) => 5
(c2) => 10
(c1) => 6
#f

しかし、これをそのまま let で包んでローカルスコープで評価すると、意図した動作をしてくれません。

(2) local scope にすると error となる。

(let ()
  (define (make-counter init incremental)
    (let ((x init)
          (y incremental))
      (lambda () (set! x (+ x y)))))

  (define c1 (make-counter 0 1))
  (define c2 (make-counter 0 2))

  (let loop ((n 10))
    (if (< n 0)
        #f
        (begin
          (if (even? n)
              (print "(c1) => " (c1))
              (print "(c2) => " (c2)))
          (loop (- n 1))))))
*** ERROR: Compile Error: [internal error] stray local variable:
"(input string port)":1:(let () (define (make-counter init i ...

Stack Trace:
_______________________________________
  0  (report-error e)
        At line 167 of "/usr/local/share/gauche/site/lib/igauche.scm"
  1  (call/cc (lambda (break-inner-read-eval-print-loop) (define (reade ...
        [unknown location]

ここで、二つ目のクロージャ生成を、単なる手続きに変えると、その様に動作します。

(3) 二つ目のクロージャ生成を止めて、単なる手続きに変えると動作する。

(let ()
  (define (make-counter init incremental)
    (let ((x init)
          (y incremental))
      (lambda () (set! x (+ x y)))))

  (define c1 (make-counter 0 1))
  (define c2 (lambda () #f))

  (let loop ((n 10))
    (if (< n 0)
        #f
        (begin
          (if (even? n)
              (print "(c1) => " (c1))
              (print "(c2) => " (c2)))
          (loop (- n 1))))))
=> (c1) => 1
(c2) => #f
(c1) => 2
(c2) => #f
(c1) => 3
(c2) => #f
(c1) => 4
(c2) => #f
(c1) => 5
(c2) => #f
(c1) => 6
#f

何故なんでしょう?
何か変なことをしてしまっているんでしょうけど……

オンラインドキュメントの参照。

■ Schemeの勉強、10日目

で、結城さんが、

  • perldocやriやrefeのような、コマンドラインベースで読めるドキュメントツールがあるとよいなあ。

と仰っています。
これについて shiro さんが、gauche.interactive の `info' を紹介されています。

やはりオンラインドキュメント環境は重要ですよね。それも、より簡単に、素早く対象にアクセスできる事が望ましい訳です。とにかく、できるだけ思考の流れを妨げたくありませんから。

結城さんが仰る様に、コマンドラインツールとして存在していれば、万人に便利なのは間違い無いと思うのですが、gosh をインタラクティブに起動できる環境さえあれば (そして、Gauche ユーザであればその環境があると思うのですが) gauche.interactive の info で十分に活用できるとは思います。
shiro さんのコメントにある様に、Info ページを表示してしまうので冗長というのはその通りかとは思いますが。
また、gauche.interactive には `apropos', `describe' なんかもあって便利ですし。

ただ、表示される内容は英語のものなんですよねえ。Gauche の make 時に何とかすれば、日本語で表示することも可能かとは思うのですが……
;; 英語がダメってのは技術者として致命的という意見もあるかとは存じますが……

私は、以前にこのエントリで紹介した (誰に?) `mode-info' で Gauche の Info が引ける様にしています。この mode-info というツールは本当に便利で、私の環境では、Perl, Ruby, Emacs Lisp, Gauche, libc の Info が一発で引ける様になっています。(libc だけ英語だ……)
mode-info を使っても、gauche.interactive の info と同様に、Info のノード単位に表示されてしまうのですが、その結果は Emacs のバッファとしてポップアップされ、対象の手続きやマクロの記述位置をポイントしてくれるので、気になることは無いと思います。
インデックスの作成時に、入力となる Info を日本語版にしてやれば、日本語の Info を引くことができますので、私にはとても重宝しているのです。

但し、mode-info では、対象とする Info ごとに説明文を抽出するモジュールを書き起こさなければならないということで、現在、対象とされている Info 以外については、モジュールの作成が必須となってしまいます。
幸い、Gauche の Info は既に対応されており、モジュールが存在していますので、そのまま行けました。

使い方は簡単で、基本的には mode-info のインストール時に make index として、対象の Info からインデックスを生成してやれば良いです。
デフォルトでは Gauche の Info は、実行するバッファが scheme の mode になっているときに引ける様になります。
私は、gauche-mode を利用しているため、`mode-info-class-alist' にモード名を追加してやる必要がありました。

インストールできたら、Gaucheスクリプトを開いているバッファで、引きたい単語にポイントを合わせた状態で、

  M-x mode-info-describe-{function,variable}
  (通常は C-h f が置き換えられる様です)

してやれば、該当する Info のノードが表示されたバッファがポップアップされます。

結城さんが Emacsen を利用されているという話はこれまで耳にしたことが無い気がします (私が知らないだけの可能性高し) し、Emacsen の利用を前提にした話は不毛という気もしますが、まあ、こんな環境もありますと言うことで。;-)
ひょっとしたら Emacs を利用していて、使ってみようと思う方もいらっしゃるかもしれませんよね。

多値を返す関数での再帰。

先日のエントリで多値を返す関数について考えてみましたが、`call-with-values' に渡せる手続きに引数が渡せないことから、再帰的に利用することができなさそうに感じていました。

しかし、よくよく考えれば、そんなことは無いですね。

二つの引数 a, b をとって、a > b なら、二つの値を近付けて行き、最も近くなる値、つまり、中間地点の近似値を得る手続きを考えてみます。
;; こういう意味の無いものしか用例として浮かばないところが、多値を扱う関数の用途を理解していないことを如実に現わしていますね。(特に再帰で)

(define (midvals a b)
  (if (<= a b)
      (values a b)
      (call-with-values (lambda () (values (- a 1) (+ b 1))) midvals)))
=> midvals
(midvals 100 2)
=> 51, 51
(midvals 2001 2)
=> 1001, 1002
(midvals 65536 128)
=> 32832, 32832

基本的には第一引数の方が大きいことを仮定していますので、最初から第一引数の方が小さいときには何もしないで終わってしまいますけど。

こうしてみると、それほど面倒な訳ではないですが、多値を返すことができるというメリットもまた、それほどには感じられなく思ってしまいます。
この戻り値を利用するときも、

(call-with-values (lambda () (midvals 65536 128)) cons)
=> (32832 . 32832)
(receive all (midvals 65536 128) all)
=> (32832 32832)
(let-values (((a b) (midvals 65535 128)))
  (print "a:" a ", b:" b))
a:32831, b:32832
=> #<undef>

とか、

(define-values (a b) (midvals 65535 128))
=> #<undef>
a
=> 32831
b
=> 32832

などとしなければならないんですよね。
どうも、特殊なことをしないとならないというのは Scheme らしくないと言いますか。

良く知りもしないで偉そうなこと書いてますね。どなたか識者の方に教えて頂きたいところです。(他力本願モードですみません)

今のところの感触ですと、Perl の、

引数群はリストとして渡され、戻り値を複数指定したときもリストとして返される。後は好きにして。

というモデルの方が合理的な気がしてしまいます。

多値を返す関数

以前のエントリで、Scheme で多値を返す関数について shiro さんに教えて頂いたのですが……

(define (dvals a b)
  (values a b))
=> dvals

という余り意味の無い、多値を返す関数を定義して評価すると、

(dvals 1 2)
=> 1, 2

となります。
ここで `dvals' は、二つの引数を取り、それぞれをそのまま返す関数なので、そのまま再帰的に利用できるかと思いきや……

(dvals (dvals 1 2))
*** ERROR: wrong number of arguments for #<closure dvals> (required 2, got 1)
Stack Trace:
_______________________________________
  0  (report-error e)
        At line 167 of "/usr/local/share/gauche/site/lib/igauche.scm"
  1  (call/cc (lambda (break-inner-read-eval-print-loop) (define (reade ...
        [unknown location]

と怒られてしまいます。
ああ、ここで shiro さんからご指摘頂いた、`call-with-values' を使うんですね。

(call-with-values (dvals 1 2) dvals)
*** ERROR: invalid application: (1)
Stack Trace:
_______________________________________
  0  (report-error e)
        At line 167 of "/usr/local/share/gauche/site/lib/igauche.scm"
  1  (call/cc (lambda (break-inner-read-eval-print-loop) (define (reade ...
        [unknown location]

そうですね、Gauche の Info には、

6.15.4 多値

                    • -

(snip)
-- 機能: call-with-values producer consumer
[R5RS] 手続きPRODUCERを引数無しで呼びます。そして、それが返した値
を引数としてCONSUMERを呼びます。CONSUMERが返す値を 返します。
(call-with-values (lambda () (values 1 2)) cons)
=> (1 . 2)

とありますから、

(call-with-values (lambda () (dvals 1 2)) dvals)
=> 1, 2

とするしかないのでしょうかね。
むう、これしかないすると再帰的には利用できないということになるのでしょうか。

高階関数として適用する方向に持って行かないとダメなのでしょうかね。
今日は時間が無くなってしまいましたのでまた今度。

Emacsリング

おおっ、id:higepon さんが Emacs リングに参加されていますね。
お待ちしておりました。;-)

このリング、私しか参加してなくて随分寂しかったんですよね。
はてなって Emacs 人口がそんなに少ないんでしょうか。確かに少ないと感じることは多いですけど。
しかも私は (S)XEmacs ですし……

で、ず〜っと一人リングだった訳ですが、私が管理者な訳ではありませんです。

R5RS 邦訳の Info を。

これまで、アルゴリズム言語 Scheme に関する第五改訂報告書を参照していたんですが、何のことはない、同じ site の http://www.sci.toyama-u.ac.jp/~iwao/Scheme/scheme.html に、日本語版の info が置かれていることに気付いたので、Info で読める様にしてみました。(翻訳者の犬飼氏、並びに上記公開サイトの木村氏に感謝致します)

しかし、これがまた XEmacs の Info では読めません。(Gauche-refj のときと同様)
以前の様に offset を書き換えるのは面倒 (あのとき、ちゃんとトランスレータとして作っておけば良かったのに……) なのと、抜本的な解決策を考えないといけないと感じているため、場当り的に対処したくありませんでした。

しかし、どうやら FSF Emacs と XEmacs の Info には相違がある様で、本当に抜本的に解決するにはどうにも時間が足りません。
上記サイトには日本語版の TeXinfo ファイルも置いてあるので、今回はそれを使って info を作り直すことにしました。

download した texi を XEmacs で開き、

  M-x texinfo-format-buffer

としたのですが、途中で、

  extraneous text at end of command line.

と error となって停まってしまいます。

  M-x texi2info

でも同様で、停まる箇所は 2956 行目の、

@noindent である。

と `@noindent' というコマンドがある箇所。
GNU Texinfo の info の 10.12 @noindent: 字下げを行なわないによると、

ほとんどの囲まれている部分で,コマンドの直後のスペースは無視されないので,@noindentを単独行に書いた方が良いでしょう.囲まれている部分以外では,行の最初に使用し,それにテキストを続けてもかまいません.

とあります。ざっと見る限り、他のコマンドで囲まれている様に見えないのですが、XEmacs 側に問題があってこの辺が正しく認識されていないのかもしれません。
`@noindent' を単独で書いた方が良さそうなので、改行を挿入して行を分けておき、再度、

  M-x texinfo-format-buffer

すると、今度は正常に終了しました。

出来た info を保存して `Info-additional-search-directory-list' に設定されているディレクトリに置くことで、無事 XEmacs の Info で読める様になりました。
しかし、抜本的解決にはほど遠いですね……

SICP リングに参加。

ようやくエントリが実際の動きに追い付いてきましたが、昨日、id:higepon さんが SICP リングを作られたことを知り、早速参加させて頂きました。
ちょっとトラブルがあったせいで慌ててしまい、登録が上手くできてなかったのを、先程やり直させて頂き、id:higepon さんの仰る様に登録できたと思います。

地道にゆっくり学んで行こうと思ってますので、他の参加者の皆さんの様なペースでは出来そうにありませんが、色々と学ばせて頂こうと思います。

魔術師本が旬なのでしょうか。

で、時間無いながらも、地道に Gauche を使った Scheme の簡単な学習と、RSS Feeds の未読消化に努めていると、最近はどうやら魔術師本 (SICP) が旬な様ですね。

計算機プログラムの構造と解釈

計算機プログラムの構造と解釈

私が SICP を知ったのは、もう数年前ではあるのですが、いつ頃だったのかは余り覚えていません。多分、http://0xcc.net/misc/sicp.htmlか、もうひとつの Scheme 入門(Scheme) (Lisp)、或いは http://www.stdio.h.kyoto-u.ac.jp/~hioki/gairon-enshuu/SchemeNotes/scheme.html 辺りを読んで知ったのだと思うのですが。
当時、実際に本屋で書籍を手に取り、拾い読みしてみたところ、概観として辿ることは可能なんですが、根本的な数学的素養が余りに不足しているため、これは到底理解不能と諦めたものでした。

兎に角、問題が解けない。と言いますか、問題が何を言ってるのかすら判らなかったものです。いや今でも判らないですけど……

しかし、id:higepon さんのSICP関数型言語の勉強に「計算機プログラムの構造と解釈」を読もう - higepon blogや、id:naoya_t さんのhttp://sicp.naochan.com/memo.plなどを拝見しますと、皆さんはサクサクと問題を解かれていますねえ。尊敬致します。

しかし、これまで書いてきた自分の Emacs Lisp の拙いコードを見るにつけ、この様な素養を身に付けなければいかん (もう遅いという話もありますが) のだ、と自分を鼓舞するつもりで、一週間程前に魔術師本の購入に至りました。

で、少しずつ読み進めてはいますが、やはり私には難しいですね。
皆さんの様にサクサクとは行きませんが、ゾウガメの歩みで進めて行きたいと思います。

遅々として進まず。

先日のエントリ (d:id:teny:20060416) で、Gauche という Scheme の実装をインストールしていた (素晴しい処理系を公開しておられる shiro さんに感謝致します) と書きましたが、このインストールを実施したのが 2月の半ばでした。
もう 5月に入ったというのに、その間、殆ど放置状態。
プライベートで色々と問題があって時間が無いのは確か (仕事すらできなかったり……) ですが、少しは何とかしたいものです。

そもそもは多値関連の話やクロージャの実装確認など、Emacs Lisp ではできないことを Common LispScheme の実装を使って実際に確認してみたいという欲求があったところに、私の多値関数の議論というエントリに shiro さんからコメント頂いたりということがあり、それなら Gauche をインストールしてみよう、と思い立ったところが始まりでした。

しかし、未だに当初の目的には辿り着けず。
まあ、ゾウガメで行きますか……