vieweditattachhistoryswikistopchangessearchhelp

HyperTalkとプロトタイプベース

HyperCardより暖簾分け。--CUE



HyperCard の継承と C++ のクラス継承

HyperCardの継承を、C++で言うようなクラスの継承と同じように考えると、にっちもさっちも行かなくなる。

なぜなら、HyperCardにはC++のクラスに相当する概念がない(もしくは、ユーザーからはアクセス可能でない)からである。
逆に言えば、C++の継承をHyperCardの継承と同じように考えてはいけないという事でもある:
例えばC++で、カードクラスをバックグラウンドクラスから派生させたり、ボタンクラスをカードクラスから派生させたりしてはいけない。メソッドをメッセージとみなせば、一見、それでうまく行くように見えるが、実際には設計ミスという苦労を背負い込む事になると思われる。--CUE

「クラス…がないから」なのかどうかは知らないんですが、それ以前にそもそも
「何が何を(何から)」継承するのか、という用語定義(?)が、
HyperCardとC++とでは違う、ということだったりしないっすか?--戯



HyperCard はプロトタイプベースか

そうですね。HyperCard の継承は、委譲に近いように思います。クラスベースというよりはプロトタイプベースですね。--sumim

私がプロトタイプベースについての解釈を間違っていなければ(何しろ、NewtonScriptくらいしか知らないので)、HyperCardはプロトタイプベースとは言えないですね:なぜなら、HyperCardのオブジェクトやそのプロパティは、明示的にそうしない限り、何かから動的にコピーされるという事は決してないからです。--CUE

たしかに。では HyperCard のウィジェットをオブジェクトっぽくとらえたとき、クラスベースオブジェクトというより、オブジェクト自らがメソッド(正確にはメッセージハンドラ)を持てて、その委譲やオーバーライドができる点でプロトタイプベースと呼ばれることの多いオブジェクトベースのオブジェクトに似ている…と言い換えさせてください。--sumim

って、よく読み返したらそんなのコンテクストから自明じゃないですか(^_^;) ただのボケにマジレスしちゃったのか?<オレ もっとも、HyperCard でいう“継承”はプロトタイプベースの委譲より、やはりクラスベースオブジェクトの継承に近いという反論だとしたらそれはそれで興味深いのですが。> CUE --sumim

…とちょっと憤ってみましたが、CUE さんの書き込みをよくよく読むと(コンテクストを把握できていないのはおまえじゃ>ワレ スマン!>オレ)どうもプロトタイプベースにピンと来ておられないような気がします。次回の例会後食事会で小一時間、プロトタイプベースについて語ってあげましょう(笑)。NewtonScript はかなりいじられたのですか? 文法はともかく、あれはほとんど SELF なので理解は足りるとおもうのですが…。--sumim



委譲と継承

あれ?
俺の理解では、委譲という概念があり、そのさい委譲先オブジェクトとしては色々なものを取り得る…
(というか、委譲先は複数であることも有り得ますよね。「どんな事柄を」委譲したいか、ってのを使い分ける感じ。)

そして、委譲の使い道つーか応用の中の一つの流派(?)として、
委譲先としてプロトタイプオブジェクトなるものを採ることにして、
「知らない事柄は何でもかんでもそいつに委譲する」ことにしちゃう、
という流派が有り、それをプロトタイプ(なオブジェクト指向うんぬん)と呼ぶ、のだと思っていたのですが…? -戯

#つまり、プロトタイプis-a委譲、ではなく、プロトタイプhas-a委譲、という感じ。#aじゃないかも知れないけど。

和訳すると、委譲にからめてプロトタイプベース云々した私のコメントがそもそもおかしいと?--sumim

期待してます>小一時間 --CUE



プロトタイプベースと NewtonScript

