hira:質問箱
hiraの疑問とか悩みとか
Q
'()
Q&A
atmarkのwrite表現について
atmarkをwriteすると|@|となるのですが、なぜでしょう。 SXMLをwriteするときにイヤンな感じなので@のみにして欲しいと思ったのですが、きっと訳ありに違いないと思ったので質問してみました。
;;;実行例 (write-to-string '@) ;=> "|@|" (eq? '@ (read-from-string "@")) ;=> #t ;;;これが#tになって欲しい (let1 atstr "@" (equal? atstr (write-to-string (read-from-string atstr)))) ;=> #f
- R5RSでは"@"はシンボルの先頭文字になれませんね。だから"||"でクオートしているんでしょう。
- hira:なるほど。でもやっぱりどうしても@を@としてwriteしたい時はどうすればいいのだろう・・・(write.cを読んでいる)(symbol.cを読んでいる)・・・そうか。symbol.cのspecialの@の部分を0にすればよさげですね。このテーブルを動的に設定できると嬉しいのですが、Gauche的にどうでしょう?
- writeはreadと対になる、機械可読なS式を出力する手続きなので、人間にとっての見た目を気にする場合は、printを使ったほうがいいんじゃないでしょうか。シンボルの先頭文字として使える文字をカスタマイズ可能にしたとしても、それだけでは不正なシンボルを出力できるようになるだけなので、メリットはあまりないと思います。
- hira:ども。Shiroさん、最近匿名で書いてます?とりあえず、Gauche的にread/writeが対になっていない、というのがこのコードの主張です。
;;;これが#tになって欲しい (let1 atstr "@" (equal? atstr (write-to-string (read-from-string atstr)))) ;=> #f
で、シンボルの正/不正についてですが、R5RS的に不正とはいうものの、Gauche的には正(というかちゃんと読める)というのがこのコードの主張です。(eq? '@ (read-from-string "@")) ;=> #t
また、read-tableがいじれるようになった場合、シンボルの正/不正という議論は無意味なものになると思います(read-tableをどうするかについては保留のままでしたっけ?)。 結局、人間にとっての見た目を気にする場合はwrite一発では済まず、適当に整形してあげる必要があるのでSXML整形出力関数を書いているのですが、この@を特別扱いするのが面倒なので綺麗で簡単な回避策を探しているというのが現状です。メリットは(read-tableのカスタマイズ機能が無い現状では)@についてしか無い、という意見には賛成です。あと、printはdisplayなので、シンボル・キーワード・文字列などの区別がつかなくなってしまうので、今回の用件では使えません。 - えー、Shiroさんじゃないです。なのでポリシーはわからないんですが、Gaucheが"@"を識別子の先頭に許しているのは、ほかに害がないから現状そのようにしている、という程度に思ってます。数字で始まりアルファベットを含むものがシンボルと解釈されるのと同じように。なので、Gaucheでは"@"で始まるものは正しい識別子である、というのは違うと思います。read-tableはよく知りません。
- hira:どうもです。もともと私は「正か不正か」という事柄には何の価値も感じないので、その点については議論することが出来ないのでした。なので「Gauche的には正」と言ったのは失言でした。撤回します。
ただ、Gaucheは「実用的か否か」という価値観を重視していると思っているので、私が「実用的でない」と感じた部分については、今回のようにツッコミを入れている次第であります。どういった意図でこのような仕様になっているのかはShiroさんのみぞ知る、ということなので、ここであれこれ想像してもしかたないですね。
あと、Gaucheのこの動作については知りませんでした。ご教授、ありがとうございます。
(symbol? '123abc) ;=> #t (symbol? '123) ;=> #f (write-to-string '123abc) ;=> "|123abc|" (read-from-string "123abc") ;=> |123abc|
(あーん、長文書いてたらconfrictしちゃいました・・・Shiroさん飛び出てるし↓) - Shiro: 呼ばれて飛び出てじゃじゃじゃじゃーん。3点ほどあるのでネストします。
- えーと、現在の状態はある意味(write側の)手抜きです。 実際のコードはsymbol.cのsymbol_printですが。 曖昧さが生じる場合だけエスケープする、ということにすると、symbolのwriteの たびに結構重い処理を通さないとならなくなるため、現在は安全側に倒す簡略化した 処理を使っているためです。ただ、SXMLの|@|は私も気になっていたので、 特別扱いするかもしれません。
- あと、「文字列をreadしたものをwriteして同一文字列になる」ということは 普通のSchemeオブジェクトでも保障されていません (数値リテラルを考えてみて 下さい)。Lisp系言語の外部表現で普通重視されるのは、 「オブジェクトをwriteしてreadすると同値なオブジェクトになる」という性質と、 「オブジェクトを何回writeしても同じ表現になる」という性質です。
- それから、特定のケースだけをカスタマイズした何かを書きたい、という 要求はこれに限らずあるので、簡単にフックを書けるようにしておく 方が良さそうですね。現状だとwriteのトラバース部分を自前で 持たないとならないから。
'|'と'()'の組み合わせについて
acのときの1,2が#fになるのはこういうものなんでしょうか? abと同様、"a","c"が返ることを期待していたのですが、どうでしょう。
((#/(a)(b)|(a)(c)/ "ab") 0) ;=> "ab" ((#/(a)(b)|(a)(c)/ "ab") 1) ;=> "a" ((#/(a)(b)|(a)(c)/ "ab") 2) ;=> "b" ((#/(a)(b)|(a)(c)/ "ac") 0) ;=> "ac" ((#/(a)(b)|(a)(c)/ "ac") 1) ;=> #f ((#/(a)(b)|(a)(c)/ "ac") 2) ;=> #f
いきなり自己解決(ココに書くと速攻で自分のミスに気付くらしい。)↓
((#/(a)(b)|(a)(c)/ "ab") 0) ;=> "ab" ((#/(a)(b)|(a)(c)/ "ab") 1) ;=> "a" ((#/(a)(b)|(a)(c)/ "ab") 2) ;=> "b" ((#/(a)(b)|(a)(c)/ "ab") 3) ;=> #f ((#/(a)(b)|(a)(c)/ "ab") 4) ;=> #f ((#/(a)(b)|(a)(c)/ "ac") 0) ;=> "ac" ((#/(a)(b)|(a)(c)/ "ac") 1) ;=> #f ((#/(a)(b)|(a)(c)/ "ac") 2) ;=> #f ((#/(a)(b)|(a)(c)/ "ac") 3) ;=> "a" ((#/(a)(b)|(a)(c)/ "ac") 4) ;=> "c"
健全なマクロ内でのgensym?
gauche.parameterの実装を眺めていたのですが、tmp1aとtmp2aが不思議でなりません。 展開されたらtmp1a,tmp2aだらけになるように思ってしまいます。 実際はgensymされたように振る舞っているようなのですが、一般的にそういうものなのでしょうか。 このワザは使える!と思ったのですが、裏技だったらどうしようと不安になったので質問してみました。
- hira: これが答えか。またやってもうた。。。以下、R5RSより抜粋。
↓ここでlet変数のgensymを説明している。letの変数部分に来てやっとgensymらしい。テンプレートに現れているがパターン変数でも識別子... でもない識別子は,リテラル識別子として出力に挿入され る。もしリテラル識別子が自由識別子として挿入されるな らば,その場合,その識別子の束縛であって,かつ元々の syntax-rules のインスタンスが現れている箇所を含んでい るようなスコープの束縛が参照される。
もしリテラル識別子が束縛識別子として挿入されるならば,その場合,その識別 子は,自由識別子の不慮の捕獲を防ぐために実質的に改名される。
;;;lib/gauche/parameter.scm
(define-syntax %parameterize
(syntax-rules ()
((_ (param ...) (val ...) (tmp1 ...) (tmp2 ...) () body)
(let ((tmp1 val) ... (tmp2 #f) ...)
(dynamic-wind
(lambda () (set! tmp2 (param tmp1)) ...)
(lambda () . body)
(lambda () (param tmp2) ...))))
((_ (param ...) (val ...) (tmp1 ...) (tmp2 ...) ((p v) . more) body)
(%parameterize (param ... p) (val ... v) (tmp1 ... tmp1a) (tmp2 ... tmp2a) more body))
((_ params vals vars other body)
(syntax-error "malformed binding list for parameterize" other))
))
;;;sample
(%parameterize () () () () ((a b) (c d)) body)
;hira image
=> (%parameterize (a) (b) (tmp1a) (tmp2a) ((c d)) body)
=> (%parameterize (a c) (b d) (tmp1a tmp1a) (tmp2a tmp2a) () body)
=> (let ((tmp1a b) (tmp1a d) (tmp2a #f) (tmp2a #f))
(dynamic-wind
(lambda () (set! tmp2a (a tmp1a)) (set! tmp2a (c tmp1a)))
(lambda () body)
(lambda () (a tmp2a) (c tmp2a))))
;actual? tmp1b, tmp2bのように一意なシンボルになる
=> (%parameterize (a) (b) (tmp1a) (tmp2a) ((c d)) body)
=> (%parameterize (a c) (b d) (tmp1a tmp1b) (tmp2a tmp2b) () body)
=> (let ((tmp1a b) (tmp1b d) (tmp2a #f) (tmp2b #f))
(dynamic-wind
(lambda () (set! tmp2a (a tmp1a)) (set! tmp2b (c tmp1b)))
(lambda () body)
(lambda () (a tmp2a) (c tmp2b))))
シンボルの束縛値を得るには
そのスコープに束縛されているシンボルの値が欲しいです。
(define a 'A)
(define b 'B)
(define (ab sym)
(let ((a 1) (b 2))
(symbol-bound sym)))
(ab 'a) => 1
(ab 'b) => 2
これを実現するsymbol-boundはどうやれば作れますか? evalを使ってもトップレベルの環境しか見れないので、やはり無理なのでしょうか。
(define (eval-ab sym)
(let ((a 1) (b 2))
(eval sym (interaction-environment))))
(eval-ab 'a) => A
(eval-ab 'b) => B
- Shiro: 無理です。ローカル変数の名前というのは単なる記号で、 コンパイル時にのみ使われるので、letの中身が実行されるときには aやbという「名前」は存在していません(内部的には、「0番目のローカル変数」、 「1番目のローカル変数」、のようになっています)。 一応、デバッグ用の情報を探せば見つけられなくはないですが… この機能があったとして、どういうふうに使いたいですか?
- hira (2004/03/26 17:51:23 PST): 何でもキャストできるような関数を生成するマクロを書いているとき、欲しいと思いました。 *->listという関数の場合、引数の型がstringならstring->listを呼ぶというものです。 そのときstring->listをchar-listではなくline-listを返すものに一時的に挿げ替えたいと思いました。 それをローカル変数の隠蔽で実現しようとした、というのが経緯です。
applyは遅い?
(use gauche.time)
(use srfi-1)
;;everyを知らずに作っちゃったmy-every
;;悔しいのでgaucheのeveryと競争してみる
(define my-every
(case-lambda
((pred ) #t)
((pred val . rest) (and (pred val) (apply my-every pred rest)))))
;;my-everyからcase-lambdaをはずす
(define (my-every2 pred . args)
(if (null? args)
#t
(and (pred (car args)) (my-every2 pred (cdr args)))))
;引数をmy-everyに合わせたgaucheのevery(N-ary caseを削除。Fast pathのみ)
(define (every pred . lis1)
(or (null-list? lis1)
(let lp ((head (car lis1)) (tail (cdr lis1)))
(if (null-list? tail)
(pred head) ; Last PRED app is tail call.
(and (pred head) (lp (car tail) (cdr tail)))))))
(define charlist (string->list "
01234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789
01234567890123456789012345678901234567890123456789
"))
(time (apply my-every char? charlist))
(time (apply my-every2 char? charlist))
(time (apply every char? charlist))
;;;結果
gosh> (time (apply my-every char? charlist))
;(time (apply my-every char? charlist))
; real 0.063
; user 0.063
; sys 0.000
#t
gosh> (time (apply my-every2 char? charlist))
;(time (apply my-every2 char? charlist))
; real 0.017
; user 0.015
; sys 0.000
#t
gosh> (time (apply every char? charlist))
;(time (apply every char? charlist))
; real 0.000
; user 0.000
; sys 0.000
#t
gaucheのevery速いです。 my-every2は、たまにmy-every並に遅いときがあります。 gaucheのeveryはつねに0。これは(一般的に?)applyのコストが高いからなのでしょうか?
- Shiro: applyの実装は処理系によって全然違うと思うので、一つの 処理系から一般的な傾向は出せないと思います。Gaucheでもかつては applyは非常に遅かったのですが、今はそこそこじゃないかなあ。 プロファイラが無い状況で上記の結果を説明するのは難しいですが、 不定長引数を取る関数の呼び出しでrest引数がconsされるせいって理由が 考えられますね。固定長引数、もしくはrest引数に入る引数が無い場合は、 引数は全てスタックに置かれるので、アロケーションが発生しません。 例えばmy-every2は、再帰で呼び出される度に一回consが 入りますが、everyの方はinner loopはconsが入りません。case-lambda で作られた関数は全引数が一度リストにされるので、引数の数だけ consされます。その後ディスパッチも入りますから、不利ですね。
- hira (2004/03/25 01:06:05 PST):どうもです。applyのコストは処理系によって違うんですね。最近case-lambda版の書き方が気に入っていたので、ちょっと気になったのでした。実際のところ、現状の速度で全然問題ないです。
- Shiro: case-lambdaも理屈の上ではかなりアグレッシブに最適化できると 思うんで、必要にせまられたら最適化すると思います。
eof値を得るには
eof-objectを保持していたいのですが、*eof*のような定数ってありませんか? 今はこんなことをやって取得しているのですが、どうもしっくりきません。
gosh> (define eof (with-input-from-string "" (lambda () (read))))
eof
gosh> (eof-object? eof)
#t
eofの取得方法はこれしかないのでしょうか。 この質問の背景にはGaucheRefj:port-for-eachなどの「入力ポートと関係していないreader」を簡単に作りたい、という要求があります。
- fuyuki: バリエーションはいくつか考えられますが(see pack.scm)、基本的にそういう手しかないはずです。
- hira (2004/03/25 00:08:43 PST):どうもです。そうでしたか。Javaの場合EOFは-1だから考える必要なかったんですよね。Gaucheにも定数で欲しいなぁ。
- fuyuki 2004/03/25 19:38:08 PST: eofが定数になってないのは、あまりそれが一般的な意味でのオブジェクトではないからじゃないかと思います。うまく説明できませんが、Schemeがそうしない理由はなんとなくわかります。Scheme的。が、実用上不便なのはたしかなので、srfiにしてみるといいかもしれません。:)
- hira (2004/03/26 01:44:29 PST): あとundef。Schemeにはnullが無いんですよね。'()だの#fではなく「型無し値無し」を意味するnullが無い('()はlist、#fはboolean)。undefに巷のnull並の地位を与えてやりたいです。booleanな束縛の初期化に'()、listな束縛の初期化に#fと使い分けるというのも貧乏臭くてイヤだなぁと。
gosh> (define u1 (print)) u1 gosh> u1 #<undef> gosh> (eq? u1 (newline)) #t
- hira (2004/04/17 02:32:14 PDT): こういうコードをよく見かけます。上と同じ事をずっとスマートに実現しています。
(define undef (if #f #f))
- hira (2004/04/17 02:32:14 PDT): こういうコードをよく見かけます。上と同じ事をずっとスマートに実現しています。
define-syntax, syntax-rulesのプロシージャ版ってありませんか?
下のマクロはプロシージャの方が良いのですが、マクロの引数マッチが便利なのでマクロにしちゃってます。
(define-syntax /fi
(syntax-rules ()
((_ (inf opt) thnk ) (apply with-input-from-file inf thnk (apply %add-autoditect (%parse-opt opt 'i)) ))
((_ (inf args ...) thnk ) (apply with-input-from-file inf thnk ( %add-autoditect args ... ) ))
((_ inf thnk ) ( with-input-from-file inf thnk :encoding "*JP" ))
))
これを次のようなプロシージャに変換したいのですが、どうするのが手っ取り早いでしょう。 作ろうかと思ったのですが、有り物があるならそれを使いたいなぁと思いまして。
(define (/fi . args) 引数パターンでディスパッチして、引数をちゃんとバインドする)
- Shiro (2004/03/18 03:02:02 PST): syntax-rulesまで複雑なマッチングは使えませんが、 複数のlambda listパターンを持たせるならcase-lambdaというのがあります。 srfi-16ですが、Gaucheでは組み込みになっています。
- hira (2004/03/18 03:11:35 PST): どもっす。やっぱりありますよね。早速みてみます。
- hira (2004/03/18 04:42:23 PST): やってみました。1と2は「infがboundされていない」と怒られてしまいますね。これがsyntax-rules未満な部分でしょうか。なんか中途半端だなぁ。でも作るのは凄く大変そう。実装のイメージが全然わきません。
(define /fi (case-lambda ;1 (((inf opt) thnk ) (apply with-input-from-file inf thnk (apply %add-autoditect (%parse-opt opt 'i)) )) ;2 (((inf args ...) thnk ) (apply with-input-from-file inf thnk ( %add-autoditect args ... ) )) (( inf thnk ) ( with-input-from-file inf thnk :encoding "*JP" )) ))
- 参考