Scheme:初心者の質問箱:log00
- メール送信の方法
- R5RSマクロ中のdefineについて
- 部分スプリット
- なぜSchemeはstatic scopeで設計されているのでしょうか。
- ナンバープレイス
- Gaucheで実行中のコードを差し替えるには?
- Lisp を書くのによいキーボードは?
- 「Schemeについてどう思うか」についてどう思うか?
- cycle関数
- doとwhile
- /proc/self/statが使えない場面で
- リストから組合せを作りたいのですが…
- 関数の呼び方
- RedHat EnterpriseLinux ES release 4でthreadのテストに失敗する
- この正規表現の問題をどうやって解いたらよいか教えてください。
- hash-tableのキーに関数を使うのはNGなんでしょうか?
- xml->sxmlの逆変換
- グローバルなマッチ
- WindowsでGaucheからOpenGL
- おしりの$
- stream と副作用
- 循環リストのlength
- NaN == 0?
- Lisp の間違いな機能
- 読むテクニック
- even-stream
- eval
- portの条件
- gauche.vportの内と外で継続を辿りたい
- 不完全文字列
- partcont.scm
- sys-unsetenv
- practical-scheme.net
- 中身が1つだけのcollectionから中身を取り出す。
- 集合的なリスト
- equal?で比較するcase
- エラーの起きた環境で REPL
- cygwinでOpenGL
- let* は控えるべき?
- 分かってしまえばなんてこと無いけど難しかったこと。
- R5RSについての疑問
- 実プロダクトでのScheme活用事例
- Gaucheの拡張方法
- http-get
- Scheme学習に関して
- Schemeが最強って本当?
- Common Lispと比べると長所・短所は何でしょうか?
- 関数型ってなに?
メール送信の方法
Gauche:メールは読みましたが、現在メール送信の標準的なAPIは有るのでしょうか。 無ければ、sendmailを使うのが今のところ無難でしょうか。
- Shiro(2007/08/22 13:02:03 PDT): まだ無いです。環境によっていろいろなバックエンドを 使い分ける必要があるため、汎用的なモジュールを書くのは時間がかかりそうです。 環境で決め打ちが出来るなら決め打ちで書いてしまうのが手っ取り早いと思います。 ローカルのsendmail起動もしくはローカルのMTAにSMTPで話せるならそれが一番手軽でしょうね。 他サーバのSMTPと話すのは、最近だといろいろな認証に対応しないとならないでしょうから。
- ありがとうございます。ローカルのsendmailをwith-output-to-processで開いて送り出すことにしました。日本語文字のヘッダでの折り返しで苦労しています。plain textを送るだけなら、折り返しさえなければ問題ないです。
- Shiro(2007/08/24 12:50:57 PDT): ヘッダの作成についてはrfc.822に手続きがあっても 良さそうですね。今はパージングだけですが。
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のどのような利点が、このような選択をさせたのだと思いますか?
- The Revised Report on SCHEME, a Dialect of Lisp p.3 の lambda の説明によると
ということで、funarg 問題を解決するためのようです。funarg 問題については http://en.wikipedia.org/wiki/Funarg_problem 。When the precedure is eventually invoked, the intuitive effect is that the the <body> in an environment consisting of (a) the environment in which the lambda-expression had been evaluated to produce the procedure, plus (b) the pairing of the identifiers of the <identifier list> with the arguments supplied to the procedure. The pairings (b) take precedence over the environment (a), and to prevent confusion no identifier may appear twice in the <identifier list>. The net effect is to implement ALGOL-style lexical scoping [Naur], and to solve the funarg problem [Moses].
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でのシンプルなサンプルは無いでしょうか?
- 齊藤(2007/01/07 01:07:52 PST): シンプルとは言えないかもしれませんが、kahuaなどどうでしょう。
- Shiro(2007/01/07 02:31:45 PST): 特別なサンプルが必要というほど大げさな話じゃなくて、 実行中のプログラムがreplを受け付けられるようにしておいて (例えばGaucheRefj:gauche.listener) そこに新しい関数定義を流し込むだけです。 だから別に最近の動的言語なら何でも可能な話ではあるんですが。
- Rui(2007/01/07 06:25:48 PST): goshを実行中のプログラムと考えると、再定義はしょっちゅうやっているのではないでしょうか。次のコードは簡単なサンプル(のつもり)です。自前でREPLを提供するのは面倒なので、goshのREPL自体を使うことにしているので、goshを起動して下のコードをコピペしてください。コードをコピペするとechoサーバが別スレッドで動き出します。
(use gauche.threads) (use gauche.net) (define *port* 5678) (define echo-filter (lambda (val) val)) (thread-start! (make-thread (let1 server (make-server-socket 'inet *port* :reuse-addr? #t) (rec (main) (let* ((sock (socket-accept server)) (in (socket-input-port sock)) (out (socket-output-port sock))) (let loop ((istr (read-block 4096 in))) (cond ((eof-object? istr) (socket-close sock) (main)) (else (display (echo-filter istr) out) (loop (read-block 4096 in))))))))))
5678番ポートへの入力をエコーします。手抜きのために同時に1接続しか処理できませんが、それは気にしないでください。telnet localhost 5678などというようにtelnetで接続すると、入力がそのまま送り返されてくるはずです。で、実行中のコードを差し替える例として、echo-filterを再定義してみます。echo-filterは最初はvalues相当なのですが、これを差し替えると入力をフィルタした結果を出力できます。次のコードをgoshに入力すると、次の瞬間から入力が大文字でエコーされるはずです。(use srfi-13) (define (echo-filter istr) (cond ((string-complete->incomplete istr) => string-upcase) (else istr)))
実行するとこのように意図したように動きます。string-upcaseをstring-titlecaseなどで置き換えたりしていろいろ遊んでみると楽しいかもしれません。$ telnet localhost 5678 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. this is a pen. (これは入力) this is a pen. (これは出力) <<<ここでecho-serverを再定義> the quick brown fox ju (入力) THE QUICK BROWN FOX JU (出力)
- j-kon?(2007/01/07 19:57:03 PST): ありがとうございました。勉強してみます。
Lisp を書くのによいキーボードは?
Emacs でメタ/コントロールキーを多用するのにキーボードのお勧めありますか? またノートPCではどうでしょうか。
- び(2006/12/29 21:15:46 PST): 定番ですがHHKやHHK Lite2。ノートだと、AppleのノートのJISキーボードはAの左側にControlキーが来るので使いやすいと思います。もっとも、こういうヒューマンインタフェース部分については慣れが極めて大きなファクターになるので、他の良いものを紹介されてもなかなか乗り換えられないということもあります。
- Shiro(2006/12/30 01:04:38 PST): 私はもう小指の付け根でControlを押すのに慣れちゃったんで、
Aの隣がControlはちょっと困ります (ついでに、Cを書いてるとCapsLockって多用するんだけど
私だけ?) ノートPCだと確かに押しにくい。
私の知る或るEmacsウィザードは、 Kinesisのキーボードを使ってます。 キーマップをカスタマイズして親指にMeta/Controlを割り当ててました。 「もうこれじゃないと打てない」と言って、自宅用、会社用、予備など数台持ってたはず。 ちなみに彼は、画面一杯にEmacsを開いて縦に2分割し、ちょうど80カラムづつになるような フォントを選び、シェルから何から全てをEmacsの中で済ませてしまいます。 一時期複数マシンで並列処理をするプログラムを書いてた時はEmacs窓が16分割とかされてて 各ノードのリモートシェルが走ってました。
あと、LispマシンSymbolicsのキーボードは 括弧が打ちやすくなってます。
- leque(2006/12/30 13:47:54 PST): 私も Kinesis キーボードを使ってます。親指 Control はデフォルトの位置だと遠いので A の隣にしています。
ノート PC や普通のキーボードの場合には Control を A の横にして、Alt(Mac の場合は Command)を Meta キーとして親指で押してます。日本語キーボードの場合だと無変換や変換をリマップしてしまう人もいるそうです。ただ、私の場合はもともと vi 使いなので、Emacs でも viper モードをつかっていて Meta キーはそれほど使いません。DrScheme には DivaScheme という Structual Editing モードがあり、Emacs のような finger gymnastics が必要ないのだとか。 :-) http://www.cs.brown.edu/research/plt/software/divascheme/ http://www.scheme.dk/blog/2006/10/divascheme-structural-editing-for.html
ところで Symbolics のキーボードだと Meta や Control がタッチタイプしやすいように見えないんですけど、これってどうやって押してたんでしょう?
- Shiro(2006/12/30 15:30:23 PST): 私もSymbolicsでまじめに開発したことは無いので 推測ですが、この位置なら親指じゃないでしょうか。普通のキーボードのAltを 私は親指で押しています。Emacsで頻出するCtrl-Meta-なんとかの場合、左小指の付け根で Ctrlを、左親指でAltを押せば、キーボードの左半分は残りの4本の指でカバーできます。
- kiyoka (2007/01/01 06:22:57 PST): 私も大量にコードを書く時はKinesisのキーボードを使います。 ちなみに、私はWebからメールからshellから全てをEmacsの中でやる派です。(Lingrだけは無理ですが^_^) Kinesisを使うときは、左親指がCtrlキーです。 PowerBookを使う時はAの横のCtrlキーで、会社では小指の付け根でCtrlキーを押します。 というわけで、全てのパターンをいつでも切りかえて使える脳になってしまいました。 身体的に一番疲れないのは、Kinesisです。どこにもストレスがかからないので肩凝りも少ないです。この左親指コントロールは一番オススメです。 全ての環境でKinesisが買える財力がある人はKinesisが良いのでは無いでしょうか。そうでない人は脳内スイッチをつかいましょう。 ちなみに、Ctrl以外のキーマッピングまでは無意識でスイッチできないのでKinesisのキーマッピングは日本語106に似せています。(小括弧など)
- Rui (2007/01/01 17:45:46 PST): 私もKinesisのキーボードです。Kinesisキーボードはマイナーなだけに、ここにこれだけユーザがいるのは驚きです。一見奇妙な形に見えるけど良さを追求しているという点がSchemeと共通するんでしょうか。Kinesisは特別にLisp向きのキーボードというわけではありませんが、いいキーボードを求めていてあの形に抵抗がないならおすすめです。
「Schemeについてどう思うか」についてどう思うか?
◆◆◆ About Scheme ◆◆◆という文章があります。Schemeについての理解も未だ浅い上にCommon Lispについては全然知らないので、その両方を知っていてSchemeを好む方の声を聞きたいです。(2006/12/13 19:57:49 PST)
- 2つを比べると名前空間の違いが大きいかな。わたしはSchemeの方が好きですね。引用された文章そのものに対する意見としては、人それぞれいろんな考え方があっていいと思います。勘違いと思われる部分もありますが、そもそも勘違いを引き起こす要因がなにかあるのだなと考えさせられます。
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
- び(2006/11/30 04:05:24 PST): こんな感じでしょうか。スレットセーフかとかset!邪悪とか何にも考えてませんが。
(use srfi-1) (define (cycle ls) (let1 cls (apply circular-list ls) (lambda () (begin0 (car cls) (set! cls (cdr cls))))))
- 齊藤(2006/11/30 05:15:26 PST): ちょうどひとつ前のネタがそのまま使えますね。Scheme:generatorとdoとwhileのページの最後にあるmake-generatorを利用した実装だとこういうこともできます。
(define (cycle ls) (make-generator (cut for-each <> (apply circular-list ls))))
- nobsun(2006/11/30 05:43:37 PST): 素朴に (^^).
(define (cycle ls) (let ((l (length ls)) (i 0)) (lambda () (begin0 (list-ref ls i) (set! i (modulo (+ i 1) l))))))
- leque(2006/11/30 06:03:02 PST): びさんとほとんど同じ。
(use srfi-1) (define (cycle xs) (let1 xs (apply circular-list xs) (lambda () (pop! xs))))
- nobsun(2006/11/30 06:09:08 PST)
(use gauche.parameter) (define (cycle ls) (let1 i (make-parameter -1 (lambda (j) (modulo (+ j 1) (length ls)))) (lambda () (list-ref ls (i (i))))))
- ありがとうございます。いろんなやり方があるんですね。--coze
doとwhile
最近のEcmaScriptにはジェネレータという機能があります。それと同じような感覚で使えるものをSchemeで実装してみようとこんなものを書きました…
- Shiro(2006/08/09 10:51:16 PDT): これはとても興味深い話です。長くなりますんで移動します → Scheme:generatorとdoとwhile
- 2006/08/11 03:19:19 PDT:ありがとうございます。要は最初に捕捉した継続を使いまわしていたことが問題だったわけですね。納得しました。
/proc/self/statが使えない場面で
とあるフリーサーバにGaucheをインストールしました。 そのサーバではLinuxが使われてます。 一般ユーザの権限で/procにアクセスできないようになっているせいで、GCがスタックの開始アドレス取得に失敗する模様です。(おそらくセキュリティ上の都合でしょう。) 何か簡単な迂回策があれば教えて下さい。GCライブラリの中の話なので本来ならそちらに質問すべき内容かとは思いますが、英語でちゃんと説明できる自身がないので…。
- Shiro(2006/07/06 21:02:55 PDT): Gauche特有の話ですし、初心者向けでもないので 別ページにて →Gauche:GC:/proc/self/statが使えない場合
リストから組合せを作りたいのですが…
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#))
何が起きているのか、どなたか教えて頂けないでしょうか?
- Gosh のプリンタは write/ss 相当だからです。 #n= は共有されてるペアを示しています。次のようにすると期待通りに表示されると思います。
(write (combination-maker '((a b) (c d) (e f))))
- すみません。Gauche 0.7.4のリリースメモに書いてありました。 write/ssを知らなかったもので出力を見てひとりで慌ててしまいました。skr
- util.combinationsの再実装ですね。
関数の呼び方
(Scheme:Procedureに移動しました。)
RedHat EnterpriseLinux ES release 4でthreadのテストに失敗する
- Shiro: Gauche:Bugsに移動します。
この正規表現の問題をどうやって解いたらよいか教えてください。
- 文にあらわれた全ての
<a href="ホゲ">>>1から1000までの数字</a>
に対して、その両側にあるタグ<a href="ホゲ">と</a>
を消す。
例:
<a href="./foo.png">foo.png</a><br><a href="./index.html#30">>>30</a><br>
↓
<a href="./foo.png">foo.png</a><br>>>30<br>
自分なりに書いてみたコード
(define str "<a href=\"./foo.png\">foo.png</a><br><a href=\"./index.html#30\">>>30</a><br>") (regexp-replace-all #/<a href=.*>(>\;>\;\d{1,4})<\/a>/ str "\\1")
だと、結果が、">>30<br>"になってしまいます。gemma
- (2006/05/02 02:19:16 PDT) Rui: 正規表現の繰り返し表現は「強欲」(greedy)で、最も左の一番長いマッチを探します。「<a href=.*>」を「<a href=abc>...</a><a href=def>」にマッチさせると、「.*」が「abc>...</a><a href=def」にマッチしてしまうんですね。これを解決するには、タグを閉じる「>」を超えないよう「.*」の部分を書き換えます。「[^>]*」に書き換えるのがいいかな。ここらへんは正確さと手間の問題になるのですが。
(define str "<a href=\"./foo.png\">foo.png</a><br><a href=\"./index.html#30\">>>30</a><br>") (regexp-replace-all #/<a href=[^>]*>(>\;>\;\d{1,4})<\/a>/ str "\\1")
- (2006/05/02 08:05:39 PDT) gemma: なるほど!初心者の自分にもわかりやすい、丁寧な解答をいただきました。本当にありがとうございました。
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)"
- R5RS では手続き同士の equal? の結果は規程されていません。この場合は eq? や eqv? を使うべきです。
gosh> (define ht (make-hash-table 'eq?)) ht gosh> (hash-table-put! ht cons 1) #<undef> gosh> (hash-table-get ht cons) 1 gosh> (define ht2 (make-hash-table 'eqv?)) ht2 gosh> (hash-table-put! ht2 cons 2) #<undef> gosh> (hash-table-get ht2 cons) 2
Shiro(2006/04/15 15:48:30 PDT): ここでの問題はequal? ではなくて (手続き同士の equal? は eq? と同じです)、equal-hashtableを作るために必要なハッシュ関数が 手続きに対しては定義されていない、というエラーです。
equal-hashtableに使われるハッシュ関数hashには、ハッシュ値がプロセスイメージと 独立である(そのため永続テーブルに保存したり別プロセスに送ったりできる)という 属性もあるので、単純に オブジェクトのアドレス値からハッシュ値を計算するといった方法が使えないのです。
この「ハッシュ値がプロセスイメージと独立」という性質はCommon Lispの sxhash関数に倣ったものです。ただ、単一プロセス内でequal-hashtableを 使う限りにおいては不要な性質であり、選択の余地が無い今の実装は 使いづらいですね。ぼちぼちhashtable回りも改善してゆきたいと思います。
- なるほど。言われてみれば (equal? cons cons) とかは #t になりますね。
- なるほど。分かりやすい説明ありがとうございます。どうあるのが正しいか、なかなか難しく興味深いですね。-- ふじさわ
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*>"
- 以前同じ話がありましたね。ここ-->kiyoka:log:2005
グローバルなマッチ
Gaucheの正規表現(#//)を便利に使わせて頂いております。 Perlで/gを指定したときのように、一行中にある複数のマッチした文字列を取り出す方法はございますでしょうか? es (2006/04/11 04:35:07 PDT)
- Rui (2006/04/12 05:59:58 PDT): 自分でループをまわすしかないかも。Perlライクな動きだと次のようなコードで実現できるかもしれませぬ。
(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")
- es (2006/04/12 08:44:22 PDT): ありがとうございます! 結構前から悩んでいたんですが、なるほど、とても勉強になりました。内包表記についても事も知ることが出来ました。
- Rui: 実は内包表記つかったのこれが初めてです。使ってみると便利かも。
WindowsでGaucheからOpenGL
線型代数の本で一次変換のことを読んで興味がでてきました。そしてOpenGLが行列変換を使って種々の変換を行っているらしいことまではわかりました。まずは何を用意したらWindowsで動かせるでしょうか?MinGW版のGaucheで動かしたいです。 sasagawa
おしりの$
pa$ や map$ などの、おしりの$がへんてこりんに見えます。$をつけている理由はなんですか?
- Scheme:!と?によると、「これ($)に関しては特にScheme間でのコンセンサスはなさげ。 Gaucheでは部分適用を表すのに使っている」だそうです。
- 2006/03/21 19:16:11 PST: Programming:WayToHaskeller:はいはい で、Haskell の $ からきているという話があります。
- なるほど、そうなんですか。ありがとうございました。(といいながらも Haskellのページを読んでも全くちんぷんかんぷんでした^^; Haskell勉強せねば…)
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 度呼ばれているようなんですが、 これはなぜでしょうか?
- Shiro(2006/03/14 13:02:04 PST): これはsrfi-40参照実装のバグっぽいですね。
stream-consのdelayの中で、第2引数が2度評価されてます。
(stream-cons *c* (s)) の展開形はこうなります:
(%make-stream (delay (if (not (stream? (s))) ;; (s)の評価1 (error "attempt to stream-cons onto non-stream") (cons *c* (s))))) ;; (s)の評価2
普通はstream式はidempotent(何度評価しても結果は同じ)なので見過ごされて いたんだと思います。普通stream式は呼ばれるとstreamを作って返すので、 今までstream-consは呼ばれるたびにstreamをひとつ余分に作成してた わけですな。これをfixすると少し性能もあがるかも。
- Shiro(2006/03/14 13:14:39 PST):
fix前
;(time (stream-ref (stream-iota -1) 100000)) ; real 1.124 ; user 1.130 ; sys 0.000
fix後;(time (stream-ref (stream-iota -1) 100000)) ; real 0.650 ; user 0.650 ; sys 0.000 100000
- とおる。(2006/03/15 15:51:29 PST): なるほど、マクロを展開してみれば良かったんですね。 しかしきれいに 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を使っています。
- 2006/02/23 04:00:07 PST: R5RSの6.3.2節には「A chain of pairs not ending in the empty list is called an improper list.」とあります。#0=(1 . #0#)はpairが延々と連鎖しているので一見proper listのように見えますが、末尾が()でない以上、これはproper listではありません。
- ありがとうございます。これはproper listではないんですね。ということはlengthで無限ループに陥るケースとは、引数にどのような循環リストを与えたときなのですか?
- 2006/02/23 05:24:54 PST: Gauche 0.8.6のソースを斜め読みしてみましたが、循環リストで無限ループしないように対策がされていました。なのでリファレンスの記述が誤っていると思われます。
- そうですか。調べてくださって、ありがとうございました。
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で何度も出ているのですが、すっきりした結論が出ていないのです。 だいたい議論はこんな感じです:
- (let ((a (/ 0 0))) (eq? a a)) はScheme的には#tになるのが自然。
- R5RSでは (eq? x y) ならば (eqv? x y) でなければならない。
- R5RSではx, yが数値で共にexact、もしくは共にinexactの場合、 (eqv? x y) ならば (= x y) でなければならない。
- しかし、IEEE754では NaN != NaN と規定されている。
案としては、確かこんなのが出てたと思います。
- IEEE無視でScheme的には NaN == NaN としてしまう
- R5RSを曲げて (eq? NaN NaN) => #t でも (eqv? NaN NaN) => #f を許す。
- そもそも NaN は Not a Number なんだから、3.の制約は無しってことで (eqv? NaN NaN) => #t でも (= NaN NaN) => #f でいいんじゃない?
個人的には最後の案でいいかなという気がしてます。あ、(= NaN 0)は#fに なるべきだと思います。バグですね。
- (質問者)丁寧な説明ありがとうございます。Schemeのど初心者なのですがNaN振る舞いについての理解が深まりました。
Lisp の間違いな機能
『ハッカーと画家』p.187 に
Python は Lisp ハッカーの多くが間違いだと思っている機能さえコピーしている。
とありますが、その機能とは何ですか? リスト内包表記があるんだから map や filter は 必要ないというのをぱっと思い浮かんだのですが、それとは違いますよね。
また Python 関係なしに、Lisp の間違い(だとハッカーが思っている)機能には、どのような ものがあるのですか?
- Shiro(2006/02/13 02:31:51 PST): Paulが何を考えていたかは聞いてみないとわかりませんが、 個人的には 'lambda' という名前かな (長いから)。
- ありがとうございました。ということは Lisp でプログラムを書くときにも「lambda が fun とか fn だったらなあ」と思うのは僕だけではないんですね^^;
- Gauche的には(define fn lambda)とでも(define fun lambda)とでもすれば
お好きにできますよ。
身内に(define alias define)を披露して大爆笑されたヤツでーす。(^^;cut-sea:2006/02/14 19:37:42 PST
読むテクニック
even-stream
even style な stream で、stream を返す関数が全体を (delay (force ...)) で囲まなければならない理由が分かりません。 囲まなくても動くように見えますが、メモリリークを防ぐためとかの理由でしょうか。
- 分かった気がする。例えば SRFI-40 の map2 とかで、
(define (map2 func strm) (if (nil2? strm) nil2 (cons2 (func (car2 strm)) (map2 func (cdr2 strm)))))
とかやっちゃうと、delay される前に nil2? で force されちゃうから、odd stream みたいに一つ先の要素まで評価しちゃうってことか。 ということで合ってるでしょうか?
eval
- scm
> (define x '(* 2 3)) (define x '(* 2 3)) #<unspecified> > (car x) (car x) * > (eval x) (eval x) 6
- gauche 0.8.6 on FreeBSD 4.11
gosh> (define x '(* 2 3)) (define x '(* 2 3)) x gosh> (car x) (car x) * gosh> (eval x) (eval x) *** ERROR: wrong number of arguments for #<subr eval> (required 2, got 1) Stack Trace: _______________________________________ 0 (eval x) At line 3 of "(stdin)" 1 (eval x) At line 3 of "(stdin)"
なぜでしょう?
- R5RSでのevalは引数を2つとる手続きで、2つ目の引数には環境識別子を渡します。Gaucheのevalでは、環境識別子としてモジュールを渡すことになっています。なぜ環境識別子を渡す必要があるかというと、環境が、自由変数の解決やグローバルな束縛の追加に影響してくるからです。環境識別子によって、evalの中でdefineしたグローバル変数がどのモジュールに追加されるのかということが変わってくるのですね。外側の変数を参照している場合には、それがどのモジュールの変数を指すのかというところが変わってきます。ローカルな環境が取得できるScheme実装なら、ローカル変数を参照することもできるはずです(実際にそういう実装があるのかどうかは知りません。Gaucheでそういうことが不可能なのはパフォーマンス上の都合だと思います。)
- ありがとうございます。湯浅先生のScheme入門の第1章演習問題[1.5.1]のところで引っかかっている初心者なものでややこしい話はわかりませんが、(eval x ()) とでもすればいいのですね。scmで読み進めます。
- Shiro(2005/11/30 03:52:18 PST): 湯淺先生の本はR5RS以前に出たので、
現在のSchemeと異なる点がちょっとだけあります。モダンなSchemeでは
(eval x (interaction-environment))
と書くのが正解です。
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)
- Shiro: flushやfillなどはメソッドとして定義するのではなく、
普通の手続きを<buffered-output-port>の対応スロットに格納するようにします。
メソッドディスパッチはオーバヘッドが大きいためです。
ソースツリーの ext/vport/vport.scm がサンプルになると思います。
どうしてもメソッドでやりたいという要求があれば、 メソッドで定義できるようにするクラスをvportの上に作れなくは ないです。
ところで上のコードが通っちゃうのは本当はまずいな。 Cで定義されたクラスを祖先に持つクラスを複数使って多重継承することは 本来出来ないはず。<network>の定義でエラーが出ないとまずい。
- 御回答ありがとうございます。どうにか実現することができました。bufferedが使いこなせず、virtualの方だけですが、おおよその雰囲気はつかめたようです。
(use gauche.net) (use gauche.vport) (use gauche.uvector) (define (sock-output-port socket) (let ((putb (lambda(ch) (socket-send socket (string ch)))) (puts (lambda(str) (socket-send socket str)))) (make <virtual-output-port> :putb putb :puts puts))) (define (sock-input-port socket) (let ((getb (lambda() (string-byte-ref (socket-recv socket 1) 0))) (gets (lambda(siz) (socket-recv socket siz)))) (make <virtual-input-port> :getb getb :gets gets))) (define socket (make-client-socket 'inet "localhost" 80)) (define out (sock-output-port socket)) (define in (sock-input-port socket)) (display "GET / HTTP/1.0?r?n?r?n" out) (read-line in) (socket-close socket)
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)
- 一応、ここまで作ったソースも出しておきます。→socket/server.scm
- Shiro: うーむ。それを許すには、Cでポートにアクセスしている部分を 全てトランポリンで書き直すか、継続の実装をSCM方式(Cスタックまるごとコピー)に 置き換えるしかないと思います。どちらも現実的ではないですね…。 vportの中の継続が持ち出せると、確かに色々面白いことがSchemeレベルで 書けるんですが。
- nekoie: なるほどです……。read系関数のラッパー関数を提供しても、近い事が実現できそうな事に気付いたので、とりあえず、そっちで試してみようと思います。ありがとうございます。
不完全文字列
u8vectorが広く採用されるに従って、不完全文字列の存在意義が薄れているような気がしますが、長期的に不完全文字列は廃止方向ですか?
- Shiro(2005/11/21 14:39:32 PST): 完全に廃止できるかどうかはまだわかりません。
現在の実装が不格好なのは気になっているので何とかしたくはあるのですが、
不正なバイトシーケンスを文字列内に一切許さないという厳しい方針だと
それはそれで不便なこともあるので。とりあえず、「どうなるかわからないので
あまり積極的に使うことは勧めない」とさせて下さい。
方向としてふたつ考えています。うまくいけば2番目の方向が使いやすいかなと 思っているのですが、うまくいくかどうかもう少し考えてみないと何とも。- 現在の不完全文字列を(immutableな)u8vectorに置き換える代わりに、 文字列操作関数がu8vectorも取れるようにする。
- 不完全文字列の処理を見直し、不正なバイトだけを特別扱いできるようにする。 例えば途中に不正なバイトが入っている "あいう?x80えお" のような文字列であっても、 文字として読むと #?あ #?い #?う #?stray-byte-80 #?え #?お のように 完全な部分はふつうの文字として読めるようにする。現在、不完全な文字列を 許していない文字列操作関数や正規表現についても、このような不正バイトが 含まれる文字列を扱えるように拡張する。
partcont.scm
Kahuaの部分継続はすごく便利なので、Kahua以外でも使いたいです。Gauche本体に取り込まれたりはしないのでしょうか? -- nekoie(2005/11/16 12:35:02 PST)
- Shiro(2005/11/17 01:19:04 PST): 部分継続のプリミティブにshift/resetを据えることが 良いことなのかどうなのかもう少し考えたいので、保留にしてあります。 どうも一口で説明できないので、何かもっと別のわかりやすい方法があるんじゃないか という気がして。コード自体は小さいですしBSDLなので、当分コピーして 使ってもらえればと思います。
sys-unsetenv
システムの環境変数操作について。unsetenvやclearenvなど削除系の関数はマニュアルに載っていないのですが、実際はsys-unsetenv関数が使えるようです。マニュアルの漏れですかね? -- ふじさわ(2005/11/15 03:32:45 PST)
- Shiro(2005/11/17 01:19:04 PST): 漏れですね。えーっと、環境を変更する関数群はPOSIX準拠なのか どうか調べなくちゃと思って忘れてたのかな。 でもsys-putenvは載せてるな…
practical-scheme.net
ShiroさんのWiLiKiのページ履歴を差分表示するまで気付きませんでした。 いつの間に? [skr]
- Shiro(2005/11/07 16:50:54 PST): ドメイン取ったのは少し前です。今は ミラーしてますが、そのうちshiro.dreamhost.comの方はリダイレクタに 置き換えると思うので、ブックマークなど直して頂けたらありがたいです。
中身が1つだけのcollectionから中身を取り出す。
Collection cの中身が1つだけとわかっている場合、
(fold (lambda (x _) x) '() c)
なんてやって値を取り出しているんですが、他にもっとスマートな方法は無いでしょうか。 Sequenceなら(ref s 0)でおしまいなんでしょうけど。 ちなみに、dbi/dbdの結果から、値を取り出したいのです。katsujiro
- (find (lambda (_) #t) c) とか。
- ねるWiki:ねる こんなのつくってます。
(define-method collection-car ((self <collection>)) (call-with-iterator self (lambda (end? next) (if (end?) #f (next)))))
- とりあえず、dbiの方のインタフェースが変わって、一部result-setがsequenceになったので(ref s 0)で済むようになりました。findとか使えますね。効率考えたら、イテレータでということになりますでしょうか。ありがとうございます。katsujiro
集合的なリスト
SRFI:1 の lset って遅くないですか?集合演算ごとに線形時間か、それ以上かかりそうな……
- Shiro: (2005/10/12 17:55:52 PDT): アルゴリズムの改善案があったら歓迎します。 ただ、リストを集合として使おうという時点で、要素数は限られてると 考えられるのではないかと思います。数十個以上の要素が考えられるなら 最初からハッシュテーブルなどを選ぶべきでしょう。
- ただ、汎用collectionに対する集合演算があると便利、 という意見ならもっともです。gauche.collectionあたりにあると便利ですかね。
equal?で比較するcase
caseシンタックスは、キーと一致するclauseを判定するのにeqv?を使いますが、eqv?ではなくequal?が必要なときはどうしたらいいでしょうか? もちろんcondを使って、自分でcondのtest clauseにequal?を並べることはできますが、(1)キーの生成に副作用がある場合にはプログラマが明示的にローカル変数を作らないといけない、(2)副作用がないとしても同じキーを並べるのが面倒、という理由で、equal?で比較するcaseがあると便利だと思うのですが。自分でシンタックスを追加してもいいとはいえ、そういうものが標準で用意されていないのは、なにかいいやり方が別にあるから?
- Shiro(2005/09/24 02:02:16 PDT): いえ、たぶん必要ならマクロですぐ書けるけれど
標準にするほどには頻繁に必要とされない(もしくは一般化しにくい)、
という理由なんではないかと推測します。
ちなみにutil.matchを使えば似たようなことが出来ます。Scheme的には
「どうせやるならこのくらいまで一般化したい」という心理が働くのかも。
(match var ((or "abc" "def" "ghi") ....) ;;; 文字列のいずれかにマッチ ((or '(abc) '(def) '(ghi)) ...) ;;; リテラルリストにマッチ )
- び(2005/09/24 06:06:54 PDT): Gaucheを使い始めた頃、string=?で比較するcaseが欲しいと思って以下のようなマクロを書きました。
(define-syntax case* (syntax-rules (else) ((_ (exp ...) clauses ...) (let ((pred (exp ...))) (case* pred clauses ...))) ((_ pred (else e1 e2 ...)) (begin e1 e2 ...)) ((_ pred ((c1 c2 ...) e1 e2 ...)) (if (find pred '(c1 c2 ...)) (begin e1 e2 ...))) ((_ pred ((c1 c2 ...) e1 e2 ...) clause clauses ...) (if (find pred '(c1 c2 ...)) (begin e1 e2 ...) (case* pred clause clauses ...)))))
こんな具合に使います。(case* (pa$ string=? var) (("abc" "bcd" "cde") "ABCDE") (("zyx" "yxw" "xwv") "VWXYZ") (else "HOGE!!"))
でも、実際のコードでこれを使ったことは1、2度しかなかったと思います。どうもcaseって中途半端な感じがしてたんです。そうか、util.matchって手がありましたね。当時はまだGaucheにはなかったですけど。
- 実際にmatchは試してみましたが、デフォルト(マッチするclauseがないとき)の動作がエラーなのがちょっと不便でした。(_ #f)とかを最後に置けばいいんですけど。
エラーの起きた環境で REPL?
このWiLiKiでどなたかがボソっとおっしゃっていたような記憶がありますが。
Lispの処理系なんかによくある機能だと思うんですけど、エラーが起きたときの環境でreplのプロンプトがでたりするのって、Scheme(Gauche)ではどうなんでしょうか。
再現性の低いバグとかの解析とか、1回だけの処理で起きたエラーからとりあえず復帰させて処理を継続させるとか、いろいろ便利(?)(katsujiro)
- Shiro(2005/09/23 19:27:00 PDT): 「充実したデバッグ環境」は1.0以降に手をつける予定です。 現在のGaucheでもエラーハンドラはエラー発生時の環境で呼ばれているんで、 そこでreplを呼べなくはないですが、ローカル環境を取り出すAPIが無いし エラーから復帰できるとは限らないんであまり使えないでしょう (エラーからちゃんと復帰するには、errorやraiseを呼び出す側で安全な リスタートポイントの継続を渡す機構が必要。でもCで書かれたルーチンから Scm_Error()で呼ばれた場合なんかは安全なリスタートは不可能なことが多い。 多くのCコードはScm_Error()から帰って来ることを想定していないから)。
- なお、私自身は、Lisp処理系でエラー時にデバッガに入られるのを
むしろ煩わしく感じてまして。もちろん解析したいとかエラーからその場で
復帰したいとかいうケースはたまーにあるんですが、それ以外のほとんどの
場合はデバッガを抜ける手間が増えるだけなんで。
直前のエラー時のコンテキストを保存しておいて、トップレベルに戻った後で
デバッガに入ることを選択できたらいいなと思ってます (そういうScheme処理系が
あったはず)。ただ、全部Schemeで書いてたら継続を取っとけばいいんですが、
Gaucheの場合Cパートとの絡みがあるので完全なリスタートは難しいかも。
- core dumpしたら、デバッガって感じですね。そのcoreに再入可能だとすごいかも。そのときの、束縛一覧をとれると、もう十分という感じですが。
cygwinでOpenGL
cygwinでGauche-glが使えるというのは、cygwinのX Window上という事でしょうか? Windowsそのもの(書き方が変で済みません)で使う方法があったら教えて頂けないでしょうか?
- Shiro(2005/09/21 16:11:25 PDT): 私が試したのはWindowsネイティブ のGLだったと思います。 Xは入れてないので。glutをどこかから入手しておく必要があるはず。 後は特に苦労した覚えはないのだけれど。
- とおる。(2005/09/24 06:42:47 PDT): cygwin の opengl を入れておけば GLUT も使えました。 X はなくても大丈夫です。
let* は控えるべき?
自分の書く関数は let* からはじまることがよくあります. これは避けた方がいいという話があるみたいですけど, 理由がわかりません. ええと, 「手続き型発想」をしてるつもりはさらさらなくて, 「f(x) = (1/Z) exp L, Z = √(2πσ^2), L = - D/(2σ), D = (x - μ)^2」 みたいに, ごちゃごちゃした式の部分部分へ「名前」をつけて 見やすくしてるだけのつもりです.
- Shiro: 別に避ける必要はないと思いますよ。Paul Grahamが 「手続き型発想をしているとlet*を多用する傾向がある」とどこかで 書いてたような気がしますが、「let*を多用したら手続き型発想だ」とは 言ってなかったんじゃないかなあ。
- ただ、もう少し突っ込んだ見方としては、let* は必要以上に
プログラムの実行順序に制約をかけてしまうという傾向があります。
例えば次のコード片において:
(let* ((a (f0 x y)) (b (f1 x y)) (c (f2 a b)) (d (f3 a b))) ...)
f0, f1, f2, f3が全て副作用無しであるとすれば、本来、f0とf1の呼び出し、 およびf2とf3の呼び出しはそれぞれ順不同で良いはずですが、let*が使われて いることから自動的にf0→f1→f2→f3という順番が強制されてしまいます。 処理系としては、次のように書いてもらった方がずっと最適化がやりやすいです。(let ((a (f0 x y)) (b (f1 x y))) (let ((c (f2 a b)) (d (f3 a b))) ...))
- nobsun: モナドみたい...:-)
- skimu: 手続き的発想が特に悪いということも無いので、関数的発想と適当に使い分ければいいんじゃないでしょうかね。 でも、ある関数でやたら長い let* を書いてしまったら、それはプログラムの構成を見直して見る良いチャンスだというのも真な気がします。Scheme に限らず、ローカル変数がやたらたくさんあるのは見苦しいですよね。 (今日ちょうど、一つの let* に 30 あまりのローカル変数がいる関数を書いた.... 書いてて気分が悪くなった。)
- katsujiro: このトピックを見て、letの束縛列の評価順が、左から順番じゃないことを知らない自分に気がつきました。あわててR5RSを読みまして、確かにそのように書いてあります。今のところ自分のコーディングで問題になったことないけど、バリバリ副作用だらけの式を書いている(お互い束縛-使用の依存がないからlet)ので、冷や汗ものでした。値の部分で副作用が起こるような場合は、評価順のために全部let*でなければまずいんですね。
分かってしまえばなんてこと無いけど難しかったこと。
R5RSについての疑問
くだらないことなんですが、zero?という述語、なぜ用意されているんでしょう? (= x 0)で十分だと思うんですけど。(eq? x '())と同じ意味のnull?が用意されているのに対応して、zero?が存在しているのかしら?
- 私は普通に使っています。(= x 0)と書くことはまずありません。確かに冗長なのですが、意味があって適切な名前が付けられる述語はソースコードの見通しが良くなって良いと思うのですが。
実プロダクトでのScheme活用事例
ここを読んでいて感じたのですが、他言語コミュニティと比較して、Schemer内の職業プログラマ比率は低めなのでしょうか。 いや、そんなことは無い、実プロダクトでもバリバリ使っているよ!という事例がありましたら、教えていただけるとうれしいです。学習の励みになります。
また逆に、「今はまだ実プロダクトでは厳しいかもしれない。でも、○○が整備されればイケるんじゃないかな?」といったご意見でもうれしいです。それはそれで、目標ができて励みになりますから。
- Shiro: 使われているとすれば、そのほとんどがインハウス向けツール だと思います。だとすると具体的に発表しにくいケースも多いんじゃないかと 想像します。
- び: 客先納品物で運用やメンテナンスが完全に手許を離れるようなものにScheme(あるいはGauche)を使うのは、現状では営業面や政治的側面から難しいと思います。ただ、運用やメンテも含めてやるような仕事だと、わたしはGaucheを使っちゃってます。まぁわたしがバリバリの職業プログラマかと言われるとかなり怪しいですが。
Gaucheの拡張方法
http-get 問題の解決に関して Gauche に libcurl を組み込んだらいいんじゃないかと思ったのですが、その方法がわかりません。適当なドキュメントはありますでしょうか?
- Shiro: まだないです。C APIを今猛烈な勢いで見直してる最中です。 今どうしても必要なら既存の拡張ライブラリを読んだりGauche:MeCabあたりを 参考にしてみて下さい。もう少し待てるなら、0.8.6で若干拡張ライブラリが 書きやすくなるのでそれをお待ち下さい。
http-get
Gaucheでhttpクライアントを作ろうとしています。 このとき http-get 関数に :Accept-Encoding "gzip, compress" を 指定して圧縮されて受け取ったデータを展開するにはどうすればよいでしょうか?
javaみたいにgzipのストリームがあれば便利なんですが、ストリームどころか 素のgzipのユーティリティも見当たらないようです。 知ってる方お知恵をお貸し下さい、おながいします。
- び: プロセスポートでgzipに喰わせてあげれば良さそうな気がします。call-with-output-processとかwith-output-to-processとか。あーでもレスポンスヘッダを見て動的に処理を切り替えるんだとすると、http-getのsink引数に適当なファイルポートを渡してファイルに格納して、flusher引数にヘッダを見て必要ならgzipにパイプで流し込むような手続きを渡してあげなきゃダメですね。
- ああなるほど、Transfer-EncodingがgzipかどうかはHTTPレスポンスヘッダをみないとわからないけど、http-getの:sinkは単なる出力ポートしかとらないから、ポートへの入力をgunzipすべきかどうかが、flusherに制御がわたるまで判断できないんですね。かといって、いったんどこかに全部バッファするのはいまいちな気がする。APIの問題?
- Shiro: そっか。sinkにポートじゃなくて手続きも渡せるようになってたら
どうかな。
- び: それいいと思います。手続きはパーズ済みのヘッダリストと、レスポンスボディが読み出せるポートを引数にとって、ヘッダを読み込み、パーズし終わったところで呼ばれる、と。
- age。でもホントはgzipの面倒、http-getが全部見てくれたほうがよくない? http-getみたいなお手軽な手続きを使う理由は、送信のためのエンコーディングがどうなっているかを気にしないためだから。少なくともchunkedエンコーディングについては勝手に処理してくれているし、それを意識していないでしょう。とはいえそのほかの理由でヘッダを知りたいことがあるかもな〜。微妙。
- Shiro: まー良く使う場合についてはhttp-getが面倒を見る方向が よろしいでしょう。そのうちutil.compress.gzipは作るつもりだったんで、 それからってことで。
- び: 個人的には「全部面倒見てくれてもいいけど、生のボディをいじる自由も与えてね」という感じでしょうか。ただ、そうやってオプションが増えていくのはいやな感じなので、sinkに手続きを渡せればgzipの面倒を見てくれなくても十分OKです。
Scheme学習に関して
Schemeが最強って本当?
どーゆう風に強いの?
- 他の言語と比べてということなら、それぞれの言語にいろんな特徴があるわけだから、一次元的に順序をつけられるものではありませんね。ある人にとってScheme が最強のツールであるということはあると思います。
- なるほど、確かにそうですね。それでは、Schemerの皆様が考える、「Schemeってここが素敵」「Schemeのここが好き」っていう点は何でしょうか?(人によって少しずつ違うとは思いますが。。)
- skimu: まぁSchemer's Wayでも読んでみてください。 僕的には入手できる情報の S/N 比がとても高いところが気に入ってます。たとえば、amazon.com で Java の本検索すると出てくるのは粗悪品ばかりですが、 Scheme では粗悪品を見つける方が大変。Scheme は情報すくないってよくいわれるけど良い情報の量は他の言語に負けてないと思います。
- skimu: あと、息子の Carl が Sussman の弟子であった関係で Feynman が Thinking Machines で働いていたり、Wizard Book? を勉強していたなんてことも Feynman ファンの僕には重要かも。
- び: Schemeが、というよりGaucheが、という話になりますが、素のSchemeって実用には少々潔癖にすぎるところがあるのですが、Gaucheはそこを微妙に緩めることで気持ちよくプログラミングできるようになっている気がします。その緩め方/緩め所が絶妙。
Common Lisp?と比べると長所・短所は何でしょうか?
Shiro: これは長くなりそうな話題だな。議論がおもしろくなってきたら 別ページに移すかも。
私はSchemeラヴな人間なので、言語としてはSchemeの単純さと統一性を長所と考え、 Common Lisp?の仕様の大きさや過去をひきずったライブラリを短所と考えます。 一方、処理系の充実度、安定度ではCommon Lisp?の方に一日の長があり、 例えば業務でシリアスなアプリケーションを書く場合、私は今なら Common Lisp?を選びます。ネイティブコンパイルがちゃんと出来て、 それをかりかりチューニングできて、デバッガやプロファイラなどのツールが 充実してて、という環境は重要です。
かつてはCommon Lisp?の方がライブラリも豊富だし、とも思っていたのですが、 最近はSchemeもSRFI等のおかげでライブラリが充実しつつあり、さらに 自分が欲しいと思ったライブラリをGaucheに実装している関係上、 「欲しいものにすぐ手が届く」感はGaucheでプログラミングしている時の方が 強くなっています。もっともGaucheのライブラリはまだ全然足りませんが。
関数型ってなに?
C言語の関数とは別物?
- 別物です。
- も少し詳しく言うと、C言語の「関数」にその関数が実行される「環境」を くっつけたものになります。 なんでもλあたりも 参考になるかもしれません。