hiraさんのとこより:
2004/03/02 07:42:32 PST: slotにprivateなアクセッサを定義しようとして、あれ?っと思いました。 generic functionでディスパッチということは、自他の区別なんて無いのかと。 moduleのimport/exportが近いけど、staticな変数の可視/不可視の制御なので、ちょっと違う。 slotにpublic/privateの概念を持ち込むにはどうすればいいのだろう? クラスの中に入る仕組みがあれば良いのだろうけど。 そうすれば、slot-refにイライラしなくても済むっていうオマケも付くのだが。
(slot-ref self 'hoge) ;;21文字 CLOS:フツーの書き方 this.getHoge() //14文字 Java:thisマニアが自己カプセル化したときの書き方 hoge ;; 4文字 CLOS/Java:私が望む書き方
Shiro: 私もslot-refにはうんざりしてます。最近はほとんど (ref self 'hoge)ですが、それでも面倒です。
スロット名だけ、っていうのは、CLOSだとwith-slots構文が ありますね。Gaucheでも参照に関してはマクロですぐ書けますが、 (set! hoge value)を実現するのは難しそうです。
(with-slots (hoge huge hage) object ... hoge ...) ;; <- この中でのhogeはobject.hogeを参照
いくつかのScheme処理系には、識別子自体をマクロフォームとして 展開できるものがあり(例: SchemeCrossReference:define-id-macro)、 そういうのを使ってwith-slots内のhogeを(ref object 'hoge)に 展開してしまえば、set!も使えます。
ただ、わたし自身は「レキシカルスコープだけ」という単純さが Schemeの強さだと思っているので、複数のスコープルールを持ち込むのは 話をややこしくするだけだと思います。例えば:
(with-slots (hoge fuge hage) object (lambda () hoge))
こんなふうにクロージャを作った場合、クロージャ内のhogeは 何をクローズしているでしょうか?
Scheme的に考えれば、この時点でレキシカルなobjectに束縛された オブジェクトのhogeスロット、ですね。 ただ、インスタンス変数にselfやthis無しで アクセスできるようなOOPの場合、暗黙のスコープとして 「その時問題になっているインスタンス」が仮定されるので、 クロージャ作成時のobjectではなく、 クロージャ実行時のobjectを参照したくなる場合もあるんじゃないでしょうか。 (そもそもクロージャなんてない、ってOOPでは、作成時のobjectを 閉じ込むっていうこと自体できませんし)。
'ref'は特に、オブジェクトのスロットの先のオブジェクトのスロットの… とたどってゆくときにうざくなります。
(ref (ref foo 'bar) 'baz)
今、ぼんやりと、これをこんな風に書けたらどうかなあと思っています。
[foo 'bar 'baz]
メカニズムとしてはリーダーマクロでrefのネストに展開します。 これだと、普通のスロットアクセスは [this'hoge] とか (set! [this'hoge] value) になります。どんなもんでしょ。
ちなみに、[x y]を(ref x y)に展開する、とだけ定めておくと、 自動的にこういうこともできます。
(define x '#(a b c)) [x 1] => b ;; vector-ref (define x "abc") [x 2] => #\c ;; string-ref
(slot-ref self 'hoge) ;;21文字 CLOS:フツーの書き方 this.getHoge() //14文字 Java:thisマニアが自己カプセル化したときの書き方 [self'hoge] ;;11文字 Gauche:slot参照案 by shiroさん this.hoge // 9文字 Java:thisマニア [hoge] ;; 6文字 Gauche:slot参照案 by hira(反則気味) hoge ;; 4文字 CLOS/Java:hiraが望む書き方
Shiro: 何をselfにします? define-methodは第一引数を特別扱いしませんよ。 ああ、そうじゃなくて、selfへの束縛は自分で作ってもいいのか。 うーん、暗黙の変数参照はいや〜んだなあ。それに、複数のオブジェクトが 絡むパターンで困りませんか。
例えば、オブジェクトyのa,b,c,dスロットの値をxにコピーするとき、 よくあるパターンはこんなんです:
(for-each (lambda (s) (set! (ref x s) (ref y s)) '(a b c d))
self明示だと:
(for-each (lambda (s) (set! [x s] [y s])) '(a b c d))
暗黙のselfにするとどう書きます?
xがselfであるならこうなりますね。
(for-each (lambda (s) (set! [s] (ref y s))) '(a b c d)) =>(for-each (lambda (s) (set! (ref self s) (ref y s))) '(a b c d))
selfの省略は自クラスではなく自インスタンスに対するものなので、片方のみが[]アクセスとなります。 「可視性とアクセッサのサンプル」のcf4と書けずにcf2にした感がありますね。 そもそもgeneric functionにクラス/インスタンスメソッドという区別が無いんだよなぁ。あえて言うならクラスメソッドか。 んー、クラス/インスタンスメソッドなんて言い出すと、またクラスの内外が問題になってくる。。。 つづきは可視性のところで。-- hira
(define-macro (define-class* name supers slots . options)
(list 'begin
`(define-class ,name ,supers ,slots ,@options)
`(define-method object-apply ((object ,name) . rest)
(let loop ((obj object)
(arg rest))
(if (null? arg)
obj
(loop (slot-ref obj (string->symbol (keyword->string (car arg))))
(cdr arg)))))))
(define-class* <foo> () ((bar :init-keyword :bar)))
(define-class* <fee> () ((baz :init-keyword :baz)))
(define fee (make <fee> :baz "baz!"))
(define foo (make <foo> :bar fee))
(foo :bar) => fee
(foo :bar :baz) => (fee :baz) => "baz!"
Shiro: これだと、object-applyを自分の目的に使いたいクラスが 困ってしまうんですよね。
Shiro: Scheme的には、「好きなOOPを使う」ってことでいいんじゃないですか。 共用するライブラリは何らかの共通フレームワークがあったほうがいいので あんまり毛色の違うマクロや名前がぶつかるシステムはまずいですが、 外に見せるのがクロージャだけ、というのならそういう問題もないと 思います。
このへんも参考になるかな:Scheme:制約の拡散
可視性の制御にも、他のメカニズムを持ち込むのではなくレキシカルスコープ 一本でゆくのが正しいと思うので、CLOSスタイルのスロット定義に うまくクロージャを絡められるとおもしろいとは思います。 例えばvirtual slotのsetterとgetter間で状態を共有するのに、今は インスタンスを介するしか方法がないですが、いちいちそのために 別スロットを作るというのはどうも汚い。 何か、クラス定義時点で、インスタンス作成時に作られる静的スコープ みたいなものが作れるとよさげです。
hira:うん。それが出来ればいいですね。 define-classの中にdefine-methodを書くようなイメージになりますか? そうなると「メソッドはクラスに属さない」という事から逸れそうだけど。
fuyuki: これは私も気になっているところです。 CLOSとクロージャというのを私はおおざっぱに 大小2本の刀があるようなイメージで捉えているんですが、 両者がうまく使い分けられるか? 協調できるか? といった点には いまだに自信がありません。 あと、CLOSは本当にSchemeになじむのか? ということも。
fuyuki: ちょっと余談ですが、私はこのところ 「多重継承の禁止」や「メソッドを束ねるクラス」は 「最適化として」正しいのかもしれないなんてことをぼんやり考えています。
gosh> (define-method hoge ((str <string>)) (list "gf str:" str))
#<generic hoge (1)>
gosh> (define-method hoge ((c1 <char>) (c2 <char>)) (list "gf char:" c1 c2))
#<generic hoge (2)>
gosh> (hoge "hello")
("gf str:" "hello")
gosh> (hoge #\a)
*** ERROR: no applicable method for #<generic hoge (2)> with arguments (#\a)
Stack Trace:
_______________________________________
gosh> (hoge #\a #\b)
("gf char:" #\a #\b)
gosh> (define (hoge str) (list "norm:" str))
hoge
gosh> (hoge "hello")
("norm:" "hello")
gosh> (hoge #\a)
("norm:" #\a)
gosh> (hoge #\a #\b)
*** ERROR: wrong number of arguments for #<closure 0x10282ec0(str)> (required 1,
got 2)
Stack Trace:
_______________________________________
public class Hoge
{
private Object fuga;
public Object getFuga() { return fuga; }
public void setFuga(Object f) { fuga = f; }
public void cf1(Hoge y) { fuga = y.fuga; } //NG
public void cf2(Hoge y) { fuga = y.getFuga(); } //OK
public void cf3(Hoge y) { setFuga(y.getFuga()); } //めんどい
public static void cf4(Hoge x, Hoge y) { x.fuga = y.fuga; } //NG
public static void cf5(Hoge x, Hoge y) { x.setFuga(y.getFuga()); } //めんどい
}
(define (make-hoge)
(let1 fuga #f
(define (self m . args)
(define (get-fuga) fuga)
(define (set-fuga! f) (set! fuga f))
(define (cf1 y) #f)
(define (cf2 y) (set! fuga (y 'get-fuga)))
(define (cfs y) (self 'set-fuga! (y 'get-fuga)))
(define (cf3 y) (set-fuga! (y 'get-fuga)))
(cond ((eq? m 'get-fuga) (apply get-fuga args))
((eq? m 'set-fuga!) (apply set-fuga! args))
((eq? m 'cf1) (apply cf1 args))
((eq? m 'cf2) (apply cf2 args))
((eq? m 'cf3) (apply cf3 args))
))
self))
(define (cf4 x y) #f)
(define (cf5 x y) (x 'set-fuga! (y 'get-fuga)))
privateといってもinstance-privateとclass-privateがある。 クロージャの束縛に「ドットで直アクセス」なんて出来ないということ。
instance-private class-private
CLOS × ×
MPS ○ ×
Java × ○
Ruby ? ?(WRITE-ME)
hira:MPSのスロットアクセスは、なかなか良い感じです。 でもそれ以外の面で、色々と不満があるのも事実。 MPS側の視点から理想を眺めてみよう、というコーナー。
※関数世界とは(proc obj arg...)という世界。オブジェクト指向世界は(obj proc arg...)となる。
Shiro: Gaucheのクラスは型システムって面もあるので(弱いけど)、 プロシージャを拡張するという考えではなく、新しい型をプロシージャ的に 振る舞わせるって考えはありますね。要はobject-applyを使うんですが。
舞台裏はこんな感じ。ただ、ユーザは気にする必要はありません。
(define-class <mps-meta> (<class>) ())
(define-syntax define-mps
(syntax-rules ()
((define-mps (name . args) . body)
(define-mps name (lambda args . body)))
((define-mps name maker)
(begin
(define-class name ()
((%maker :allocation :class :init-value maker)
(%closure :init-keyword :%closure))
:metaclass <mps-meta>)
(define-method object-apply ((class <mps-meta>) . args)
(make class :%closure (apply (class-slot-ref class '%maker) args)))
(define-method object-apply ((obj name) . args)
(apply (ref obj '%closure) args))
name))))
以下のような具合に、ほぼクロージャベースのオブジェクトのように使えますが、 作られるオブジェクトはfooクラスのインスタンスになっています。
gosh> (define-mps (foo x)
(lambda (msg . args)
(case msg
((get) x)
((set!) (set! x (car args))))))
#<class foo>
gosh> (define f (foo 4))
f
gosh> (f 'get)
4
gosh> (f 'set! 5)
5
gosh> (f 'get)
5
gosh> (class-of f)
#<class foo>
ディスパッチ部分に関しては、別途マクロを書くのがよいのでは。 それはdefine-mpsとはorthogonalに書けると思います。
hira:凄い。これでobject-*メソッドを定義出来ます。Gaucheの加護が得られるようになりました。やったー。
fuyuki:ディスパッチマクロの例としてはこんなのがあります。 http://lists.sourceforge.jp/mailman/archives/gauche-devel-jp/2003-January/000118.html
hira:上のメールから抜粋しときました↓。
> 3. case によるディスパッチはやっぱり格好悪い。
> 3はかなり一般的な問題だと思うので(gettext.scmでもやっている)、何か適当 > なユーティリティを提供したほうがいいのかもしれません。
そうっすねえ。このへんOO言語ならかえって悩まなくても いいんでしょうが… Gaucheのオブジェクトシステムだとこういうローカルな メソッドバインディングは扱いにくいですね。
実行する側からだと、メッセージによってディスパッチする 手続きは効率もそんなに悪くないですし、素直だと思うんですよ。 問題がコードの字面だけなら、こんなマクロを使ってしまえば:
(define-syntax dispatching-lambda (syntax-rules () ((_ method more ...) (dispatching-lambda-sub () () method more ...)))) (define-syntax dispatching-lambda-sub (syntax-rules (define) ((_ (symbol ...) (proc ...)) (lambda (message . args) (case message ((symbol) (apply proc args)) ... (else (error "unknown message" message))))) ((_ (symbol ...) (procs ...) (define (method . args) . body) more ...) (dispatching-lambda-sub (symbol ... method) (procs ... (lambda args . body)) more ...)) ))こんなふうに書けますね:
(dispatching-lambda (define (get) ...) (define (next seed) ...) (define (update! val) ...) (define (delete!) ...) (define (insert! key val) ...))こういうマクロをひとつ標準で持っておくと便利かもしれませんね。
('hoge obj) --> (slot-ref obj 'hoge)
gosh>'hoge obj
↓
('hoge obj)を評価
もともとの動機はslot-refの引数並びが$マクロと相性が悪いというのがありました。$ flip slot-ref 'hoge $ ...ちなみにここでflipは
(define (flip fn a b) (fn b a))です。多引数に拡張しても良いでしょう。
(define-method x->^ ((sym <symbol>)) (cut ~ <> sym) )
(define-method x->^ ((lst <list>))
(match lst
[(x) ($ list $ (x->^ x) $) ]
[(x . xs) (pack$ cons (x->^ x) (x->^ xs)) ] ) )
(define-method x->^ ((vec <vector>))
(match vec
[#(x) ($ vector $ (x->^ x) $) ]
[#(xs ... )
(^y (map-to <vector> (^z ((x->^ z) y) ) xs) ) ] ) )
('hoge obj) ---> ERROR
(map (x->^ 'hoge) objs) ---> (map (cut ~ <> 'hoge) objs)
(map (x->^ 'foo 'bar) objs) ---> (map (cut ~ <> 'foo 'bar) objs)
; x->^ は他の場合にも定義できそうな気がします。
; refではなくslot-refを使いたいときはどうするか?という問題もあります。
; もしくは、<collection>では問答無用に x->^するという選択もありかなとか。
(x->^ '(name content) ) ; ---> 'name と 'content スロットを取得して、リストにする関数
(apply 'hoge obj rest) ---> ERROR (x-apply 'hoge obj '()) ---> (~ obj 'hoge) (x-apply 'hoge obj rest) ---> (cons (~ obj 'hoge) (apply x-apply 'hoge rest)) ; 実験的(どう書けばいいかよくわかっていないのですけど・・・) ; applyの末尾のリストの扱いをどうするかよくわからないのですが、感じとして: (x-apply 'foo 'bar obj1 obj2) ---> (list (~ obj1 'foo 'bar) (~ obj2 'foo 'bar) ) (x-apply '(foo bar) obj) ---> (list (~ obj 'foo) (~ obj 'bar) ) (x-apply '(foo . bar) obj) ---> (cons (~ obj 'foo) (~ obj 'bar) ) (x-apply #(foo bar) obj) ---> (vector (~ obj 'foo) (~ obj 'bar) )
(define-method x-apply ((sym <symbol>) . arg)
(receive (syms objs) (span symbol? arg)
(let1 refn (cut apply ~ <> sym syms)
(match objs
[(x) (refn x) ]
[(xs ... ) (map refn xs) ] ) ) ) )
とりあえず、単純なケースから作って見ました。applyという名前じゃないほうがよかったような気もしてきました。(define (%ref% refn pred . arg)
($ (^x (if (= (length x) 1) (car x) x ) )
$ map
(match-lambda
[( ((? (complement pred) obj) ) ( (? pred syms) ... ) )
(apply refn obj syms) ]
[( ((? (complement pred) objs) ... ) ( (? pred syms) ... ) )
(map (cut apply refn <> syms) objs) ] )
$ flip slices 2 $ group-sequence arg :key pred)
)
(define (%flip-ref% refn pred . arg)
($ (^x (if (= (length x) 1) (car x) x ) )
$ map
(match-lambda
[( ( (? pred syms) ... ) ((? (complement pred) obj) ) )
(apply refn obj syms) ]
[( ( (? pred syms) ... ) ((? (complement pred) objs) ... ) )
(map (cut apply refn <> syms) objs) ] )
$ flip slices 2 $ group-sequence arg :key pred)
)
(define (~~ . arg) (apply %ref% ~ symbol? arg ) )
(define (flip~~ . arg) (apply %flip-ref% ~ symbol? arg ) )
(~~ obj 'x) = (~ obj 'x) (~~ obj 'x 'y) = (~ obj 'x 'y) (~~ obj1 obj2 'x 'y obj3 'u 'v 'w ) == (list (~~ obj1 obj2 'x 'y ) (~~ obj3 'u 'v 'w ) ) == (list (list (~ obj1 'x 'y) (list obj2 'x 'y) ) (~ obj3 'u 'v 'w) )
gosh> (~~ (find-module 'gauche) 'table) #<hash-table eq? 0x8204ed8> gosh> (~~ (find-module 'gauche) 'table 'cond-list) #<gloc gauche#cond-list> gosh> (~~ (find-module 'util.list) 'table) #<hash-table eq? 0x8398208> gosh> (~~ (find-module 'util.list) 'table 'cond-list) #<gloc util.list#cond-list> gosh> (~~ (find-module 'gauche) 'table (find-module 'gauche.generator) 'table) (#<hash-table eq? 0x8204ed8> #<hash-table eq? 0x82bd3c0>) gosh> (~~ (find-module 'gauche) (find-module 'gauche.generator) 'table) (#<hash-table eq? 0x8204ed8> #<hash-table eq? 0x82bd3c0>) gosh> (~~ (find-module 'gauche) (find-module 'util.list) 'table 'cond-list ) (#<gloc gauche#cond-list> #<gloc util.list#cond-list>)
gosh> (flip~~ 'table 'cond-list (find-module 'gauche) (find-module 'util.list) ) (#<gloc gauche#cond-list> #<gloc util.list#cond-list>)
($ apply = $ ~~ x y 'hoge 'foo 'bar)のように書けます。
yamasushi(2013/04/29 06:00:10 UTC)slot-refが使いにくいと感じるのは、反対の意味をもつ働きをさせようとするからで、
(bunpai x 'a 'b 'c) == (list (~ x 'a) (~ x 'b) (~ x 'c) ) (values-bunpai x 'a 'b 'c) == (values (~ x 'a) (~ x 'b) (~ x 'c) )
のような演算子があればいいのではないでしょうか? つまり、~の意味を反転させたようなものです。 オブジェクトのほうをセレクタに作用させる、というイメージです。これとmatchやreceiveを組み合わせればいいのではないでしょうか?
yamasushi(2013/04/28 12:07:31 UTC)selfがない場合について。
globalなオブジェクトとしてモジュールシステムそのものがあると考える。
(~ 'gauche 'cond-list)
でgaucheモジュールのcond-listを参照する。
また、
(bunpai 'gauche 'scheme)
== ( (シンボルから'gaucheモジュール変数をrefできるobj)
(シンボルから'schemeモジュール変数をrefできるobj) )
と見なせる。
この場合には、上のケースは
(~ 'module 'gauche 'cond-list)
のようになる。
(~ 'enviroment 'PATH )
でPATH環境変数を取得する。 また、
(bunpai 'environment 'module)
== ( (シンボルから環境変数をrefできるobj)
(シンボルからモジュールをrefできるobj) )
と見なせる。