CommonLisp Scheme Pa ..
[2ch|▼Menu]
705:ミミ
04/10/11 16:57:33
Unicode (UTF-16) ベースの Scheme 処理系を作る場合、
R5RS の read-char 手続きは、ポートから 2 バイトを読み取って Unicode 文字を返すのが
自然ですよね?1バイト読み取りのためには read-byte のような拡張手続きを用意して。
既存コードとの互換性がなくなると思いますけど。。。

706:デフォルトの名無しさん
04/10/11 17:18:08
read-charとかの関数の他に読み込む文字種を指定できるといいんじゃないかな

(set-input-char-set 'ネコミミモード)

(with-input-char-set 'ネコミミモード
(read)
)
=>ネコミミモードでーす

とか


707:デフォルトの名無しさん
04/10/11 17:53:02
read-char は「文字」を返すだけで、それがどんなエンコードだとかどんなサイズであるかなどは R5RS では規定されていない

708:デフォルトの名無しさん
04/10/11 17:53:35
CMUCL は,C++ の .so を呼べないのでしょうか?

//////////////////// a.cc
int foo (int a, int *b)
{
  int c;
  c = *b - 10;
  *b = a * 10;
  return c;
}
////////////////////

# g++-3.4 -shared -o a.so a.cc

;;;;;;;;;;;;;;;;;;;; a.lisp
(use-package "ALIEN")
(use-package "C-CALL")
(load-foreign "./a.so")

(def-alien-routine "foo" int
  (a int) (b int :in-out))

(foo 13 3)
;;;;;;;;;;;;;;;;;;;;

# lisp -load a.lisp -eval '(quit)'

とやると,(長いので分割)

709:708
04/10/11 17:56:00
; Loading #p"/xxxxx/a.lisp".
;;; Opening shared library ./a.so ...
;;; Done.

Undefined foreign symbol: "foo"
   [Condition of type KERNEL:SIMPLE-PROGRAM-ERROR]

Restarts:
  0: [CONTINUE] Return NIL from load of "a.lisp".
  1: [ABORT   ] Skip remaining initializations.

Debug  (type H for help)

(KERNEL:UNDEFINED-FOREIGN-SYMBOL-ERROR-HANDLER "<error finding name>"
                        #.(SYSTEM:INT-SAP #x3FFFC7E8)
                        #<Alien (* #) at #x3FFFC480>
                        (14))
Source: Error finding source:
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:code/interr.lisp.
0] (quit)

710:デフォルトの名無しさん
04/10/11 18:58:09
a.soのシンボルをリストアップしてみりゃわかるよ

