Scheme:初心者の質問箱:log00

Scheme:初心者の質問箱:log00


メール送信の方法

Gauche:メールは読みましたが、現在メール送信の標準的なAPIは有るのでしょうか。 無ければ、sendmailを使うのが今のところ無難でしょうか。

R5RSマクロ中のdefineについて

マクロ中でdefineを使用するとマクロ使用の周りのスコープの束縛に作用する処理系と、マクロ自体でスコープが閉じる処理系があるようなのですが、どちらが正しい挙動なのでしょうか?

具体的には

(define-syntax foo (syntax-rules () ((foo) (define x 100))))

というマクロを定義して、トップレベルで

(foo)
(display x)
(newline)

した場合、アンバウンドエラーになる処理系(scheme48,Gambit-C,mzscheme,sisc)と100が表示される処理系(Gauche,scm,guile)があります。

このマクロfooはxに対する束縛を挿入しますが、このxは実質的にリネームされると考えると前者が正しいように思えます。しかし、それならばマクロ中のdefineは常に内部定義という事になり、R5RSがマクロの項でトップレベルのdefineに言及している理由がわからなくなります。

Shiro(2007/05/21 06:44:49 PDT): 「内部定義」になるのではなく、どちらの解釈でも 挿入されるdefineはトップレベルだけれど、xがグローバルにリネームされるか どうかという違いである、ってことじゃないでしょうか。 (R5RSのp14冒頭の "may or may not introduce binding" は文脈から、「トップレベルの bindingかどうか」ではなく、「hygienityが考慮すべきbinding」を問題にしている ように私には思えます。)

定義される変数をマクロ呼び出し時に与えてやれば曖昧さは回避できます。

(define-syntax foo (syntax-rules () ((foo var) (define var 100))))

(foo x)
x  ;; => 100

Scheme48系統の方式の場合、マクロ展開時に挿入される変数が外からは実質アクセス 出来ないことになりますが、そのマクロ展開時に同時に挿入されるコードからは アクセスできるわけなんで、例えば次のようなコードでxを外部から隠蔽するのに 便利といえば便利ですね。

(define-syntax foo
  (syntax-rules ()
    [(foo proc) (begin (define x 100) (define (proc) x))]))

(foo bar)
(bar)  ;; => 100

質問者(2007/05/21 07:46:28 PDT): Scheme48系統の挙動について、挿入された変数にアクセスできないことから、マクロでスコープが閉じていると考えていたのですが、スコープ自体はGauche方式と同様、マクロ使用と変わらないんですね。Shiroさんの洞察が正しかった証明

((lambda ()
  (newline)
  (foo)))

マクロでスコープが閉じていれば問題ないはずですが、Scheme48系統でもすべてエラーになりました。 トップレベルのdefineはset!としての顔もありますから、必ず衝突回避をした方がいいとはいえないでしょうね。R5RSではdefineは(letrecに変換できる内部定義の方はともかく、トップレベルの方は)束縛コンストラクタであるとは書かれていないから、マクロの挿入する束縛にあたらない、という解釈になるのでしょうか。いかなる場合も衝突を許さず、必ず回避させるScheme48系統と、プログラマが望むであろう衝突は許すGauche系統といえますね。

ちなみにGauche系統も挙動が2種類あって、内部定義としての(define x 0)の後に(foo)を書くと、衝突回避をしてくれるGaucheと、duplicate bindingになるscm・guileに分かれます。

部分スプリット

Shiro(2007/05/06 00:37:25 PDT): 思いがけず盛り上がっているので別ページに移します→ Gauche:部分スプリット

なぜSchemeはstatic scopeで設計されているのでしょうか。

Scheme:マクロ:CommonLispとの比較にあるような多くの差異が、この選択から発生しているようで、大きな設計上の変更のように思えます。static scopeのどのような利点が、このような選択をさせたのだと思いますか?

Shiro(2007/02/12 20:04:50 PST): まあそういうことです。一番最初のSchemeの論文も、 "SCHEME is essentially a full-funarg Lisp" で始まっています。