私は、プロトタイプベースというのは、何かしらの複製が起るものだと思っていましたが、そうではないのかもしれない:有野氏の名言「プロトなんていらないじゃん」の通り、実際のところ、複製されたように見せ掛けるだけで済む話であって、複製する必要はないとも言えるわけだし。 --CUE

HyperCardに於いて、meはいつもメッセージハンドラのあるオブジェクトを指すし、the targetはいつも最初にメッセージを受け取ったオブジェクトを指している。
私の理解では、プロトタイプ上のスロットでは、selfがそのプロトタイプ自身を指す事はなく、そのスロットの複製が存在するオブジェクトを指すのではなかったかと思いますが、どうでしょうか。HyperCardではそのような事は(明示的にでない限り)起こり得ないので、プロトタイプベースではないと思ったのです。--CUE

というか、私はNewtonScriptと他のプログラミング言語との違いを、その点によってのみ識別しているのですが。 --CUE

>おかしいと?
ごめん。実はそうみたい。

>有野氏
複製は無関係で単に参照すればいいだけだ、と少なくとも俺は最初(?)から思っていたんですが、
すると今までCUE氏とあちこちで交わしたこの話題についての会話は、思いっきりズレまくっていたのかも?

で、それ(参照)すら要らぬと言っているからこそ、有野氏恐るべし、と思っていたのだけども。
なにせ、どうやって現実的に実現するつもりなのかさっぱり判らないんで… -戯
#彼はオブジェクトは孤立させても良いと思ってるんじゃないか?と俺は想像。

>そのスロットの複製が存在するオブジェクトを指すのではなかったかと思いますが
やはり俺の理解では、ですが、
(プロト関係の)親に子がぶら下がると、子から親への一方通行の参照だけが作られる、
その子に何らかの属性値がbindされると、もしその属性名と同じ属性名が親に有れば、「子から見て」親の同名属性は「マスク」されてしまう、
ということなのかなと思っていたのですが。

別分野(かな?)の用語でいえばこれはCopyOnWriteみたいなものだと思っています。
いっけんCopyしたかのように見えるけどCopyではない、と。