711:708
04/10/11 19:20:52
>>710
はじめてナノメートル nm しました.
うまくいきました! ありがとうございます.
今から >>710 に萌えます (;´Д`)


gcc で作ったやつと比較して,トンでもないシンボル名になっていました.
でもその名前で lisp コードに書き,アンダーバー `_' をハイフン `-' に変えて
使ってみると,正常に動きました.
シンボル名が変にならないようにする方法はこれから調べます.

712:デフォルトの名無しさん
04/10/11 19:35:31
>>705 see srfi-56


713:デフォルトの名無しさん
04/10/11 22:27:42
call-with-current-continuationについて教えてください.
多分誤解しているのだと思います.
たとえば,
(call/cc (lambda (k) (* 20 (k 30))))
   => 30
になると思います.
これを,
「kというprocedureがactiveになった時点で,それに
渡されるべき30という引数が返されたのである」と
理解していましたが,これは誤りなのでしょうか?
この考え方をもって既存のコードを読むと,実際に
返される値と異なる結果になってしまいます.
どこがいけないか,ご指摘願えますでしょうか.
よろしくお願いいたします.

714:デフォルトの名無しさん
04/10/11 22:55:04
kという手続きがxを引数に呼び出されると、
対応するcall/ccの返却値としてxが戻される。
もちろん後続の処理もcall/ccが普通に戻った場合と同じように続く。

715:デフォルトの名無しさん
04/10/11 23:08:44
>>713
「procedureがactiveになる」ってどゆこと?

716:デフォルトの名無しさん
04/10/11 23:24:50
>>713
Gaucheでの出力だけど...

(define m '())
(list 1 2 3 (call/cc (lambda (k) (set! m k))) 4 5 6)
--> (1 2 3 #<subr continuation> 4 5 6)
m
-->#<subr continuation>
(m 999)
--> (1 2 3 999 4 5 6)
(list 1 2 3 (+ 100 (call/cc (lambda (k) (set! m k)))) 4 5 6)
--> 継続と100は足せないというエラー
(list 1 2 3 (+ 100 (call/cc (lambda (k) (set! m k) 100))) 4 5 6)
--> 1 2 3 200 4 5 6
(m 500)
-->1 2 3 600 4 5 6
つまり継続を呼び出すときの引数でそれを作った(call/cc ...)が丸ごと置き換わると考えるととりあえずよいかもしれない。

うまい説明になってないかな^^?
(list 1 2 3 (+ 100 (call/cc (lambda (k) (set! m k)))) 4 5 6)
--> 継続と100は足せないというエラー
(list 1 2 3 (+ 100 (call/cc (lambda (k) (set! m k) 100))) 4 5 6)
--> 1 2 3 200 4 5 6
(m 500)
-->1 2 3 600 4 5 6
つまり継続を呼び出すときの引数で(call/cc ...)が丸ごと置き換わると考えるととりあえずよいかもしれない。

あまり説明になってないかな^^?

717:デフォルトの名無しさん
04/10/11 23:26:16
>> 715

713です.
「procedureが呼び出される」に訂正.

>> 714

ありがとうございます.
ということは根本的に間違えた考えではないと理解して,
コードを追い直しています.


718:716
04/10/11 23:27:55
書き込みの途中に継続を呼んでしまったようだ... ごめん m(_ _)m


719:デフォルトの名無しさん
04/10/11 23:45:56
>> 716

ありがとうございます.
実際に追っているコードを示した方が早いので,そうします.
たとえば,Kent Dybvig プログラミング言語Scheme (日本語版)
p.61に,call/ccのサンプルとして

(let ((x (call/cc (lambda (k) k))))
(x (lambda (ignore) "hi")))
=> "hi"

というのがありますが,これは結局
(call/cc (lambda (k) k) (lambda (ignore) "hi"))
と等価(でよいのでしょうか)ですね.
この式内の引数 (lambda (ignore) "hi")はprocedure
だから,答は本にあるように "hi" じゃなくて,その
procedure自体(#<user-defined-function> みたい)
になると思うのです.
しかし確かにguileなどでは本の通り "hi" と出ます.
問題はこのprocedure自体を返すのか,evalった結果を
返すのかの違いにあるのだと思いますが、「eval忘れ」
がどこかにあるのでしょうか?
ちなみに kawa ではエラーとなり確認できません.


720:デフォルトの名無しさん
04/10/11 23:47:02
>>711
>シンボル名が変にならないようにする方法はこれから調べます.

そいつはC++を使ってる限り無理だ。関数の多重定義を実現するために、
シンボルには型情報がくっつくようになってる。

721:デフォルトの名無しさん
04/10/11 23:47:17
継続(continuation)のメモ
URLリンク(www.opengroupware.jp)

722:デフォルトの名無しさん
04/10/12 00:03:24
719 です.

>問題はこのprocedure自体を返すのか,evalった結果を
>返すのかの違いにあるのだと思いますが、「eval忘れ」
>がどこかにあるのでしょうか?
は間違いですね。
問題はこのprocedure自体を返すのか,呼び出した結果を
返すのかの違いにあるのだと思いますが、「呼び出し忘れ」
がどこかにあるのでしょうか?
の意味です。


723:708
04/10/12 00:04:17
>>720
なんと,,,そうですか.今ちょっと調べ疲れていたところでした.
どうもありがとうございました.
まあ lisp コードなんてすぐ修正できてしまうからいいんですよね!

724:デフォルトの名無しさん
04/10/12 00:13:42
>>719
最初に (x (lambda (ignore) "hi")) を評価するとき x は継続であり、それは
「最初の (call/cc (lambda (k) k))」から戻ってきて、返却値を x に bind し、let の body を評価する」
という継続である。
なので、これを評価すると最初の (call/cc (lambda (k) k)) から (lambda (ignore) "hi") が返却され、
これが x に bind され、(x (lambda (ignore) "hi")) が再度評価されることになる。
二度目に評価されるとき、x には (lambda (ignore) "hi") が bind されているので、これはつまり
((lambda (ignore) "hi") (lambda (ignore) "hi"))
ということになり、最終的に "hi" が返却される。

725:デフォルトの名無しさん
04/10/12 00:34:47
訂正
719の中の置換したコードは
((call/cc (lambda (k) k)) (lambda (ignore) "hi"))
のつもりでした.

>>724
ありがとうございます.最終的に
((lambda (ignore) "hi") (lambda (ignore) "hi"))
に置換されるのがミソのようですね.
まだまだSchemerになれていないので,724をなぞって
ゆっくり考えます.ありがとうございました.


726:デフォルトの名無しさん
04/10/12 01:59:29
>>723
extern "C" {
ネコミミモード
};

727:716
04/10/12 02:43:54
おれが安易に「丸ごと置き換わると考えるととりあえずよいかもしれない」なんて書いちゃったのは良くなかった。ごめんなさい m(_ _)m

置換で考えると上手くない場合もあるんだ。
例えば、

(let ((c 100))
(let ((x (call/cc (lambda (k) k)))) ;<- @1
(let ((c 200))
(x (lambda (n) (+ 1 c)))))) ;<- @2
--> 201

の (call/cc (lambda (k) k)) を単純に (lambda (n) (+ 1 c))に置き換えて考えてしまうと

(let ((c 100))
(let ((x (lambda (n) (+ 1 c))))
(let ((c 200))
(x (lambda (n) (+ 1 c))))))
--> 101

となって結果が合わなくなってしまう。

これは@2のlambdaで作られた#<closure>は@1に送られるけど、その環境は@2を持っているということが単純な置換で考えると消えてしまうからなんだ。
というわけで、716は忘れてください ^^;

728:デフォルトの名無しさん
04/10/12 09:57:42
スコープだけじゃないね。
(let ((x 0))
  (let ((c (call/cc (lambda (x) x))))
    (set! x (+ x 1))
    (c (lambda (y) x))))

(let ((x 0))
  (let ((c (lambda (y) x)))
    (set! x (+ x 1))
    (c (lambda (y) x))))
はちがうし。
あたかも置き換えたかのようにジャンプするというか・・・。

729:デフォルトの名無しさん
04/10/12 20:10:09
713です.
多くの丁寧な follow-up をありがとうございます.
call/ccはSchemeらしいところだと思うのですが
それだけ奥が深いですね.
何せ midnight programmer なものですから,
これから寝るまで頭をひねって考えます.
今後ともよろしくお願いします.

730:デフォルトの名無しさん
04/10/12 20:27:57
schemeの継続って、プログラムの最小限要素をくくりだすのはいいが
そのためにやたらコストの高いものを導入してしまったという感じがする。
Smalltalkでの何でもかんでもオブジェクトとメッセージに近いものがある。
継続そのものが便利なときもあるけど、なくてもほとんど困らない。


731:デフォルトの名無しさん
04/10/12 20:31:45
継続抽出は銀の弾丸です


732:デフォルトの名無しさん
04/10/12 20:44:17
ほんとにそうなら継続のある言語がとっくに天下をとってると思う。


733:デフォルトの名無しさん
04/10/12 21:44:11
悪い方が良い法則。

734:デフォルトの名無しさん
04/10/12 22:35:38
おまえらただ継続継続いいたいだけちゃうんかと。
継続なんて極力使わない方がいい。
gotoがよりパワフルにそしてわかりにくくなったものだから。
銀の弾丸どころかソフトウェアエンジニアリング的には狼男だ。

735:デフォルトの名無しさん
04/10/12 22:39:36
例外処理のような上手い使い方ならいいんだけどね

736:デフォルトの名無しさん
04/10/12 23:02:34
ちんちんかゆいーー!

737:デフォルトの名無しさん
04/10/12 23:03:46
gotoが悪ならthreadなんて巨悪だな

738:デフォルトの名無しさん
04/10/12 23:04:48
>>736
cut しろ


739:デフォルトの名無しさん
04/10/12 23:35:23
>>737
threadとgotoは比べるもんじゃないだろ。
threadは基本的に代えがきかない。

740:デフォルトの名無しさん
04/10/13 03:12:19
The Seasoned Schemerの継続の説明らしい章を読んでます。
で、質問なんですが、この本の中では継続を「call/cc」じゃなくて
「letcc」(schemeの場合)、「throw」「catch」(CommonLispの場合)で
説明してるんですけど、これらは等価なものなんですか?


741:デフォルトの名無しさん
04/10/13 03:30:06
>>740
shiroさんのなぜ Scheme には return がないのか
URLリンク(www.shiro.dreamhost.com)
を読むとその辺がわかるかも。

742:デフォルトの名無しさん
04/10/13 10:25:44
continuationとgotoは比べるもんじゃないだろ。
continuationは基本的に代えがきかない。


743:デフォルトの名無しさん
04/10/13 14:30:21
はっきり言って、continuation≒gotoだよ。認めたくないだろうけどね。
gotoでcontinueもbreakもreturnも出来るじゃん、美しい!って悦に入ってるのが
schemer

744:デフォルトの名無しさん
04/10/13 16:12:52
だがそれがいい

745:デフォルトの名無しさん
04/10/13 16:21:01
>>742
おうむ返しだけだと馬鹿にしか見えませんよ。

continuationはやろうと思えばいくらでも書き換えられる。
書き換えたらフラグだらけになったり、
巨大な関数になったりしてして醜いから、使う、というものでしょ。
もちろんそういうときに使うのはいいけど、
できるときはwhileとかforとかmacroにしたほうがいいだろう。

threadはthreadを使わずに同じ機能を実現することが難しい。
特にthreadのどれか一つが死んだりしても他のthreadが監視しておいて
処理できる、という能力はエミュレートできない。
エミュレートだとスケジューリングがうまくいくように一単位の
処理時間をコントロールできないといけないし。

746:デフォルトの名無しさん
04/10/13 21:25:49
ケイゾクに恨みでもあるんですか?

747:デフォルトの名無しさん
04/10/13 21:57:42
色んな意味でコストがかかるところ。


748:デフォルトの名無しさん
04/10/13 22:54:32
>>746 ないけど?

749:デフォルトの名無しさん
04/10/14 00:13:10
threadだって高速に処理切り替えてるだけだから、
gotoで出来ないことはない。

750:デフォルトの名無しさん
04/10/14 00:39:00
スレッドと継続は全く別個の独立した概念であり、対立するものでもないのに何をムキになってるんだろう。


> 書き換えたらフラグだらけになったり、
> 巨大な関数になったりしてして醜いから、使う、というものでしょ

それこそ継続という概念が primitive なものであるということであり、scheme らしいところでは?
primitive なものさえあれば他の機能はそれらを組み合わせてできる。美しい。
例えば、コルーチン程度であれば call/cc で簡単に実現できる。

751:デフォルトの名無しさん
04/10/14 00:54:20
コルーチン程度しかできない、の間違いでしょ

752:デフォルトの名無しさん
04/10/14 01:06:04
Rubyの継続の実装はスレッドと同じだそうですよ
URLリンク(i.loveruby.net)

753:デフォルトの名無しさん
04/10/14 01:44:06
>>749
gotoでどうやって
> 特にthreadのどれか一つが死んだりしても他のthreadが監視しておいて
> 処理できる、という能力はエミュレートできない。
するんだ?
そしてどうやって一つの処理単位がCPUタイムを食いすぎないことを保証するんだ?
> エミュレートだとスケジューリングがうまくいくように一単位の
> 処理時間をコントロールできないといけないし。

>>750
> スレッドと継続は全く別個の独立した概念であり、対立するものでもないのに何をムキになってるんだろう。
じゃああなたが>>737>>749を説得して上げてください。

754:デフォルトの名無しさん
04/10/14 01:45:11
gotoさえ使えば、breakもcontinueもreturnもいらないのはもちろん、
スレッドも関数もいもいらない。こういう機能はgotoを組み合わせてできる。
美しい。

755:デフォルトの名無しさん
04/10/14 01:53:27
>>750
美しかろうがどうだろうが、
「継続を無闇に使うべきでない」という事実には変わりはないな。

756:デフォルトの名無しさん
04/10/14 01:54:45
アセンブラにすりゃただのジャンプ命令。
ジャンプ命令使わずにアセンブラでプログラム組めるわけが無い。
即ち、息をするようにgotoを使え。

757:デフォルトの名無しさん
04/10/14 04:40:12
引数つきgoto

758:デフォルトの名無しさん
04/10/14 08:16:21
〉〉753OSやリアルタイム制御の本みれば書いてあるよ。つーか知らないの?

759:デフォルトの名無しさん
04/10/14 08:28:20
ヘタレLisperと本物のプログラマを隔てるOSという一つの壁

760:デフォルトの名無しさん
04/10/14 08:29:07
>>736-738
Prologの話?

761:デフォルトの名無しさん
04/10/14 09:24:37
まだちんちんかゆいよーー!
なぜか皮が膨らんできてる・・・

762:デフォルトの名無しさん
04/10/14 11:39:34
>>751
ユーザモードのスレッドは、本質的にはコルーチンと同等だけど?

763:デフォルトの名無しさん
04/10/14 16:06:08
コルーチンって何?

764:デフォルトの名無しさん
04/10/14 16:18:27
>>763
URLリンク(www.google.com)

765:デフォルトの名無しさん
04/10/14 17:26:42
>>762
ユーザーモードのスレッドは必ずしもそうでないと思うけど、
ユーザーモードのスレッドライブラリはそうだね。で、それが何か?


話は変わって、
そもそもSchemeの継続ってプリミティブか?
単にCPSで書けばいいだけじゃないの?こっちはどんな関数型言語にもできるし。

766:デフォルトの名無しさん
04/10/14 17:43:15
機械語のライブラリを実行中に継続を取ってきても
きちんと動くように要請してるんじゃない?

767:デフォルトの名無しさん
04/10/14 20:38:44
>>765
なんでそこに CPS が出てくるのか意味がわからない

768:デフォルトの名無しさん
04/10/14 20:45:57
>>768
Continuation Passing Style のことだよ?

769:デフォルトの名無しさん
04/10/14 21:25:50
>>767 の言いたいことを推測。

CPSで継続を陽に扱うには、最初から全部CPSで書かなくちゃならない。
CPSで書かれていないコードから呼ばれるコードで継続を取り出したかったら
call/ccはプリミティブにならざるを得ない。

…ってなとこか?
CPSでもMonadみたいな形で継続を隠すことはできるけど、それだって
最初からそのつもりで書いてないと。

個人的には、Schemeの継続は言語の実験をするための道具って
感覚が強いな。




770:765
04/10/14 21:44:13
>>769
言語の要素のプリミティブじゃなくて、
>>750
> それこそ継続という概念が primitive なものであるということであり、scheme らしいところでは?
> primitive なものさえあれば他の機能はそれらを組み合わせてできる。美しい。
の話。

(Schemeの)継続なんて無くてもCPSで書けば、gotoでもコルーチンでも
call/ccでも(w実現できるんだから、プリミティブな(基底をなす)機能ではないんでは?
ということ。

> 個人的には、Schemeの継続は言語の実験をするための道具って
> 感覚が強いな。
同意。

771:デフォルトの名無しさん
04/10/14 22:03:36
やたら継続を美しいと賛美しているのは、つい最近大学の
講義で継続を知って嬉しくなってる厨房だよね?

772:デフォルトの名無しさん
04/10/14 22:11:31
>>771
はい。

773:デフォルトの名無しさん
04/10/14 22:26:47
四角いタイヤでも目盛がついていれば長さを測ったりできて便利かもしれんが、
それで高速道路を走るのは無理だ。

774:デフォルトの名無しさん
04/10/14 22:37:43
で?

775:デフォルトの名無しさん
04/10/14 22:42:13
ちんちんかゆいーー!

776:デフォルトの名無しさん
04/10/14 22:44:58
>>775
切っとけ。

777:デフォルトの名無しさん
04/10/14 23:38:01
>>770
だから?基底をなす機能しか使っちゃいけないなら、
ラムダだけ使えば?
理論上は統べての計算はラムダ式で可能なんだから、


778:デフォルトの名無しさん
04/10/15 00:28:51
継続がプリミティブだからエライと言い出した
のは継続厨房でしょうが

779:デフォルトの名無しさん
04/10/15 01:15:03
だからつかわなきゃいいじゃん
threadだろうがcall/ccだろうが理解してない
人間がつかうと危険なのは当たり前。

780:デフォルトの名無しさん
04/10/15 01:20:39
>>778
エライなんて誰も言ってないよ

781:デフォルトの名無しさん
04/10/15 10:39:17
>>777
基底をなす機能しか使っちゃいけないなんて誰も言ってないよ。

>>779
理解している人間が使ったって危険なんですが。
というか、誰が使っても危険なものだということを理解していない人間は
理解している人間ではないでしょうね。

782:ミミ
04/10/15 14:26:27
>> 個人的には、Schemeの継続は言語の実験をするための道具って
>> 感覚が強いな。
>同意。
私も同意。
例外処理のような代替機能があれば十分だと思う。

783:デフォルトの名無しさん
04/10/15 16:26:43
どういう場合になにが危険といってるの?実装といっしょにあげてみてよ。

784:デフォルトの名無しさん
04/10/15 17:19:01
>>782
で、言語の実験で良い結果が得られたらその度に代替機能を実装してくの?

785:デフォルトの名無しさん
04/10/15 17:29:19
>>783
危険っぽいコード
(define go #f)

(call/cc (lambda(cc) (set! go cc)))

(call-with-input-file "foo" (lambda(port) (go port)))


はわわ〜

786:デフォルトの名無しさん
04/10/15 17:32:26
>>784
パフォーマンスを上げたいなら専用化した方がいいからね。
限定的な継続にして万能な部分を切っていく。
VBのバリアント型みたいなものだよ。

787:デフォルトの名無しさん
04/10/15 17:45:17
>>784
良い結果がって言うよりさ、そもそも言語の設計で「良い悪い」を
判断するのって使ってみないとわからんわけじゃん。で、処理系
ネイティブに実装する方式だと、その処理系を使ってる人しか
試せない。だけどSchemeの場合、かなり凝ったことまで言語組み込み
のプリミティブを組み合わせで書ける。そしたら、R5RS準拠の
処理系ならどれでもその提案を試してみることができるわけだ。

こいういう場合に使われるcall/ccなんかは、むしろ提案する
言語機能の仕様記述なわけよ。ところが、Schemeの場合は
その仕様記述が動かせるプログラムになる。


788:!= 782
04/10/15 18:26:51
>>784
そう。実装してく。
whileやらbreakやらgeneratorやらに抽象化してそれを安全に使う。
call/ccはそれを作る道具であって、call/ccのスパゲッティを毎回
ほどいて、俺には解けるから危険じゃないとか言って喜ぶための
ものじゃないと思うね。
上手い抽象化を考えたりその抽象化を実装したりするのに頭をつかおう。

789:ミミ
04/10/15 18:27:43
>>784
実用上の開発では言語実装にまで遡って設計の見直しを図ることは稀でしょう。

もちろん継続があったらあったらでよいと思いますが、
C における goto よろしく、大規模な開発では原則として禁じるのが妥当ではないかと。
ソフトウェア工学上は継続は狼男だと言っている方がいらっしゃいましたが、
その視点におけるその意見には同意するということです。

>>785
それは継続の問題というよりも、
プログラミングの腕の問題という気が。。。
do でも論理エラーがあれば無限ループが書けるわけだし。

>>786
>パフォーマンスを上げたいなら専用化した方がいいからね。
>限定的な継続にして万能な部分を切っていく。
これに同意。
Scheme に継続しか用意しないというのは実用的ではないという感じがします。


790:デフォルトの名無しさん
04/10/15 19:46:48
「実用的な」制御構造がいずれも継続の上に(マクロで?)構築した
ライブラリとして書けるっていうのがSchemeの主張なんじゃないの?

791:デフォルトの名無しさん
04/10/15 19:52:56
話がループしているのは継続のせいですか?

792:デフォルトの名無しさん
04/10/15 20:06:32
じゃあ高速な継続の実装の仕方でも考えるかい?

793:デフォルトの名無しさん
04/10/15 20:35:42
>>791
gotoのせいです。

794:デフォルトの名無しさん
04/10/15 21:18:32
>>790
そう。
パフォーマンスとかが必要ならそれぞれの実装系においてライブラリ部分をCとかで実装していけばよい。

795:デフォルトの名無しさん
04/10/15 22:36:02
結局Schemeは非実用的ということですね

796:デフォルトの名無しさん
04/10/15 22:39:54
わざわざ去勢する必要もなかろう

797:デフォルトの名無しさん
04/10/16 01:39:19
CommonLispのマクロについての質問。
マクロはコンパイル時に評価を行う、ということは、コンパイルプロセスを
実行プロセスから分離することはできない、ということでOKでしょうか?
また、関数の中でマクロが定義されている場合、関数が呼び出される度に
マクロ展開(とコンパイル)が行われるのでしょうか?


798:デフォルトの名無しさん
04/10/16 01:58:24
どういう動作をすると思ってるの?

799:797
04/10/16 02:06:33
実行とコンパイルがインターリーブしていて、マクロの展開関数の中
から他の変数なんかも参照できる。その変数の値によって、展開の結果
が変わるかもしれない...というふうに"思って"います。

根本的に間違ってますか?


800:デフォルトの名無しさん
04/10/16 03:39:45
継続の話。

Kawa(Java による Scheme 実装)の継続は例外処理(try - catch)
によって実装されているね。確か大域脱出しかできなかったような気
がする(Common Lisp の block 相当)。

実際、おれの場合、大域脱出くらいでしか継続は使ったことないな。

801:デフォルトの名無しさん
04/10/16 04:37:48
>>797
>コンパイルプロセスを実行プロセスから分離する
lispには eval関数の様に実行時に式を評価する仕組みがあるので
コンパイル環境と実行環境を分離するのは難しいと思う。
でもこの話は、マクロとは関係ないような気がする。

>関数の中でマクロが定義されている場合
関数内でマクロを定義した場合の動作など考えたことが無かった。
で、やってみた。

;; 関数定義
(defun test (x)
(cond ((equal x 1) (defmacro m () 10))
((equal x 2) (defmacro m () 20))
(t nil))
(m))

;; 実行結果 ・・・ clisp の場合
(test 0) => 20
(test 1) => 20
(test 2) => 20

;; 実行結果 ・・・ xyzzy lisp の場合
(test 0) => 関数が定義されていません: m
(test 1) => 10
(test 0) => 10
(test 2) => 20
(test 0) => 20

xyzzyではマクロ展開を実行時に行っていて、clispでは関数定義時に
行っているようだ。CLtL2 的にはどうなっているんだろう?

802:デフォルトの名無しさん
04/10/16 08:26:44
重複定義でエラーが正解

803:デフォルトの名無しさん
04/10/16 09:41:37
>>801
その例は実行時までプログラムの意味が決まっていないよね?
そういうマクロはたとえ可能としても悪いマクロだと思う
(実際おれは不可能と思ってたし)
役に立つ場合って何かあるかな?

804:デフォルトの名無しさん
04/10/16 15:10:07
>>797
> また、関数の中でマクロが定義されている場合、関数が呼び出される度に
> マクロ展開(とコンパイル)が行われるのでしょうか?

コンパイルされたコードの中でのマクロ展開はコンパイル時に行われて、
実行時には再度行われない、てことになってます。
ですから、関数内でマクロを再定義するようになっていたとしても、
展開に使われる手続きはコンパイル時の環境にあるものになるはずです。
コンパイルしない場合には、何時、何度展開されるかは実装依存です。
cf. URLリンク(www.lisp.org)

>>801
xyzzy でもコンパイルすると clisp と同じ結果になりますね。
でもいまいちピンとこない結果だなあ。
(defmacro m () 20) は展開時には、つまりコンパイル時であれ関数定義時であれ、評価されませんよね?
だったら (m) は展開できない気がするんですが。勘違いしてる?

805:ミミ
04/10/16 15:19:43
> (defmacro m () 20) は展開時には、つまりコンパイル時であれ関数定義時であれ、評価されませんよね?
リーダが読み取って、最適化された内部構造体に変換するときに、
ついでに defmacro を評価してしまうという実装はあり得ると思う。
(そんな処理系を見たことがある気がする。)

その場合、最後に出てきた m の定義によってオーバーライドされてしまうので、
上記のような結果になるのでは。

806:804
04/10/16 15:34:00
>>805
あーなるほど、それなら納得いきますね。
確かに普通それで問題ないでしょうし。

807:デフォルトの名無しさん
04/10/16 16:17:58
そういうマクロはエラーにして欲しいなあ。
eval介入するならともかく、使い道なんてないでしょ。


808:デフォルトの名無しさん
04/10/16 17:47:56
Gaucheはエラーになった

809:デフォルトの名無しさん
04/10/16 18:00:35
>>808
GaucheってSchemeでしょ?
Scheme的には>>801て文法的にありえんし…
mのスコープが意味不明。

810:デフォルトの名無しさん
04/10/16 18:27:24
>>797-799
> また、関数の中でマクロが定義されている場合、
これって macrolet のことを言いたいのかなあ、とふと思った。

> 実行とコンパイルがインターリーブしていて、マクロの展開関数の中
> から他の変数なんかも参照できる。その変数の値によって、展開の結果
> が変わるかもしれない...というふうに"思って"います。

これについては、こんな例もあるかな。

(defvar *foo*)
(defmacro %foo (x)
`(,(if *foo* 'car 'cdr) ,x))
(defun foo (ls key)
(let ((*foo* key))
(%foo ls)))

として、

(let ((ls '(a b c)))
(values (foo ls t)(foo ls nil)))
=> ? , ?

とりあえず今手元にある xyzzy では、コンパイルしないと a , (b c)、
コンパイルするとコンパイル時の *foo* の値によって a , a or (b c) , (b c) が、
更に *foo* の値が未定義だと (b c) , (b c) が返ってきました。
……しかし、どうにも病的な例しか思い浮かばないなあ。

811:デフォルトの名無しさん
04/10/16 20:26:58
letがないものが条件コンパイルのイディオムとして使われて
てもよさそうだと思ったんだけど、そうでもないのかな?

812:797
04/10/16 22:43:41
コンパイル時の環境・値をキーワードにしてHyperSpecを必死になって
読んで、部分的な理解を得ました。

マクロ展開のようなコンパイル時に実行される式はevaluation env.のもと
で評価され、そうでない本当に実行時に実行される式はrun-time env.のもと
で評価される。で、evaluation env.とrun-time env.が同一である保証は
無いと。
私の場合、この二つが同一と仮定していたので、797の発言になったわけ
ですね。実際は、run-time env.の中の束縛とは異なるかもしれない束縛を
evaluation env.に加えてやることでコンパイルプロセスを実行プロセスから
分離していると。
後は、コンパイルの過程でevaluation env.がどのように構築されていくのか
が解ればいいのですが、これも理解に時間かかりそう。

>>810
まだ文法に自信がないですが、こんなのなら病的ではないかと。
期待通りには振る舞わないみたいですが。

(defun debug-print (form value) ...)
(defvar *do-debug*)
(defmacro debug-value (form)
(if *do-debug*
(list debug-print `(quote ,form) form)
form))


813:Ruby >>>>>>>>>>>>>>>Scheme
04/10/17 06:09:54
Scheme を駆使して、「普通のやつらの上を行け」るのは、(少なくとも現状では、そして恐らく永久に)ごく一部のプログラマであって、
普通のプログラマが少しでも「普通のやつらの上を行け」るのは、Ruby になるような気がしています。
URLリンク(jp.rubyist.net)

814:デフォルトの名無しさん
04/10/17 06:26:35
結局のところ、CommonLispでマクロを安全に使うには、
(1)トップレベルで定義する。関数内では定義しない。
(2)同名のマクロの再定義はしない。
(3)スペシャル変数等の環境で動作が変わるマクロは書かない。
ということですか?

815:デフォルトの名無しさん
04/10/17 10:15:21
ものをちゃんと理解する、が一番じゃないかな。
まあ理解できないからこそ安全に〜とか言ってるんだろうけど。


816:デフォルトの名無しさん
04/10/17 11:04:12
>>815
それなりの指針を与えることは、有益だとおもうけどどうかな。
だれだって最初は初心者な訳なんだし。

817:デフォルトの名無しさん
04/10/17 11:11:48
>>816
指針としては、

マクロは単に式を変形する
・ソースを短かくするために書け
・いつ式が変形されたか気を付けろ(コンパイル時、実行時、再定義に注意)

って基本を叩きこめば十分じゃないか?まだあるかな?

818:デフォルトの名無しさん
04/10/17 11:52:18
>>816
> だれだって最初は初心者な訳なんだし。

これってよく使われるフレーズだけど全然言い訳になってないよな。
最初は初心者でも自力修得できる奴はなんぼでもいる。


819:デフォルトの名無しさん
04/10/17 13:24:58
>>813
おそらくはマクロ等を駆使することで、
「問題領域における問題の記述とコードが同一になる」 Lisp/Scheme すげえ!
って思うんだがどうよ?

820:デフォルトの名無しさん
04/10/17 15:23:24
>>817
>・ソースを短かくするために書け
これは同意できない。
結果としてソースが短くなるケースが多いとしても、ソースを短くすることは
マクロを書く目的にはなり得ません。

821:デフォルトの名無しさん
04/10/17 17:30:59
あんたにはならなくても俺はなるな

822:デフォルトの名無しさん
04/10/17 17:59:41
結果として短くなるのと、短くするのを目的として使うことを混同してませんか?

823:デフォルトの名無しさん
04/10/17 18:11:15
>>818
わからないことを責めてはいけないとおもう。
わかろうとしないことだとしてもそうだと思う。
そういう人を取り込むことが言語のすそのを広げることになるんじゃないかな。

824:デフォルトの名無しさん
04/10/17 18:14:49
>>821, 822
ソースコードの性質によるのかな〜と思うけど、どうかな。

長く、広く使われるコードであれば、直感にあう抽象化を
するためにマクロを使うべきだろう。
けど、テストコードなんかの場合はとにかく楽をするために
マクロを使っても良いように思う。

825:デフォルトの名無しさん
04/10/17 18:31:27
>>813
言語に「上下」があるわけではないと思うけどなぁ。

俺は ruby も好きだけど、scheme で書いてる時とは気分が違うよね。
なんつうか、ruby は relax しながら書くけど、scheme だと、集中して
研ぎ澄まされてく感じがする。抽象的だけどさ。

826:デフォルトの名無しさん
04/10/17 18:34:07
(DQNニレススンナヨ)

827:デフォルトの名無しさん
04/10/17 19:55:25
>>813
いいんだよそれで。「普通のやつら〜」が言ってるのは競争の激しい
ベンチャー企業は最大限に生産性をあげないと生き残れない、そのために
最強の言語を使うべきだって言ってるんであって、その他大勢が
何を使おうが知ったこっちゃない。あの文章は生きるか死ぬかの極限での
サバイバル術について語ってるんだ。そんな環境に置かれていないなら、
好みで言語を選べばいいのさ。





828:デフォルトの名無しさん
04/10/17 22:38:11
普通のプログラマにはjavaをお薦めします

829:デフォルトの名無しさん
04/10/17 22:54:30
ノーマルプログラマにお薦め→java
アブノーマルプログラマにお薦め↓


830:デフォルトの名無しさん
04/10/17 23:01:54
C

831:デフォルトの名無しさん
04/10/17 23:41:06
この掲示板は何言語で運営してるの?

832:デフォルトの名無しさん
04/10/17 23:46:39
>>820, 822
817 だけど(!= 821 ね)
煽りじゃなくて、どんな目的でマクロ書くのか教えてくれないか?
おれはマクロに関してはソースを短くする事しか考えてないんだけど…
あ、「短く」じゃなく「明快に」、と格好良く言えば伝わるかな?

>>824
おれはテストコードは逆にマクロつかわないなぁ
テストコードのテストなんてしたくないから
コピペでも良いから単純に書く、どうせテストなんだし

833:デフォルトの名無しさん
04/10/18 00:04:11
>>832
私の場合:
 * with-なんとか系 --- 最初と最後にお決まりの文句がある処理とか
 * 関数のインターフェイス ---
(foo-format t "~A" obj1 "~A" obj2 ...) とやりたい.

834:デフォルトの名無しさん
04/10/18 00:04:20
Ruby!!!!!!!!!!!!!!!!!!

835:デフォルトの名無しさん
04/10/18 00:14:14
>>832
820でも822でもないですが
イディオムや式に名前を付けるとき。短くなるとは限らない。

836:デフォルトの名無しさん
04/10/18 00:31:51
>>835
確かに短くても慣れてないイディオムを包むマクロは書くことあるけど
(そしてその名前が長い時もある)
何回も使えば少なくとも行数は短かくなる気がするけど…
(本当はタイプ量も少なくしたいけど補完があるから気にしない場合はある)
良い例がありますか?

837:デフォルトの名無しさん
04/10/18 00:48:40
マクロの例は,いろんなプログラムのソースがヒントになるなと思ったよ.
名前は忘れたけど数値計算のやつでは,型チェックをやってた.
ソースを見ると,その関数の引数の型が一目瞭然になってるの.

838:デフォルトの名無しさん
04/10/18 01:53:24
ソースのバイト数を減らすってのもいいけど
(例えば call-with-current-continuation → call/cc, multiple-value-bind → mvbind など),
重要なのは定型的コード内のパターンを抽出して構文要素数を減らすことだぜ?
ただ,inline関数で実現できるならその方がいい.
"On Lisp" を読めば抽象化手段としてマクロを使うべきかどうかの判断規準(の一例)が示されてるけど...

839:デフォルトの名無しさん
04/10/18 06:36:49
cgiにschemeが使える無料鯖ってあるの?

840:デフォルトの名無しさん
04/10/18 07:31:33
>>838 が正しいな、おかげですっきりしたぜ

841:デフォルトの名無しさん
04/10/18 08:58:39
さんざん既出だがSchemeの基本構文要素なんて10個に満たないが、
全く不便に感じないのは強力なマクロの記述能力があるからだよ。
処理系がI/Fを開放してればマクロを通してそのままマシンコードに落とせるし、
コンパイラとマクロの間には区切りがない。
LISPは本来処理系のコンパイラが貧弱でも後付でカバーできる程の能力を
秘めているが、あまりそれを認識してる奴はいないみたいだな。
ユーザーにスキルがあれば自力でinline-expandやlambda-liftingの様な
プリコンパイルが簡単に追加できる。
コアの作りこみを最小限とすれば新しい処理系はすぐに完成する。
人間で言うと脳や神経だな。骨格や肉付けはすべて後回しでいい。
コアを何度も作ってる奴は、残りをすべて使い回しが利くS式で記述する筈だ。

だからGaucheの様なCでなんでもかんでも書く方針はちょっといただけないな。
確かに労せず速い処理系になるだろうが、それは処理系固有のモノになってしまい、
LISPの自己拡張性を否定しかねない。
どのみちブートストラップの真似事してるんだからVMに渡すプリコンパイラぐらい
Scheme自身で書いてみろよ。パフォーマンスにさほど違いはないから。
と、Gaucheのソース流し読みして思った次第。


842:デフォルトの名無しさん
04/10/18 09:24:51
I/Fって何?

843:デフォルトの名無しさん
04/10/18 09:28:23
それは処理系固有のモノになってしまい、LISPの自己拡張性を否定しかねない。
ってどういう意味?
他の処理系でもマクロで拡張するんなら処理系固有とか関係なく思えるが。

844:デフォルトの名無しさん
04/10/18 09:35:03
>>841
Gauche開発の動機と目標を知っててあえて言ってる?

845:デフォルトの名無しさん
04/10/18 09:56:30
>>844
これか? URLリンク(www.shiro.dreamhost.com)


846:841
04/10/18 10:27:07
>>842 ぐぐれ

>>843 処理系に固定化され、固定化されたコードはそれ以上進化する余地なくなるということ。

>>844-845
そのリンクの文章のことではなかったけど、その中から異論を唱えると、

>実行時に、その言語のための環境(スクリプトライブラリ、初期化ファイルなど)を
> フルセットで持てるとは限らない。 最悪の場合、アプリケーションはそのバイナリ
> 単体で動作することが要求される。

これは単にアイデアが枯渇しているだけだろう。
実行ファイルに環境を埋め込む手段はいくらでもあるし、さらにその上に
環境の永続化という手段もある。

>アプリケーションの実行の主体はC/C++で書かれたコードで、
> その中から頻繁にちょっとしたリスト処理や文字列処理なんかが呼び出される。

これはevalそのものやVM、マシンコードの入り口を用意していれば済む事。
それに結局GCを伴うので、現実的には外部から頻繁には呼び出せず、一括して呼び出す方針になりがち。
そもそも外部のコンパイラが必要な時点でプロダクション環境とやらには論外だろう。

>むしろ、 memqなんて基本的なパーツはCで書いとくべきなんだ。

memqの様なすぐ代替が利く極端に小さいパーツならこの手も有効だろうが、
少しでも大きな処理になるとこの思想は破綻する。作者もそれはわかってるみたいだが。
結局実際は実装が二極化し、複雑性が増加しただけだろう。
組み込みスタブ関数郡のメンテナンス性の悪さを見ればわかる。

こんなところか。

847:デフォルトの名無しさん
04/10/18 10:39:16
ぐぐってもわからんぞ。

848:デフォルトの名無しさん
04/10/18 10:40:26
じゃあ、あんたがSchemeの処理系書いて配ればいい。

849:デフォルトの名無しさん
04/10/18 10:44:37
処理系に固定化され、固定化されたコードはそれ以上進化する余地なくなるということ。
進化させたかったら処理系に手をいれればいいじゃん。

850:デフォルトの名無しさん
04/10/18 10:53:03
>>848
公開が面倒なだけでそういう処理系は数年前に作ったよ。

>>849
それは処理系の完成度が低いと暗に示しているにすぎないだろ。
商用にするなら論外の提案。
顧客毎に固有化して、いったい誰がメンテするんだ?

851:デフォルトの名無しさん
04/10/18 11:03:16
公開してなければただの妄想言語

顧客ごとの固有化はマクロを使えばいいだろ。

852:デフォルトの名無しさん
04/10/18 11:28:06
841は組み込み系とかはやったことないんだろうなぁ


853:名無しさん@お腹いっぱい。
04/10/18 12:11:42
GaucheがCでの実装を多用しているのは、現バージョンでの実用性を考えると仕方のない部分が多いのではないだろうか。

VMでは各インストラクションごとにテーブルを使った間接分岐が行われるが、この時、ほぼ毎回分岐予測を失敗することになる。(PC用で、これの分岐予測をうまく行ってくれるプロセッサーは無いと思う)
今のプロセッサーでこのペナルティーはでかすぎる。1ワードのデータのLOADやPUSHを行うたびにパイプラインがフラッシュされるんだから...
つまり、速度を出すにはVMのインストラクション数を減らすことが重要ということになり、そのためにCで書く部分が多くなるといったところではないだろうか。

根本的な解決にはネイティブコードに落とす必要があるが、これとインタラクティブな環境を両立するのは難しい。
商用のChez Schemeならそれが出来ているが、個人が作っている処理系でそれが出来ていなくとも、あまり乱暴な言葉遣いは良くないと思う。

真面目な開発者ならネガティブな意見は大歓迎だと思うし、それは遠慮なく伝えていいと思う。
変な信者のラブレターよりよっぽと嬉しいだろう。でも礼儀は忘れないでほしい。


854:824
04/10/18 13:55:18
>>832
へええ、おもしろい。
私もテストコードにはややこしいマクロはかきませんが、
いまいち自然じゃない、長くは覚えていられないような
展開をするマクロをかくことはあります。
以下の2つがその主な理由です:
1) テストはだいたい1ファイルに閉じてるので読めばわかると思う
2) テストは繰り返しや比較が多いのでなるべく自動化したい


855:デフォルトの名無しさん
04/10/18 15:42:44
>そのためにCで書く部分が多くなるといったところではないだろうか。
話がつながってない気が


856:デフォルトの名無しさん
04/10/18 17:13:28
>>855
「VMで実行するインストラクション数を減らすために処理系やライブラリー関数をSchemeでなくCで実装する」を意図しておりました。
説明がへたですまぬ。



857:850
04/10/18 18:27:29
>>851
うぷしてみたよ。
URLリンク(syobon.zive.net:85)
サンプル読めば簡単な窓ぐらいは作れるかもしれない。

858:850
04/10/18 18:35:35
失敗した。
syobon10969.zipね。

859:デフォルトの名無しさん
04/10/18 19:26:48
schemeのプリミティブを使ってマクロを組み立てれば強力な記述力がある
構文を手にできるというのは別に誰も反対していないと思う。
しかしいくつか問題があって、一つはそのような構文を作るのに多用する継続の
利用コストが小さくないこと。
もう一つは共通語彙が貧弱なこと。SRFIで整えられてきてるけど、
各自で構文を作るのが実現できることと、それが実用上利益になることとは別問題。
それにSchemeコアが万能というわけじゃない。SRFIの多くは
結局使い勝手のために処理系の拡張を必要とするものが多い。

だから理念としてはScheme好きだけど実際に使うのはCommon Lispだったりする。



860:デフォルトの名無しさん
04/10/18 20:42:09
本人やる気ないなんて言わずに発展させていってくれよ。
面白そうじゃないかコレ。

861:デフォルトの名無しさん
04/10/18 21:05:52
本人にやる気がないのなら他の人が発展させていけばいい。

862:デフォルトの名無しさん
04/10/18 21:51:10
>>850
ソースくれよ。

863:デフォルトの名無しさん
04/10/18 22:00:14
やーだよ

864:デフォルトの名無しさん
04/10/18 22:05:07
が〜ん。
Gaucheが実行環境の永続化(?)をサポートするのをまとう。
構想はあると聞いてるので...

865:デフォルトの名無しさん
04/10/18 23:47:37
おい、このスレからチャレンジングな Scheme 実装が生まれようとしてるのか?!
面白くなってきたと思わないか?おまいら!

866:832
04/10/18 23:57:05
>>854
なるほど確かにマクロでもそのファイルだけで閉じてれば良いかも
でもテストコードに関しては、例え繰り返しとかあっても抽象化する必要は無い、
即値も平気で書いて ok!とも思うけど
それにしても Lisp ほどテストがやりやすい環境も無い!
何たって関数単位で書いたらすぐ試せるし

867:デフォルトの名無しさん
04/10/19 07:33:57
>>857
実行速度が結構出るね。
cygwin上のgoshよりほとんどのケースで速かったよ。
compiled-closureってことはeval前にコンパイルだよね。
永続化も説得力のある代物でした。
正式に公開したらかなりいけてますよ。

868:デフォルトの名無しさん
04/10/20 15:55:46
Lisp Machineで作業しているところのビデオ。既出だったらゴメソ。
URLリンク(lemonodor.com)
URLリンク(lemonodor.com)

これを見て、「Lisp Machineでは、コンパイラのエラーメッセージでさえオブジェクト」
という意味がすこしわかったよ。

869:デフォルトの名無しさん
04/10/21 12:54:30
>>850 再うpきぼんぬ

870:デフォルトの名無しさん
04/10/21 22:27:06
低レベルな質問で済みません。
CLISP で fork(2) みたいに clisp のプロセスイメージをコピーするのってどうやれば良いので
しょうか? fork(2) して子プロセスで S 式を実行し、結果を親プロセスとやり取りするような
プログラムを書きたいのです。ext:run-program で CLISP 自体を実行してやるのが普通なので
しょうか?

CMUCL ではこんな風に出来る(出来た?)らしいです。
URLリンク(cl-cookbook.sourceforge.net)


次ページ
最新レス表示
スレッドの検索
類似スレ一覧
話題のニュース
おまかせリスト
▼オプションを表示
暇つぶし2ch

5078日前に更新/286 KB
担当:undef