もうすこしくだいて説明すると、それまでのLispにおけるlambda式とか 変数束縛の実装っていうのはわりと実装の都合からデザインされてきてたところが あったんですが、関数自体をファーストクラスオブジェクトとして扱いはじめると デザインの歪みが見えて来ていました(それがfunarg問題)。 一方、Scheme開発の動機となった研究において、lambda式をクロージャと みなせば、それは単なる関数の表記法ではなく、むしろ制御の流れやプログラムの 構造化の根本となる概念ではないかっていう発見があったわけです。 言語のひとつの機能としてlambda式があるのではなく、lambda式が一番下に あって残りの言語は全てそれをレゴブロックみたいに組み合わせれば作れるじゃないか ってことですね。レキシカルスコープはそこから自然に導かれた機能だと思います。

ナンバープレイス

ナンバープレイスの問題を解きたいのですが、要素を確実に減らす方法ではとけるのですが、 バックトラックがわかりません。ambを使わないバックトラックを教えてくだ さい 

Gaucheで実行中のコードを差し替えるには?

Lisp:よくある正解Lisp:Geometryで、Lispの最終兵器として挙げられる 実行中のプログラムに対するハックですが、Gaucheでのシンプルなサンプルは無いでしょうか?

Lisp を書くのによいキーボードは?

Emacs でメタ/コントロールキーを多用するのにキーボードのお勧めありますか? またノートPCではどうでしょうか。

「Schemeについてどう思うか」についてどう思うか?

◆◆◆ About Scheme ◆◆◆という文章があります。Schemeについての理解も未だ浅い上にCommon Lispについては全然知らないので、その両方を知っていてSchemeを好む方の声を聞きたいです。(2006/12/13 19:57:49 PST)

Shiro(2006/12/15 03:50:04 PST): 両方仕事で使い込んでいる身としては、黒田さんの話は もっともだと思いますよ。…って言うとSchemerから怨差の声が上がりそうですが、 ちょっと落ち着いて、黒田さんが「言っていること」と「言っていないこと」を よく考えてみましょう。

まず、いつ何のために言語を使うか、という視点が重要です。今すぐに、 今後数年以上のスパンで使う業務システムを組むなら、私はCLを使います。 もしGaucheを使ったとしたら、その業務システムのメンテを他人に振るのが (CLほどには)容易でないので、数年は面倒を見なくちゃならない。さらに Gaucheの側に非互換な仕様変更を持ち込んで業務システムを動かなくしちゃうと まずいんで、Gauche側の設計の自由度も減ってしまう。あまり良いことが ありません。なので、今Gaucheを使うとしたら、自分が継続して面倒を みられるプロジェクトが主となります。黒田さんが言っているのはそういうことです。

ただね、もう一つ考えなくちゃならないのは、10年あるいは20年後にどの 言語でプログラムしているかということです。この点について黒田さんは 特に何も言っていない。まあCLはその時も残っているでしょうが、 CLの仕様は策定時に考えられた機能は豊富に盛りこんでいるものの、 その後に広く使われるようになったコンポーネントはカバーしてない ですよね (例:ネットワークとか多言語化とか)。今後10年で、CLの 現在の仕様がカバーしていない部分というのは、今の仕様がカバー している部分よりずっと大きくなるでしょう。もちろんCLはCLでそれに 追従しようとするでしょうが、カバーすべき範囲の大きさを考えれば、 Schemeだって条件は大して変わらない。こっから先は多分私と 黒田さんで見解が分かれるところだと思いますが、私はCLの現在の 仕様が足を引っ張ることになるんじゃないかという気がします (例えば、stringがcharacterのmutableな配列である、という仕様)。

まあそれはそれとしても、今の仕様が10年後に十分であるとはとても 思えないわけで、10年後に「今のCLのように使える」言語を手にする ためには今からそれに向けて色々実験しておく必要があるわけですな。

マクロの話については色々面白いので、別ページにします →Scheme:マクロ:CommonLispとの比較


cycle関数

リストを受け取り、そのリストの要素を先頭から順に返す(全ての要素を返したら、再びリストの先頭を返す)関数を返す関数cycleを作成したいのですが、どのように書いたらよいですか。 --coze

こんな感じです。

