トップページ⇒tech
72コメント24KB

Scheme使いの皆さまへ

■ このスレッドは過去ログ倉庫に格納されています
0001デフォルトの名無しさんNGNG
Scheme使いの皆さまへ、聞きたいことがあります。
lambdaの使い方ぐらいはわかるCプログラマにわかるように
call/ccを説明してくれませんか?

プログラミング言語 SCHEMEとか読んだのですがさっぱりです。
call/ccを呼び出すと現在実行中の位置の情報が渡されて
それを呼び出すとその位置へ戻る。とかなのかなと思いますが、、
おおっ!!っと来ないんです。。。

0002デフォルトの名無しさんNGNG
call/cc = setjmp + longjmp
0003デフォルトの名無しさんNGNG
>>1
大域gotoです。(たぶん)
様はC言語のsetjumpとlongjumpです。(たぶん)
私も最近Schemeを勉強中なのでよくわからないです。
日本語に訳す(?)と「継続」ですか。余計わからないです。
こういうときにcall/ccを使え!っていう様な適切なサンプルがあれば良いんですが。
00043NGNG
>>2
'='で括って言い切ってますが、ホントですか?
知っておられるのならサンプルを出していただけると助かります。
0005デフォルトの名無しさんNGNG
Schemeで作られた凄いプログラムがオンラインでありましたら教えてください。
0006デフォルトの名無しさんNGNG
今後展開期待上
0007デフォルトの名無しさんNGNG
なんで最近になってSchemeの日本語の本が出版されたの?
と言ってもこの二三年で二三冊みたいだけど。
0008デフォルトの名無しさんNGNG
>>7
GNUプロジェクトのおかげでしょう。
Guileだっけ?
00091NGNG
>>2
>>3
でも、call/ccはsetjmp,longjmpとは違って
スタックが深くなる方向へもジャンプできると聞いてるんですが、、、

うーむ。。誰か説明してください。
0010デフォルトの名無しさんNGNG
call/cc実行する関数(call/ccの引数)の戻り値を引数とするクロージャを作っ
て、call/ccの引数である手続きに渡す関数です。といわれてもよくわからな
いと思うので例をあげますと、

(define (bar x)
"hello")

(define (foo)
(display (call/cc bar))
)

のcall/ccは、とりあえず

(lambda (y) (display y))

というクロージャを作って、それをbarに渡された関数に渡します。barではこ
のクロージャがxにバインドされますが、barでは渡された引数を無視して
"hello"を返しているので、これがcall/cc呼び出しの値になって

"hello"

と表示されます。

一方、barを

(define (bar x)
(x "hello")
)

とすると、xにバインドされたクロージャを呼び出すことになります。ただし、
call/ccの引数として与えられた手続き内で渡された継続を実行するとそれ以
降はキャンセルされるので、「呼び出す」というよりは「ジャンプ」に近いも
のになります。この場合、渡された継続に"hello"を渡しているので、それが
call/cc呼び出しの値となって、結局また

"hello"

と表示されます。
0011デフォルトの名無しさんNGNG
どうやらcall/ccを使えば、柔軟なまるちスレッドモドキが作れる様です。
継続の区間をハードウェアスタックに持つか、ヒープに持つかで実行速度に影響が出そう。
ハードウェアスタックを使う実装の場合は継続の切り替えでスタック領域のコピーを行なう。
CPUの機能でスタックを一瞬で切り替えられる環境なら別だけど、移植性は低い。
ヒープに持つ実装の場合は単にプログラムカウンタが移動するだけ。移植性は高い。
・・・という推論は正しいですか?
0012デフォルトの名無しさんNGNG
あ、しかし継続をヒープの方に持つ方が遅そう、ということね。
00131NGNG
>>10
なるほどー。

call/ccに渡すlambdaが呼び出される時に
call/ccを呼び出した時の継続(??スタックとかの状態と考えればいいのかな...)が
渡されて来る。
そして、もし、それを呼び出すと、その継続の所にジャンプして
呼び出した時の引数が返される。
というわけですか????

こんなの書いてみました。
(define call/cc call-with-current-continuation)

(define print (lambda args (for-each display args) (display "\n")))

(define bar (lambda (x) "こんにちは"))
(define bar2 (lambda (x) (x "現在の実行を停止してこの文字列を評価した結果とします") "こんばんは"))

(print (call/cc bar))
(print (call/cc bar2))

関数から戻るのにも使えそうですね。。。
(print (call/cc (lambda (return) (return "関数から戻ります。") "本当の戻り値")))