で、あくまで参照であるために、親側の属性を書き換えてしまうことで、
複数の子へいっぺんに「いつの間にやら値が変わってた」という恐ろしいこと(^^;を
(良くも悪くも)やれてしまうのではないか、と。

逆にいえば、selfという属性(???)すら、そのトリックの中に存在しているんだと思います。
つまり子にとってのselfは子自身だし、親のselfはあくまで親自身。だけど、
「子から見れば」親のselfは子のselfで「マスク」されるから、見えるのはあくまで子のself、と。

>selfがそのプロトタイプ自身を指す事はなく、そのスロットの複製が存在するオブジェクトを指す
ん? NewtonScript の self も HyperCard の the target も Smalltalk の self も、Self の self もみな同じでそのメソッドを起動したメッセージのレシーバです。ちなみに、そのメソッドが定義されているひとつ上流のオブジェクトからメソッドを探索を行なうには、inherited、《なし》、super、resend を使いますが、この場合も変わるのはメソッド検索のスタート地点であくまでレシーバを示します。

さらに NewtonScript と Self では、self を明示的、暗示的にすることでもスロット検索のスタート地点が変わってしまいます(このことをおっしゃっておられるのでしょうか?)。Self では、self を明示的にするとメソッド定義オブジェクトからのスタートになるのでこれを推奨していません。逆に NewtonScript では、self を明示的にすることでスロット検索をレシーバから始めることができるので self の省略は避けるように言っています。

NewtonScript では、メッセージ送信でない Call With 構文というのを用いてメソッドを明示的に起動した場合、self をメソッド定義オブジェクトに設定することができます。HyperCard の me 的な意味合いを self に外的に持たせようというわけですね。ちなみに Smalltalk の場合は thisContext method who first というメッセージ式を実行しないとメソッドを定義しているオブジェクト(Smalltalk の場合インスタンスはメソッド定義元にはなれないので、クラスということになりますが)を参照できません。ちっ(笑)。--sumim


>ごめん。実はそう
すまん(^_^;)。--sumim


>やはり俺の理解
私も同じ理解です。Self でおもしろいのは、値を処理で(もしくは逆に処理を値で)子が親のそれをマスクできることですね。--sumim


>親側の属性を書き換えてしまうことで、複数の子へいっぺんに「いつの間にやら値が変わってた」という恐ろしいこと
可能だと思います。Smalltalk でクラス変数を不用意に使ってはいけないといわれる所以と状況が似ていますね。要不要、善し悪しは別にして。--sumim


>selfという属性(???)
Self では self は暗示的なスロットという設定ですので属性でよいと思います。しかし、プロトタイプスロットなので、self はどこであってもやはり self で、同じ物(つまりメッセージのレシーバ)を指しており、そうした“トリック”はなさそうです。self またはそれに準ずる物が HyperCard の me のように、常にそのメソッドを定義しているオブジェクト指している言語というのもあるのでしょうか?--sumim


親子関係という言葉を使っていいのであれば、戯の言う「子の属性が親の属性をマスクする」というのは、ごく普通の委譲・継承関係でしかありません。
少なくとも、NewtonScriptでは、(私の理解が間違ってなければ)親がプロトか否かによって、(子の属性の扱いではなく)子の扱いが変わってきます。
子に属性がない時、親がプロト親でなければ、その子には何も起きません:その親の属性が参照されるだけです(従って、やろうと思えば親の属性の内容を書き換える事も可能)。
しかし、親がプロト親なら、その親の属性が子に複製されて子に新たに属性ができ、その新しい属性が参照されます(従って、プロト親の属性は書き換えられない:書き換えの結果は子の方に残る)。
親を「プロト親」とするかどうかは子の方で決められます。
以上のような特質があるかどうかで、プロトタイプベースかどうかを私は判断していました。--CUE



NewtonScript におけるオブジェクトの挙動

frame := { _proto: ftame,
           _parent: fpame,
           msg1: func() foo:=11,
           msg2: func() self.foo:=12,
           msg3: func() SetVariable(self,'foo,13),
           msg4: func() bar:=24,
           msg5: func() self.bar:=25,
           msg6: func() SetVariable(self,'bar,26)};

ftame := { bar: 20 };

fpame := { foo: 10 };

fcame := { _parent: frame };
という状態で、次のようなことが起ります。
fcame:msg1() --> fpame の foo を 11 に変更

fcame:msg2() --> fcame に foo: 12 を新設(self、すなわちレシーバに新設)
RemoveSlot(fcame,'foo);

fcame:msg3() --> fpame の foo を 13 に変更

fcame:msg4() --> frame に bar: 24 を新設(_proto のスロットは書き換えられないのでその手前に新設)
RemoveSlot(frame,'bar)

fcame:msg5() --> fcame に bar: 25 を新設(self、すなわちレシーバに新設)
RemoveSlot(fcame,'bar);

fcame:msg6() --> frame に bar: 26 を新設(_proto のスロットは書き換えられないのでその手前に新設)
RemoveSlot(frame,'bar)
あと、NewtonScript に限っては戯さんのトリッキーな話も当たっているような印象をうけました and 私の Call With 構文に関する記述は間違っていました。まず、メソッド、つまり関数オブジェクトは独自の self スロットを持っていて、自分が生成されたときの self の状態を保持しています(つまり、Self と違い、NewtonScript の関数オブジェクトの self スロットはプロトタイプスロットではない定数スロットのような振る舞いをする)。通常の起動方法では、この self スロットを参照するとレシーバが返ります(戯さんのトリックが使われているかもしれません)。

Call With 構文では、レシーバが関数オブジェクトをどのようなパスで呼び出したとしても(関数オブジェクトを直接指定して起動している、つまり、メッセージ送信は行なわれずレシーバは実質的に不在のため当たり前といえば当たり前ですが)self は関数オブジェクト生成時に self に束縛されていたオブジェクトがそのまま使われます。これだけだと、self は関数オブジェクトを束縛しているオブジェクトを指すのか…と思いがちですがさにあらず。

関数オブジェクト *生成時* の self というのがミソで、たとえば、frame に msg:=func() frame.msg7:=func() return self というメソッドを作り、fcame:msg() でこれを起動して、frame.msg7 に func() return self という関数オブジェクトを生成します。すると、このコンテクストでの self は fcame なので、frame.msg7 の self には fcame が束縛されます。したがって、call frame.msg7 with () とすると、self として fcame が返ってきます。念のため、frame:msg7() で起動すると、ちゃんと self として frame が返ってきます。--sumim


まあちょっと話がそれてしまいましたが、プロトタイプとクローン、親と子というタームの使い方がごっちゃになっている嫌いはあれど、最後のプロトタイプベースに関する見解を除いて、CUE さんのご理解は大筋では間違っていないように思います。行き違いがあるとすれば、プロトタイプの ROM 格納性を重視した NewtonScript の特殊な仕様をして、これ即ちプロトタイブベースである…というところだったのではないかと思いますがいかがでしょう。--sumim

なるほど。更新が遡上(?)するのが_parentで、遡上(?)しないのが_protoなんですね。-戯
面白いです。ただ、そういう"ルール"が多く(覚えきれなく)なりすぎないような用心は必要っぽいですが。

ところでSetVariableの存在価値は一体何?(^^;
というか、selfやそれ以外へのアクセスPathが色々有ると、それだけ人は混乱し易いんじゃないかと老婆心。

ところで^2、同言語(環境はさておき言語仕様として)の処理系って何処かに(出来ればFREEで)転がっていますかね?
え?作れって?(^^;

ははは。それこそがバスケをはじめ、旧 Newton プログラマたちが目指し、しかしままならず誰かに託したい心境であるところのブツと言えるしょう(笑)。>処理系 --sumim

>ところでSetVariableの存在価値は一体何?
frame.slot 書式では _parent ツリーへの参照が行なわれない仕様なので、それをするためのものらしいです。_proto 、_proto._proto …と辿って諦めるか、さらに _parent に行って探してなければ、_parent._proto、_parent._proto._proto と辿って駄目なら、_parent._parent に行って…とするかの違いですね。--sumim


>しかしままならず
あ、でもこんなのがありました。動くかどうか分かりませんが。--sumim


>NewtonScript の特殊な仕様をして、これ即ちプロトタイブベースである…というところ
私はそのような事は言及していないつもりなのですが、それでは、結局、NewtonScriptの仕様のどの部分が特に、「プロトタイプベース」という修飾詞が要求する仕様の範囲外にあるのかを認識するところから、始めるべきなのでしょうか。--CUE

というわけで、ここを読み返してみました。が、これがプロトタイプベースという用語の定義の全てだとしたら(他もあたってみましたが、大差なし)、少々腑に落ちない面がありますね。その辺の事は別のWikiネームにて。--CUE

プロトタイプベースとクラスベースは対立する概念か?



NewtonScript の何がプロトタイプベースなのか

NewtonScriptからLinkされてるここには、
俺の読み違い(ちなみにNiftyの和訳なんかも活用(濫用?)しました)でなければ、
「初期のSELFにはプロトは1つだった。NewtonScriptは2つにした。最近のSELFでは優先順位つきで好きな数だけプロトが持てる」
と書いてある…ような気がします。

はーい(元気よく手を挙げる)。そもそも俺は、プロトが複数あるってゆー考え方(や実装)が有るってことは考えていませんでした。 -戯

#なので、ある意味では俺は議論に付いていってなかったとも言えます。
#要するにNewtonScriptの言語仕様を確認してなかったのだから。m(__)m

「ばぶばぶ」(次世代案)で、プロト手繰りを枝分かれさせるオブジェクトを作ったら便利だろな、とは考えましたけど、
言語仕様として最初から用意する必要があるとまでは考えてませんでした。
あるオブジェクトとプロト親(への参照)との間に、「二股ソケット」なオブジェクトを1つ挟んであげれば
それで済むんじゃないか?と思っています。

#で、元来のプロトと偽プロト(笑)の間に、どっちを先に手繰るかの優先順位が有る、という感じ。
#元来プロトがネタ切れになったら次に偽プロトを手繰る、というのを考えていました。

あと、プロトへの変更の遡上(?)がアリなのかナシなのか、も気になる所です。
俺はナシだ(やりたければ明示的にprotoにアクセスするようにコーディングしろ)と思っていたので、
「ごく普通の委譲・継承関係でしかありません」と言われても、「まさにそうです」という答えしか用意してません。

>親がプロト親なら、その親の属性が子に複製されて子に新たに属性ができ、その新しい属性が参照されます
んー、そっちについては、CopyOnWriteと考える、ということで(も)OKではないんでしょうか?
#子を作ってから親のほうを書き換えると、子からはどう見えます?…それ次第っつーことで。

というか、その委譲を、いちいちコーディングする必要が無いかどうか、がプロトタイプベースの肝
なのかなと思っていたので…。
委譲先を明示するようなコーディングは、他の無数の言語でも普通に行われていることなので。
また、プロトタイプベースを謳う言語であっても(なくても)、プロト以外のオブジェクトへの委譲
明示的に書けば出来る(し、書かないと出来ない)、という感じかと。

各者各様に解釈の違いがあって面白いなぁ。--CUE

やりたければ明示的にprotoにアクセスするようにコーディングしろ
そんな事が可能なのかという疑問が真っ先に起きるなぁ。それとも、これをして「その委譲を、いちいちコーディングする必要が無いかどうか」という事とからめているのだろうか?--CUE

>そんな事が可能なのか
ここ(Googleでたまたま見つけた)の「委譲によるAdapterパターン」に書かれてるような形を考えていたんだけど、これをもって「可能だ」と言っちゃ駄目? -戯

>からめているのだろうか?
たぶんYES。



HyperCard はプロトタイプベースでもクラスベースでもない

あ、そうか。どこですれ違っているのか分かった。
僕が「プロトタイプベースではない」と言ったのを「クラスベースだ」と言ったように、あるいは、「プロトタイプベースでは説明できない」と言ったように聞こえたのかも。
僕はクラスベースでもプロトタイプベースですらもない、と言いたかったんだけど。--CUE

ふむ。そういやクラスやプロトタイプは、既にどっかで作成した機能(?)をオブジェクト間(?)で「使いまわす」ための話であって、
オブジェクト指向のうちのごく一部の面でしかないなあ。

つまり、クラスだのプロトタイプだのという話は、使いまわしたいという欲求があって初めて考える意味の有る話(?)だし、
さらにその中でも特定の2つの戦略についての話でしかないし。-戯

ん。そういや、Delphiのimplementsは面白いかも。Javaにも同名の機能が有るけどそれとは別物で、
あるオブジェクトがある機能(Method)を実現するために、その機能を自分(や親)のクラスで実装するんじゃなく、
既にその機能を持ってる他のオブジェクトへ委譲するぞと宣言を記述するだけで出来上がり、というものらしい。
これなんかクラスでもプロトタイプでもない方式の1つなのかも。



リファクタリングは面倒なので、とりあえずサブタイトルで区切ってみました

#ところでやっぱり一連の話がHyperCardと殆ど関係ない(というか他の言語と等距離でしかない)ような気が…

>HyperCardと殆ど関係ない
リファクタリングしないといけませんねぇ…。--sumim



このページを編集 (17444 bytes)


Congratulations! 以下の 2 ページから参照されています。

This page has been visited 7327 times.