gosh> (define c (cycle '(1 2 3))) ; 1 2 3 1 2 3 ... を返す関数
c
gosh> (c)
1
gosh> (c)
2
gosh> (c)
3
gosh> (c)
1
gosh> (c)
2

doとwhile

最近のEcmaScriptにはジェネレータという機能があります。それと同じような感覚で使えるものをSchemeで実装してみようとこんなものを書きました…


/proc/self/statが使えない場面で

とあるフリーサーバにGaucheをインストールしました。 そのサーバではLinuxが使われてます。 一般ユーザの権限で/procにアクセスできないようになっているせいで、GCがスタックの開始アドレス取得に失敗する模様です。(おそらくセキュリティ上の都合でしょう。) 何か簡単な迂回策があれば教えて下さい。GCライブラリの中の話なので本来ならそちらに質問すべき内容かとは思いますが、英語でちゃんと説明できる自身がないので…。


リストから組合せを作りたいのですが…

skr こんな感じです。

(combination-maker '(a b c))

((a b) (a c) (b c))
(define (combination-maker seq)
  (if (< (length seq) 2)
      '()
      (append (map (lambda (x) (list (car seq) x))
                          (cdr seq))
              (combination-maker (cdr seq)))))

試しに

(combination-maker '(a b c))

((a b) (a c) (b c))

でも

(combination-maker '((a b) (c d) (e f)))
((#0=(a b) #1=(c d)) (#0# #2=(e f)) (#1# #2#))

何が起きているのか、どなたか教えて頂けないでしょうか?

関数の呼び方

(Scheme:Procedureに移動しました。)

RedHat EnterpriseLinux ES release 4でthreadのテストに失敗する

この正規表現の問題をどうやって解いたらよいか教えてください。

(define str "<a href=\"./foo.png\">foo.png</a><br><a href=\"./index.html#30\">&gt;&gt;30</a><br>")
(regexp-replace-all #/<a href=[^>]*>(&gt\;&gt\;\d{1,4})<\/a>/
                    str
                    "\\1")

hash-tableのキーに関数を使うのはNGなんでしょうか?

やってみると、こんな感じになります。--ふじさわ

gosh> (define ht (make-hash-table 'equal?))
ht
gosh> (hash-table-put! ht cons 1)
*** ERROR: no applicable method for #<generic object-hash (0)> with arguments (\
#<subr cons>)
Stack Trace:
_______________________________________
  0  (hash-table-put! ht cons 1)
        At line 3 of "(stdin)"
  1  (hash-table-put! ht cons 1)
        At line 3 of "(stdin)"
  2  (hash-table-put! ht cons 1)
        At line 3 of "(stdin)"

Shiro(2006/04/15 15:48:30 PDT): ここでの問題はequal? ではなくて (手続き同士の equal? は eq? と同じです)、equal-hashtableを作るために必要なハッシュ関数が 手続きに対しては定義されていない、というエラーです。

equal-hashtableに使われるハッシュ関数hashには、ハッシュ値がプロセスイメージと 独立である(そのため永続テーブルに保存したり別プロセスに送ったりできる)という 属性もあるので、単純に オブジェクトのアドレス値からハッシュ値を計算するといった方法が使えないのです。

この「ハッシュ値がプロセスイメージと独立」という性質はCommon Lispの sxhash関数に倣ったものです。ただ、単一プロセス内でequal-hashtableを 使う限りにおいては不要な性質であり、選択の余地が無い今の実装は 使いづらいですね。ぼちぼちhashtable回りも改善してゆきたいと思います。

xml->sxmlの逆変換

ssax:xml->sxmlの逆変換はsxml:sxml->xmlですか? 次のようにすると"*TOP*"などがタグになってしまってうまくいかないのですが・・・。

 (tree->string (sxml:sxml->xml (ssax:xml->sxml (open-input-string "<a></a>") '())))
 ;; => "<*TOP*><a/></*TOP*>"

グローバルなマッチ

Gaucheの正規表現(#//)を便利に使わせて頂いております。 Perlで/gを指定したときのように、一行中にある複数のマッチした文字列を取り出す方法はございますでしょうか? es (2006/04/11 04:35:07 PDT)

(use srfi-42)
(define (rxmatch-all re str)
  (let loop ((r '()) (str str))
    (let1 m (rxmatch re str)
      (if m
        (loop (let1 nmatch (rxmatch-num-matches m)
                (if (= nmatch 1)
                  (cons (m 0) r)
                  (append! (reverse! (list-ec (: i 1 nmatch) (m i))) r)))
              (m 'after))
        (reverse! r)))))

(rxmatch-all #/./ "abc")      ; => ("a" "b" "c")
(rxmatch-all #/.(.)/ "abcde") ; => ("b" "d")

WindowsでGaucheからOpenGL

線型代数の本で一次変換のことを読んで興味がでてきました。そしてOpenGLが行列変換を使って種々の変換を行っているらしいことまではわかりました。まずは何を用意したらWindowsで動かせるでしょうか?MinGW版のGaucheで動かしたいです。 sasagawa

おしりの$

pa$ や map$ などの、おしりの$がへんてこりんに見えます。$をつけている理由はなんですか?

stream と副作用

とおる。(2006/03/14 09:58:28 PST): そもそもストリームの中で副作用のあることなんかするなというのもあるかもしれませんが……。

要素が取り出されるときにグローバルな状態が変わるようなストリームを作ります。 計算されたタイミングが分かるようにデバッグプリントを入れています。

gosh> (use util.stream)
#<undef>
gosh> (define *c* 0)
*c*
gosh> (define (s)
        (set! *c* #?=(+ 1 *c*))
        (stream-cons *c* (s)))
s

で、

gosh> (define ss (s))
#?="(stdin)":4:(+ 1 *c*)
#?-    1
ss

ここでは(当然)1 度だけ評価されますが、 stream-car すると

gosh> (stream-car ss)
#?="(stdin)":4:(+ 1 *c*)
#?-    2
#?="(stdin)":4:(+ 1 *c*)
#?-    3
2

なぜか (s) が 2 度呼ばれているようなんですが、 これはなぜでしょうか?

循環リストのlength

リファレンスのlengthのところをみると、

listが循環リストの場合、この関数は無限ループします。

となっていますが、

gosh> (define ls '(1 2))
ls
gosh> (set-cdr! ls ls)
#<undef>
gosh> ls
#0=(1 . #0#)
gosh> (length ls)
*** ERROR: proper list required, but got #0=(1 . #0#)
Stack Trace:
_______________________________________
  0  (length ls)
        At line 27 of "(stdin)"
  1  (length ls)
        At line 27 of "(stdin)"
gosh> (length+ ls)
#f

となります。#0=(1 . #0#) は真性リスト(proper list)だと思うのですが違うのでしょうか?

Windows/MinGWのGaucheを使っています。

NaN == 0?

(define (nan)
  (/ 0 0))

(display (= 0 (nan)))
(display (if (= 0 (nan))
             #t
             #f))

というコードをgoshで実行すると

#t#f

と表示されます。なぜこの二つは違う結果になるのでしょうか?またそもそもNaN==0はTrueなのでしょうか?

Shiro(2006/02/23 01:16:01 PST): NaNの扱いはまだ正式に決めていないので、このへんの 振るまいはまだ当てにしないで下さい。NaNの扱いはsrfi mailing listや comp.lang.schemeで何度も出ているのですが、すっきりした結論が出ていないのです。 だいたい議論はこんな感じです:

  1. (let ((a (/ 0 0))) (eq? a a)) はScheme的には#tになるのが自然。
  2. R5RSでは (eq? x y) ならば (eqv? x y) でなければならない。
  3. R5RSではx, yが数値で共にexact、もしくは共にinexactの場合、 (eqv? x y) ならば (= x y) でなければならない。
  4. しかし、IEEE754では NaN != NaN と規定されている。

案としては、確かこんなのが出てたと思います。

個人的には最後の案でいいかなという気がしてます。あ、(= NaN 0)は#fに なるべきだと思います。バグですね。

Lisp の間違いな機能

『ハッカーと画家』p.187 に

Python は Lisp ハッカーの多くが間違いだと思っている機能さえコピーしている。

とありますが、その機能とは何ですか? リスト内包表記があるんだから map や filter は 必要ないというのをぱっと思い浮かんだのですが、それとは違いますよね。

また Python 関係なしに、Lisp の間違い(だとハッカーが思っている)機能には、どのような ものがあるのですか?

読むテクニック

初心者向けに移動しました。yamasushi

even-stream

even style な stream で、stream を返す関数が全体を (delay (force ...)) で囲まなければならない理由が分かりません。 囲まなくても動くように見えますが、メモリリークを防ぐためとかの理由でしょうか。

eval

portの条件

Win9x系を使っているので、socket-input-port,socket-output-portが使えません。そこで、gauche.vportを使ってあらためてportとして使えるようにしようとしているのですが、エラーとなってしまいます。

*** ERROR: output port required, but got #<<network> 011360E0>

かなり根本的なところで勘違いしているような気がします。 出力ポート、入力ポートとして使えるオブジェクトはいったいどのような条件を備えていればよいのでしょうか? 実際に書いたコードはこんなのです。

#!/usr/bin/env gosh

(use gauche.net)
(use gauche.vport)
(use gauche.uvector)

(define-class <network>
  (<buffered-input-port> <buffered-output-port>)
  ((socket :init-keyword :socket)))

(define-method flush ((self <network>) a b)
  (socket-send (ref self 'socket) (u8vector->string a)))
 
(define-method fill ((self <network>) a)
  (let ((temp
         (string->u8vector (socket-recv (ref self 'socket 800))))
        (write-block temp)
        (length temp)
        )))

(define socket (make-client-socket 'inet "www.w3.org" 80))
(define network (make <network> :socket socket))
(display "GET / HTTP/1.0?r?n" network)

gauche.vportの内と外で継続を辿りたい

gauche.vportとcall/cc(とgauche.selectorとgauche.net)を使って、 空のsocket-input-portからreadしようとしたタイミングで、 そこまでの処理を一旦継続として保存してから中断し、 他の処理を行うようなソケットサーバを書いていたのですが、 gauche.vportの中で作った継続を外から辿ろうとすると、

 *** ERROR: a continuation is thrown outside of it's extent: 0x81842a0

と言われて、辿れませんでした。 (<buffered-input-port>と<virtual-input-port>の両方を試して、両方駄目でした。) 確かに、vportはCの部分を通過しているので、辿れないのも分かるのですが……。 やっぱり無理でしょうか。 -- nekoie(2005/11/25 03:25:02 PST)

不完全文字列

u8vectorが広く採用されるに従って、不完全文字列の存在意義が薄れているような気がしますが、長期的に不完全文字列は廃止方向ですか?

partcont.scm

Kahuaの部分継続はすごく便利なので、Kahua以外でも使いたいです。Gauche本体に取り込まれたりはしないのでしょうか? -- nekoie(2005/11/16 12:35:02 PST)

sys-unsetenv

システムの環境変数操作について。unsetenvやclearenvなど削除系の関数はマニュアルに載っていないのですが、実際はsys-unsetenv関数が使えるようです。マニュアルの漏れですかね? -- ふじさわ(2005/11/15 03:32:45 PST)

practical-scheme.net

ShiroさんのWiLiKiのページ履歴を差分表示するまで気付きませんでした。 いつの間に? [skr]

中身が1つだけのcollectionから中身を取り出す。

Collection cの中身が1つだけとわかっている場合、

(fold (lambda (x _) x) '() c)

なんてやって値を取り出しているんですが、他にもっとスマートな方法は無いでしょうか。 Sequenceなら(ref s 0)でおしまいなんでしょうけど。 ちなみに、dbi/dbdの結果から、値を取り出したいのです。katsujiro

集合的なリスト

SRFI:1 の lset って遅くないですか?集合演算ごとに線形時間か、それ以上かかりそうな……

equal?で比較するcase

caseシンタックスは、キーと一致するclauseを判定するのにeqv?を使いますが、eqv?ではなくequal?が必要なときはどうしたらいいでしょうか? もちろんcondを使って、自分でcondのtest clauseにequal?を並べることはできますが、(1)キーの生成に副作用がある場合にはプログラマが明示的にローカル変数を作らないといけない、(2)副作用がないとしても同じキーを並べるのが面倒、という理由で、equal?で比較するcaseがあると便利だと思うのですが。自分でシンタックスを追加してもいいとはいえ、そういうものが標準で用意されていないのは、なにかいいやり方が別にあるから?

エラーの起きた環境で REPL?

このWiLiKiでどなたかがボソっとおっしゃっていたような記憶がありますが。

Lispの処理系なんかによくある機能だと思うんですけど、エラーが起きたときの環境でreplのプロンプトがでたりするのって、Scheme(Gauche)ではどうなんでしょうか。

再現性の低いバグとかの解析とか、1回だけの処理で起きたエラーからとりあえず復帰させて処理を継続させるとか、いろいろ便利(?)(katsujiro)

cygwinでOpenGL

cygwinでGauche-glが使えるというのは、cygwinのX Window上という事でしょうか? Windowsそのもの(書き方が変で済みません)で使う方法があったら教えて頂けないでしょうか?

let* は控えるべき?

自分の書く関数は let* からはじまることがよくあります. これは避けた方がいいという話があるみたいですけど, 理由がわかりません. ええと, 「手続き型発想」をしてるつもりはさらさらなくて, 「f(x) = (1/Z) exp L, Z = √(2πσ^2), L = - D/(2σ), D = (x - μ)^2」 みたいに, ごちゃごちゃした式の部分部分へ「名前」をつけて 見やすくしてるだけのつもりです.

分かってしまえばなんてこと無いけど難しかったこと。

初心者向けに移動しました。yamasushi

R5RSについての疑問

くだらないことなんですが、zero?という述語、なぜ用意されているんでしょう? (= x 0)で十分だと思うんですけど。(eq? x '())と同じ意味のnull?が用意されているのに対応して、zero?が存在しているのかしら?

実プロダクトでのScheme活用事例

ここを読んでいて感じたのですが、他言語コミュニティと比較して、Schemer内の職業プログラマ比率は低めなのでしょうか。 いや、そんなことは無い、実プロダクトでもバリバリ使っているよ!という事例がありましたら、教えていただけるとうれしいです。学習の励みになります。

また逆に、「今はまだ実プロダクトでは厳しいかもしれない。でも、○○が整備されればイケるんじゃないかな?」といったご意見でもうれしいです。それはそれで、目標ができて励みになりますから。

Gaucheの拡張方法

http-get 問題の解決に関して Gauche に libcurl を組み込んだらいいんじゃないかと思ったのですが、その方法がわかりません。適当なドキュメントはありますでしょうか?

http-get

Gaucheでhttpクライアントを作ろうとしています。 このとき http-get 関数に :Accept-Encoding "gzip, compress" を 指定して圧縮されて受け取ったデータを展開するにはどうすればよいでしょうか?

javaみたいにgzipのストリームがあれば便利なんですが、ストリームどころか 素のgzipのユーティリティも見当たらないようです。 知ってる方お知恵をお貸し下さい、おながいします。

Scheme学習に関して

初心者向けに移動しました。(yamasushi)

Schemeが最強って本当?

どーゆう風に強いの?

Common Lisp?と比べると長所・短所は何でしょうか?

Shiro: これは長くなりそうな話題だな。議論がおもしろくなってきたら 別ページに移すかも。

私はSchemeラヴな人間なので、言語としてはSchemeの単純さと統一性を長所と考え、 Common Lisp?の仕様の大きさや過去をひきずったライブラリを短所と考えます。 一方、処理系の充実度、安定度ではCommon Lisp?の方に一日の長があり、 例えば業務でシリアスなアプリケーションを書く場合、私は今なら Common Lisp?を選びます。ネイティブコンパイルがちゃんと出来て、 それをかりかりチューニングできて、デバッガやプロファイラなどのツールが 充実してて、という環境は重要です。

かつてはCommon Lisp?の方がライブラリも豊富だし、とも思っていたのですが、 最近はSchemeSRFI等のおかげでライブラリが充実しつつあり、さらに 自分が欲しいと思ったライブラリをGaucheに実装している関係上、 「欲しいものにすぐ手が届く」感はGaucheでプログラミングしている時の方が 強くなっています。もっともGaucheのライブラリはまだ全然足りませんが。

関数型ってなに?

C言語の関数とは別物?

More ...