mywhileなんてのも作れそうです。
00141NGNG
これが内側へジャンプするcall/ccでしょうか?
なんかすごいなぁ。。。
しかし内部でどうなってんのかさっぱりです。

(define jump-to-func-in ())

(define func-hoge
(lambda ()
(print "こんばんは。")
(call/cc (lambda (x) (set! jump-to-func-in x)))
(print "私は今、Schemeのcall/ccの勉強をしています。")))

(func-hoge)
(jump-to-func-in #f) ;引数に意味はない???
00151NGNG
>>11
call/ccの実装がどうなってるのかとても興味あります。
0016デフォルトの名無しさんNGNG
継続も含めて、すべてのオブジェクトはcons cellのみで表現出来る。
シンボルや文字列とかは別にするけどね。
0017デフォルトの名無しさんNGNG
vectorも、単に連続する(インデックス参照可能な)cons cell。
0018デフォルトの名無しさんNGNG
listでなくvector?>17
0019デフォルトの名無しさんNGNG
R5RSにvectorのインデックシングは一定時間でできなきゃいけないってのが
あったような気がするな……
00201NGNG
ちょっと思ったのですがSchemeだと
C言語で言う関数からのreturn(return 10;とか。)は
(define call/cc call-with-current-continuation)
(display (call/cc
(lambda (return)
; return 10;
(return 10))))
こんなのでそれっぽく出来ますがLispじゃどうするんでしょう?
00211NGNG
>>16
こんな感じですか?

typedef struct
{
ObjectType type;

union
{
int num;
char *str;
Symbol sym;

struct
{
Object *car;
Object *cdr;
} cons;
} u;
} Object;

consのcarとcdrを使えば配列、連想配列とかも実現出来ますね。

それと、最近知ったのですがA-listというのは
連想配列のことをらしいです(始めは何のことかわからなかった(EmacsLispのマニュアルで知りました))。
0022デフォルトの名無しさんNGNG
>>20
return っていうか、最後に評価した値が関数の値になる。
;; elisp しか知らないから間違いだらけかもだけど。
こんな感じ。
(defun foo (flag)
(if flag
"true"
"false"))

あと、局所脱出での return ってんだったら、throw catch ってのを使う。
(catch 'loop
(while t
(when (test)
(throw 'loop "escape!"))))



0023デフォルトの名無しさんNGNG
質問です。
Schemeインタプリタで、だらだらと記述した全てのスクリプトをファイルに保存したいのですが、
そういった事は標準手続きで可能でしょうか。
その保存したファイルを次回の立ち上げ時に読み込ませるとあら不思議、以前と同じ状態になりました。
ってのが良いんですが。
002423NGNG
あ、環境はR4RSのSCMって処理系です。
00251NGNG
>>23
(transcript-on filename)
(transcript-off)
ってがあるみたいですがGuileでは使えませんでした。

Lispの動的さを利用して、、
(while 1
(set! tmp (read)
;ファイルに書きだす
(eval tmp))
とか。。。
002623NGNG
なるほど。transcriptの実装を調べてみます。
002723NGNG
SLIB/trnscrpt.scmにありました。
002823NGNG
動きました。なかなか面白いですね。
77行足らずでこういう事が出来てしまうのか・・・。
0029デフォルトの名無しさんNGNG
schemeの話題の日本語のMLや掲示板ってありませんか?
0030ageNGNG
ge
0031デフォルトの名無しさんNGNG
pretty print?
0032デフォルトの名無しさんNGNG
全ての外部宣言のシンボルのリストとpretty printがあればできるのかな?
一般的な方法はどうなんだろ。
0033デフォルトの名無しさんNGNG
29>schemeの話題の日本語のMLや掲示板ってありませんか?

僕も schemeに興味あります。(使えませんが...)

こんな webページがあったりしますね。
http://www.sci.toyama-u.ac.jp/~iwao/Scheme/

MLとかは無いのかなぁ?
0034デフォルトの名無しさんNGNG
>33
GIMP系の日本語MLならあったような・・・。(GIMPはGuileを使ってる)
さっき軽く検索エンジンかけたけど、Scheme系の話題ってほんと少ないね。
0035デフォルトの名無しさんNGNG
portとかcall/ccやらはともかく、
データ表現としてのセルの構造はかなり便利だね。
早速自作ライブラリに組み込んだよ。
car
cadr
caddr
cadddr
:
assq
assv
assoc
003633NGNG
そういえば、SICPに関するMLはありました。
0037デフォルトの名無しさんNGNG
SICPって「計算機プログラムの構造と解釈」?
0038デフォルトの名無しさんNGNG
だれかSchemeをSchemeで書いてくれ
0039デフォルトの名無しさんNGNG
中置記法の式を1行読み込んで、S式に直すのも誰かやって〜
例:
f(x) + 2 + -1;
↓
(+ (f x) 2 -1)
0040デフォルトの名無しさんNGNG
39できたら、定数畳み込みもやってん。
(+ (f x) 2 -1)
↓
(+ (f x) 1)
0041デフォルトの名無しさんNGNG
Landin の 'The Mechanical Evaluation of Expressions' の原文
どっかにありませんか?
0042デフォルトの名無しさんNGNG
昔某大学の授業でSchemeのコンパイラを作らせたことがあるなあ。

>>38
call/ccを含んだコアの部分くらいなら簡単だけど、R5RSフルセットは
面倒なのでパス。

>>39-40
宿題は自分でやろうね

>>41
これはまたクラシックな。SECDマシンの論文だよね。たしか1964年の
Computer Journalじゃなかったっけ。こんなのオンラインであるのか
なあ。どっかの大学の図書館に頼むのが手っ取り早いと思うけど。
0043デフォルトの名無しさんNGNG
>>42
(eval (read))
じゃだめなの?
0044いつでもどこでも名無しさんNGNG
R4RSだと evalなかったからそうは書けなかったんだけどねえ..
004539>42NGNG
宿題とか決め付けてかわしてんなよヴァカボケ!
せっかくネタ提供してんのに終っちまうじゃん
授業教えてたんならこの程度ささっと書いてくれよ〜
0046デフォルトの名無しさんNGNG
つか、トークンに f(x) とか入ってると字句解析がめんどくさそう。
もう字句解析はできたと仮定して、
((f x) plus 2 plus -1)
というリストから変換するなら、わりと簡単にできるかな。
中置記法のカッコをなにか別の記号で置きかえといたほうがいいので、
たとえば (1+2)*3 なら
(leftparen 1 plus 2 rightparen asterisk 3)
に変換されるとか、そんなふうにとり決めてやってみようか。
004739NGNG
f(x)の'()'はC言語のそれと同じく後置演算子。xはその引数
-1の'-'は単項演算子(前置)とする。
当然ながら二項演算子'+'の優先順位は単項演算子よりも低い。

解析フロー
   | f ->シンボル
   | () ->後置演算子:関数呼出
   |   (引数解析開始)
   |    x ->シンボル
   |   (引数解析終了)    ;(f x) 確定 (push1)
   | + ->二項演算子(左結合)
   | 2 ->定数         ;(+ push1 2) 確定 (push2)
     | + ->二項演算子(左結合)
     | - ->単項'-'
     | 1 ->定数        ; (+ push2 -1) 確定 (push3)
     ↓

この後、push2とpush3の先頭プリミティブの'+'は優先順位が同じなので展開し、
(+ (f x) 2 + -1) が得られる。
004839NGNG
違う。
(+ (f x) 2 -1) が得られる。
004939NGNG
さらにリスト解析し、同じ階層に定数が複数見つかったら、
その先頭の演算子で畳み込むと
(+ (f x) (+ 2 -1))
↓
(+ (f x) 1) が得られる。

Cなどの手続き型言語での実装なら直ぐに思い付くけど、
こういった処理をScheme流で奇麗に書くとするとどうする?
とりあえず
単項演算子は前置+,- 後置()のみ
二項演算子はSchemeにも存在する+,-,*,/ など。
シンボルはSchemeの字句解析で得られる物で良い。
(parse '(f (x) + 2 + -1)
=> (+ (f x) 1)
0050名前ついてますか?NGNG
parseと定数畳み込みは分けたほうがいいよーん。って気がする。
つーか、単なる compilerの問題じゃ。
LL parsingが楽そうだけど、adhocな演算子優先順位解析も好き。
0051デフォルトの名無しさんNGNG
つかさ、解析木作ったら後はどうトラバースするかだけだろ。
0052デフォルトの名無しさんNGNG
>51
解析するとき普通、解析木を全部作っちゃうんですか?
作りつつ、traverseしつつ、ですよね?
005339NGNG
>>50-51
構成は既に知ってるんだけど・・
Schemeでどうやるかが知りたいの。
005439NGNG
とりあえず解析用のスタック
(define stack '())
(define (stack-push obj)
(set! stack (cons obj stack)))
(define (stack-clean)
(set! stack '()))
(define (stack-pop)
(if (null? stack)
(begin
(display "stack is empty\n")
#f)
(begin
(set! stack (cdr stack))
stack)))
Schemeで名前の解決はどうやるんだろう。
例えば、上のstack-push/popを別インスタンスに対して行いたい場合。
いちいちインスタンス指定して
(stack-push 'stack1 obj)
ってやるしかない?
005539NGNG
あ、call-withなんたらと同じ方法でいけるかな・・
005639NGNG
うげぇ、インデントされてないじゃん・・>54
005739NGNG
インデント付け直したスタック
(define stack '())
(define (stack-push obj)
 (set! stack (cons obj stack)))
(define (stack-clean)
 (set! stack '()))
(define (stack-pop)
 (if (null? stack)
  (begin
   (display "stack is empty\n")
   #f)
  (begin
   (set! stack (cdr stack))
   stack)))
lispのインデントって普通2文字?
005839NGNG
さて、単項演算子から。
(define (unary-expr tokenlist)
 (define unary_op
  '(+ -))
 (let ((unary (memq (car tokenlist) unary_op)))
  (if (not unary)
   (primary-expr tokenlist)
   (let ((result (unary-expr (cdr tokenlist)) ))
    (stack-push (cons 'unary (cons (car unary) '())))
    result))))
005939NGNG
(define (primary-expr tokenlist)
 (let ((primary (car tokenlist)))
  (cond
   ((or (symbol? primary) (number? primary))
    (let ((result (post-expr (cdr tokenlist)) ))
     (stack-push (cons 'primary (cons primary '())))
    ))
   ((pair? primary) (expr primary))
   (else #f)
  )))
006039NGNG
なんかおかしいなあ・・
まあいいか。
006139NGNG
(define (post-expr tokenlist)
(cond
((and (pair? tokenlist) (pair? (car tokenlist)))
(begin
(stack-push (cons 'func (cons (car tokenlist) '()) ) )
(cdr tokenlist)
))
(else tokenlist)))
006239NGNG
なんかSchemeで書く意味が無いな〜
この程度だったらCでフロントエンド作った方が良いな・・
0063デフォルトの名無しさんNGNG
所で、日本語に対応したSchemeってあるのかな?
(string-ref "あイいん" 0)
=>#\あ
(string-ref "あイいん" 3)
=>#\い
が返る様な実装の。
それにしても・・使ってる奴誰もいない様だな(泣
0064デフォルトの名無しさんNGNG
call/ccって要するにスタックを保存するset/longjmpだろ?
保存するから内側にも飛んでいける。
0065デフォルトの名無しさんNGNG
>>64
そんな話題とっくに終ってるのに。
・・おっと失礼
0066デフォルトの名無しさんNGNG
>>63
開発途上みたいですが
http://www.shiro.dreamhost.com/scheme/gauche/index-j.html
はどうですか?


% cd src
% ./gosh
gosh> (string-ref "Scheme処理系" 8)
#\系
gosh> (sqrt -5)
0+2.23607i
gosh>
0067デフォルトの名無しさんNGNG
>>66
おあ、知ってる人がいた・・
紹介ありがとうございます。
こういう実装をする場合、文字列も2バイト(マルチバイト)で管理してるのだろうか。
そうしないと参照するたんびに頭からなめていかなきゃならないし。
でも半角だけだったら領域がもったいないかなあ・・

(string-ref 8)
(make-string "Scheme処理系")
-------------------------------
|S |c |h |e |m |e |処|理|系|\0|・・・・
-------------------------------
|2 |2 |2 |2 |2 |2 |2 |2 |2 |2 |←各文字の長さ(Byte)
-------------------------------
             ↓
(make-character)
             |系|
             #\8c6e

文字列に対してのみ(内部的に)ポインタ型を作れば効率良いかな?
0068デフォルトの名無しさんNGNG
あ、リンク先見たらマルチバイト形式って言ってますね。(汗
うーん、しょうがないか。
日本語を自然に扱えるメリットの方が大きいし・・
0069デフォルトの名無しさんNGNG
(isjanji1 char)なんてやりたくないしね・・。
0070デフォルトの名無しさんNGNG
isjanji->iskanji
0071デフォルトの名無しさんNGNG
フラグでも用意して、半角だけだったら1バイト、漢字含んでたら2バイトとか
切り分けた方が良いかな。
でもstring-set!されたら・・アウトだ。
そのときは新たに拡張文字列を生成するかな。
あ、でもそうするとそれを参照してるセルを全部付け替えなきゃならないからだめだ。
0072デフォルトの名無しさんNGNG
あー、でも対ファイル操作の時に全部2->1変換してかなきゃなんないじゃん。
かなりのオーバーヘッドの様な・・めんどうだなあ。
■ このスレッドは過去ログ倉庫に格納されています