Rubyでプロトタイプベース談義
Ruby でお手軽にプロトタイプベース的 BankAccountの「過去のバージョンとそれに絡めたやりとり」を引っ越し。--CUE
クラスのクラス(メタクラス、「クラス名 class」と書き表され、それを評価することでもアクセスできる)に定義される Smalltalk のクラスメソッドと違い、Ruby のクラスメソッドはクラスの特異メソッドです。特異メソッドは、def オブジェクト.メッセージパターン ... end で定義することができるので、プロトタイプベースっぽい Ruby の BankAccount は次のようになります。(特異メソッドで、「余談だが、この文脈ではself==class Fooなので、こういう書き方も出来ます」との遠回しな指摘を受けましたので、そのように書き換えました。)←追記:そのような意図はないとのことです。でも、そちらのほうがスマートですので、そのままにします。
class BankAccount
@dollars=200
def self.dollars
@dollars
end
def self.dollars=(x)
@dollars=x
end
def self.deposit(x)
self.dollars=(self.dollars+x)
end
def self.withdraw(x)
self.dollars=[0,self.dollars-x].max
end
self.dollars
end
=> 200
BankAccount.dollars
=> 200
BankAccount.deposit(50)
=> 250
BankAccount.withdraw(100)
=> 150
BankAccount.withdraw(200)
=> 0
class MyAccount < BankAccount
@dollars=BankAccount.dollars
end
=> 0
MyAccount.deposit(500)
=> 500
BankAccount.dollars
=> 0
class StockAccount < BankAccount
@numShares=10
@pricePerShare=30
def self.numShares
@numShares
end
def self.numShares=(x)
@numShares=x
end
def self.pricePerShare
@pricePerShare
end
def self.pricePerShare=(x)
@pricePerShare=x
end
def self.dollars
self.numShares*self.pricePerShare
end
def self.dollars=(x)
@numShares=Float(x)/pricePerShare
self.dollars
end
self.dollars
end
=>300.0
StockAccount.dollars
=> 300.0
StockAccount.dollars=150
=> 150
StockAccount.numShares
=> 5.0
class MyStock < StockAccount
@numShares=StockAccount.numShares
@pricePerShare=StockAccount.pricePerShare
self.dollars
end
=> 150.0
MyStock.dollars=600
=> 600
MyStock.numShares
=> 20.0
MyStock.deposit(60)
=> 660.0
MyStock.numShares
=> 22.0
MyStock.withdraw(120)
=> 540.0
MyStock.numShares
=> 18.0
クラスメソッドおよびクラス変数を対象に、attr は使えないので、それを宣言したときと同様の振る舞いをするメソッドを定義してみました。改めて思うに、hoge=(x)(使用時には括弧を省略して hoge=x)というメッセージパターンは仕組みを知るとちょっとビビりますね。逆に、仕組みを知らないと、ちょっととまどう(か、hoge(x) のように関数コール風に定義してしまう)でしょうね。--sumim
↑って、「クラスを(普通の)オブジェクト代わりに」使ってるんですね。「(普通の)オブジェクトをクラス代わりに」使ってるんじゃなく。
- ちなみに、特異メソッドごとオブジェクトを複製するのはObject#cloneメソッドで出来ます。 --戯
>「クラスを(普通の)オブジェクト代わりに」使ってるんですね
そうです。さながら Wiki を掲示板のように使うがごとく、といったところでしょうか。--sumim
「BankAccount=Object.new」から話を始めなかったのは意外だ、という話です。--戯
単に意外なだけじゃなく、冒頭の論法に変なものを感じます。
- 「クラスメソッドはクラスの特異メソッドだ」は事実ですが、そこから「プロトタイプベースっぽいBAは次(クラス特異メソッドを使う)」を導くってのは変かなーと。
- どうしてクラスメソッドを使ったんだろう?
- どっちかというと「プロトタイプベースっぽいBAは次(普通のインスタンスの特異メソッドを使う)」のほうが第一歩かなーと。
>どうしてクラスメソッドを使ったんだろう?
すでに答えに到達いただいているとは思いますが、プロトスロットを代用するためです。これもすでに述べたとおり、clone で組み込めばこうしたトリッキーな手法をとることは無用です。--sumim
ええと。タイトルに「お手軽」が付いたせいで却って前より強い違和感を感じるようになってしまいました。
この手法は「お手軽」さよりも何倍も強い「トリッキー」さを感じますし、
トリッキーの代償として十分なほどお手軽さが増してるか?と問われれば、俺は殆どNOと答えます。
クラスを使うという時点で、概念的に物凄いヒネリを感じます。
#実際、「これ、どう動くんだ?」と一瞬悩んでしまいました。概念的にお手軽"じゃない"ものを感じました。 --戯
>すでに答えに到達いただいて
#俺一人が「すでに到達」してても、いまいち実りは薄いですが… --戯
>トリッキーの代償として十分なほどお手軽さが増してるか?と問われれば、俺は殆どNOと答えます。
Smalltalkers' ML 界隈では「論よりコード」という言い回しがこうした局面で用いられることがあります(念のため説明すると、論より証拠のもじりで、意味はほぼ同じです)。今、脳内で想定しておられるクラスを使わない方法が、ここで例示したものよりどのくらい「お手軽」感があるのか実際のコードで示していただけると、先の指摘で「殆どNO」とまで挑発的に言い切れる自信がどこからわいてくるのかについての理解が早くて助かります。ちなみに私の乏しい洞察力と Ruby スクリプティングのためのスキル(まだ実質、数日ですが…)では、クラスを用いた場合ほどお手軽には Object.new のパターンで(組み込みならともかく、プロトタイプスロットを用いた委譲では)実現できませんでした。念のためここで「お手軽」とは、いかに実現したいことに注力できるか、本論とは関係ないロジックをオブジェクトに組み込む手間を最小限にできるか、あるいは出来合のものを流用することで少ないスクリプトでそれを実現できるかを意味しています。--sumim
ちなみに、ここでプロトタイプベースとは、Self のオブジェクト(フレーム)やその振る舞いに近いものを俺の脳内ではイメージしています。属性やメソッドのクローン時における組み込みではなく、プロトスロットにプロトタイプを束縛して、そこに束縛されているオブジェクトに委譲する(俺の脳内では“世間”でも)一般的なタイプのものです。本当なら、値を束縛しているスロット(値スロット)へのアクセスも委譲したいのですが、そこは「お手軽」感を優先するために譲歩して、組み込みタイプを採用しています。
なお、BankAccount 自体にも、クローン(クラスベースならサブクラスインスタンス)が、プロトタイプ(クラスベースならスーパークラス)の deposit() や withdraw() のメソッドを起動して、そこでの多態でクローン(同)が正常に機能する(numShares スロットの設定が行なわれる)というのを見せたいというねらいがあります。値スロットは組み込みでよくてもメソッドスロットはだめというのは、メソッドスロットの組み込みだとこの妙(多態)がなくなるという判断基準によります。--sumim
>実際、「これ、どう動くんだ?」と一瞬悩んでしまいました。
それはそれで、してやったりという感が(へえ、こんな誰でも思いつきそうな手法で…という後ろめたさはあれど)わき起こる一方で、このコメントには(俺の少し前までの脳内)戯さんほどの人がクラスとメタクラスの継承が必ず一致するという特徴を利用したこのトリックを一瞬では見抜けなかった(というか、この話のしょっぱなでそもそも思いつきもしなかった。ひいてはくだんの Smalltalk のコードを読み解いてただけていない(^_^;))ということに対するある種の失望感や不信感が伴われて残念です。
- ちなみに余談ですが CLOS のクラスにはこうした Ruby や Smalltalk にみられる特徴がない(ユーザー定義のクラスは原則としてすべて standard-class のインスタンス)ので同手法でのお手軽感は得られませんでした。--sumim
>「論よりコード」という言い回しがこうした局面で用いられる
それを「コードで示せない限り、論は無視する」という意味で使ってしまうのは、危険では?
さもないと、対抗馬が出現するまで、任意の(!)コードに「お手軽」だの「かっこいい」だの「さいこー」だの
といった賛辞を冠することが出来てしまうことになります。#同様に罵声も浴びせれますが…
代案を出せ出せと言われても、ねえ…
「よりお手軽なもの」「よりトリッキーでないもの」が今ここに有る、とは俺は言ってないんですけど。
書けんことは無いのかも知れないけど今は思いつきません。
でも、上記コードが、相対評価ではなく絶対評価で(笑)「トリッキー」なのは確かだと思いました、ということです。
#「論よりコード」は、こういう局面では用いないんじゃないかと思う。似てるけど違う或る局面で使うんだろうなと。
#もしかして、論を口語より的確というか厳密に表現するためには、コードが(非常にしばしば)向いている、ということなんじゃないかな。
#例えば、「じゃあ、あのコードがお手軽であるという主張を、コードで示してみてよ」とsumimさんが俺に言われたとしても、
#「そんなこと言われても、既にコードが書いてあるじゃん」(←でも"俺は"それじゃ納得しなかったから、これじゃ堂堂巡り)
#か「そんなの書きようがないじゃん」か、くらいしか選択肢が無いんじゃないかと。
#つまり、「お手軽だ」という論を「より的確に表現」するコードなんてものはこの世に無い、んじゃないかと。
>ここでプロトタイプベースとは、(中略)組み込みではなく(中略)
その「ゲームのルール」はココが初出?
しかも、
>値スロット(中略)譲歩して、組み込みタイプを採用しています。
という風に例外も多い(というか半分が例外という…)ルール、なのですね。
つまり(sumimさんの)線引きが何処にあるかは俺はよく見えなかったわけで。
俺なら、「お手軽感を優先」するっていうなら、お手軽に:-)Object#cloneで済ませていたところかも。
>そこでの多態でクローン(同)が正常に機能する
これ(は技術的に既知ですが)から、上記の複雑なルールを「察しろ」ということですか?…だとしたら、ちょいときつい。
ついでにもうひとつ馬鹿にされるネタ(笑)を披露しておきますと、
戯/ばぶばぶには未だに「super」に相当する能力が有りません。
内部的には要するに、read対象objectとwrite対象objectを同一でなくするというワザ
(つまり、superから読んでもいいけど、superに書き込んだら駄目、という制御)なんですが、
そういう仕組みはまだ全然組めていません。
>>これ、どう動くんだ?
>クラスとメタクラスの継承が必ず一致するという特徴
あ。1つ言い忘れましたが、もう1つ問題が有ります。
rubyはそれこそお手軽さ(笑)のために、対称な複数の概念に必ずしも対称な書き方を用意してるわけじゃなく、
一部の概念にだけエコヒイキをしている場面がありまして、
そのせい(だと俺は思ってます)で、「class Hoge; @aaa=....」という書き方に面食らったんです。
それって意図通りに動くんだっけ?という意味で。
それはそうと、「クラスとメタクラスの継承が必ず一致」云々とは、あまり関係ないんじゃないでしょうか?
つまりそもそも、概念的にもメタクラスの出番があまり無いんじゃないかと。
もしかしてメタクラスが「見えにくい」ように意図的に制御してるのかも>matz氏
…と思うことは時々有ります。
>このトリックを一瞬では見抜けなかった
あ。やっぱりトリックなんですか。
ではこれ、「誰にとっての」お手軽を狙っているんですか?sumimさん(と俺)だけが対象者なの?
あ。それとも、トリック性と(概念的)お手軽性とが両立する、という主張を含んでいますか?
だとすればそれはそれで面白いと思います。
ところで、「self.dollars」というコードが(「class BankAccount」の文脈で)出てきて、
それを評価&表示した値がコメントで「=> 200」と示されていますが、ちとビックリ。 --戯
で、慌てて調べたんですが、クラス定義 の最後のほう(「特異クラス定義」の寸前)によると、
ver1.6系と1.7系(以降?)とでは、class定義が値を返すかどうかが違ってるんですね。
#irb(ソースを1行づつ入力してrubyの反応を見れるツール)だと1.6でも一見その制限が無いかのように見えますが。
ん〜、戯節炸裂でもう笑うしかないですね。--sumim
どの辺がですか? --CUE
>ちとビックリ。
そうですね。ここでは、Ruby 1.6.7 + irb でやっています。--sumim
>あのコードがお手軽であるという主張を、コードで示してみてよ」とsumimさんが俺に言われたとして
言われたとして、出せますよ。必要があれば。もっとも、これだけ「お手軽」感が疑問視されるとは思わなかったので意外でしたが、まあそれなりにもりあがったので、そのうち書くつもりではありました。>非「お手軽」バージョン--sumim
>出せますよ。
>非「お手軽」バージョン
あのぉ。おっしゃってる言葉の意味が分かってて冗談で言ったんですよね?--CUE
>ついでにもうひとつ馬鹿にされるネタ(笑)を披露しておきます
いやいや。これは今や、ある意味(はなはだしゃくにさわる物言いとは思いますが、私の脳内における)戯さんの人格を保証する(つまりコミュニケーションに価値があるか否かの判断においてこれを是とする)最後の砦みたいなもので、super の有り無しなんてささいなことで馬鹿にするなんてとんでもないです。申し訳ないことに、また、恥ずかしながらまだ仕様を拝見しただけで、実際にいじるにいたってはいませんが、もしこのような御持論の具現化作業がなかったら、失礼ながら戯さんのオブジェクト論、オブジェクト指向論には興味すら持たなかったでしょう。この界隈には机上の空論や仮定の極論が好きな、ちょっと小うるさい御仁がおられるのだなぁ…という程度で無視していたと思います。--sumim
>もしこのような御持論の具現化作業がなかったら
私がsumim氏の言わんとする事を見誤っていないとすればですが、
「戯氏はsuper不要論を唱えており、したがって、自身の手による言語ばぶばぶにもsuperを実装しなかった」
と言っているように聞こえるのですが、合ってますか?
もしそうだとしたら、それは戯氏の言っている事を誤解している事になりますが(そして、sumim氏にはそれを読み取っていただけなかったようです)。--CUE
class NewClone < Proto ... end を自動化するメソッドを組めないか考えて見ました。class Class
def newCloneOf(c)
@clone=new(c)
@clone.dollars=c.dollars
@clone
end
end
で、MyAccount=Class.newCloneOf(BankAccount) とできます。(クラス)インスタンス変数を動的に抽出してちょする機能があればできそうです。--sumim
>ちなみに、特異メソッドごとオブジェクトを複製するのはObject#cloneメソッドで出来ます
とのご指南をいただきましたので、ちょっと試してみると、属性もコピーされるようですね。本件の場合、属性だけコピーできればよいので、特異メソッドであるクラスメソッドごと複製されてしまうのは冗長、というか、継承機構を流用してスーパークラスをプロト代わりに使うために、クラスをオブジェクトに見立てた意味がなくなります(エンベッド=組み込みするなら、プロトスロット相当のものは無用でしょう)が、とりあえず clone を使えば、MyAccount、StockAccount、MyStock の定義はそれぞれ、MyAccount=BankAccount.clone 、StockAccount=BankAccount.clone 、MyStock=StockAccount.clone で済ませることができそうです。--sumim
>クラスメソッドごと複製されてしまうのは冗長
Object#cloneの代わりにObject#dupを使うと、どうでしょうか?
なお、クラスをオブジェクトに見立てるのを止め、かつcloneを使えば、辻褄は合います。
- ところでRubyは、「クラス変数(文法的には頭が@@で始まる変数)」の導入は遅れました。「クラスに状態を持たせる」事は、あまり重要視されてなかったのかも。
>スーパークラスをプロト代わりに使う
ああ。proto参照のことを忘れていました(^^;。たしかにcloneとかでは(内部はさておき表面上は)エンベッドになっちゃいますね。
- 本当にprotoをきちんとやるならば、method_missingの仕組み(やそれを使ったDelegateライブラリ)を使うほうが良さそうです。
--戯
>本当にprotoをきちんとやるならば
もとより、承知です。うけて、語弊があるようなので(エクキューズは Io のほうに入れたつもりなのですが、読み取っていただけてないようなので)タイトルを変えました。--sumim
>Object#cloneの代わりにObject#dupを使うと、どうでしょうか?
ああ、これはいいですね。上のを書き直すと他のコメントのコンテキストが通りにくくなるので、以下に改めて書き直します。class BankAccount
@dollars=200
def self.dollars
@dollars
end
def self.dollars=(x)
@dollars=x
end
def self.deposit(x)
self.dollars=(self.dollars+x)
end
def self.withdraw(x)
self.dollars=[0,self.dollars-x].max
end
self.dollars
end
=> 200
BankAccount.dollars
=> 200
BankAccount.deposit(50)
=> 250
BankAccount.withdraw(100)
=> 150
BankAccount.withdraw(200)
=> 0
MyAccount=BankAccount.dup
MyAccount.deposit(500)
=> 500
BankAccount.dollars
=> 0
class StockAccount < BankAccount
@numShares=10
@pricePerShare=30
def self.numShares
@numShares
end
def self.numShares=(x)
@numShares=x
end
def self.pricePerShare
@pricePerShare
end
def self.pricePerShare=(x)
@pricePerShare=x
end
def self.dollars
self.numShares*self.pricePerShare
end
def self.dollars=(x)
@numShares=Float(x)/pricePerShare
self.dollars
end
self.dollars
end
=>300.0
StockAccount.dollars
=> 300.0
StockAccount.dollars=150
=> 150
StockAccount.numShares
=> 5.0
MyStock=StockAccount.dup
MyStock.dollars=600
=> 600
MyStock.numShares
=> 20.0
MyStock.deposit(60)
=> 660.0
MyStock.numShares
=> 22.0
MyStock.withdraw(120)
=> 540.0
MyStock.numShares
=> 18.0
ん〜、イメージ通り!--sumim
>(エクキューズは Io のほうに入れたつもりなのですが、読み取っていただけてないようなので)
じゃあ「Ioに入れた」と明記すればいいじゃないですか?
明記しないと、3分で他人には判らなくなりますよ?
ここのサイトでは、幾つかのページがActiveなとき、まさに分単位で変更されていくので、「3分」は誇張ではありません。
そして、後で、別のActiveな話題に際して、つまり全然別の切り口から、ページが更新されると、
元の話の流れでActive筆者たちが何を考えどう更新していったのか、が、非常に追跡しにくくなりますよ?
話の流れに組み込まれてるページが1つならば、まだいいのですが、
複数のページがそうなっている場合は…
>タイトルを変えました。
どこをどう変えたんでしょう?
変更がこのページだけだと安心できるならば、Swikiのhistory機能で見れば済みますが、
どうやら変更がこのページだけに限ってると安心することが出来ないようなので、「どこを」変えたのか明示して頂けると
後の世のためになると思います。
今くっちゃべってる私だけならまだ何とかなるんですが、3日後の自分すら他人ですし、まして赤の他人は…
文脈依存が多すぎると、「他人に読めないページ」になりますよ?
なお、もとより書き捨てを期待なさっているならば、失礼。
>タイトルを変えました。
>どこをどう変えたんでしょう?
つか、二人がそれぞれに見ている線がねじれの位置にあるってだけですね。--CUE
>ねじれの位置
いやぁ、なんかもっといろんなものがねじれまくっているような気がします。もちろん私の対応が悪いのは確かです。さらに、互いに自分のスジはまっすぐだと思っているからいかん、お互い様だってのは分かるんですけんどね。このページは例外として、おそらく戯さんのほうが正論でしょう。それも分かります。
ただ、普通は書かないで済ませられる説明やエクスキューズをいろいろ考えながら書き連ねなければならないのは、とにかく疲れるんですよ。ああ言えば、こう言うの応酬…で。ある種の効果を期待して、しかし一方で低きに流れる怠惰をして、あえてわざとそうしている部分もありますから、こっちもたいがいなのも分かっています。
でも、たいてい、知識のある人とのやりとりはつらいことがあってもそれなりに楽しくて、もっと努力をしよう、もっと分かってもらうように工夫しよう…自分には何が足りないのか学びとろう…と前向きになれるのですが、ここではそれは皆無です。ただただ、つらいだけ。失う ATP と時間と毛根と気力はあっても、得るものは何もなし。とくにこのページは自分ではこれ以上のものはたぶんないと思っているだけに、しかもやっと出てきた代替え案はこちらの書いたことを読んでもいない有様で、気が滅入るばかりです。--sumim
>遠回しな指摘を受けました
一応ですが。
別に、このページの上のほうの例をそう書き換えてくれ/書き換えるといい、と(遠回しにせよ)指摘したつもりは、ありません。
ただ単純(純粋)に、Rubyはそう振舞う(ことが出来る)、という事実を書いただけです。
ちなみに、そう書くほうがRuby的に(あるいはそれ以外の任意の意味において)「望ましい」かどうかも、知りません。
或る日それを発見し(ちなみにマニュアルではなくコーディング中に偶然気付いた)、
その時に遭遇してた(或る)局面では、楽だと自分は感じたという、ただそれだけです。
#だから、もしかして「ただのバグ」かも知れません。なおruby 1.6.xで見たものです。
クラス(の特異)メソッドの話題が動いていたので、それ繋がりで"発見"のことを思い出したので書いた、というだけです。
…「望ましい」とか「バグ」とかについては、
言ってて気になったので調べてみました。
すると、Rubyコーディング規約なるページにモロに
「クラスメソッドの定義にはselfを使用する。」というのが有りました。
ということは少なくともバグじゃないし、むしろ望ましい、と言えそうです。
(というのは、記憶に間違いが無ければここはRuby界隈の有力者のサイトなので、「信じる(という賭け)」の勝率はかなり高いでしょう。)
良かった良かった。
ええと。
>多態でクローン(同)が正常に機能する(numShares スロットの設定が行なわれる)
というのが何を指すのかよく判らないでいる、というのが有りまして。 -戯
というのは、以下のコードでも一見同じに動いてしまうので。
#少なくとも動作が同じという意味では、間違ってませんよね?見落としは無いですよね?
さて、以下のコードの場合、メソッドの「継承」がどうなってるのかは、俺もよく判ってません。
つまり、これらのコードの中でsuperと書いたら何が起きる(何が参照される)のかが判ってない。
#試せば判るはずですが、それは数分(?)後の楽しみということで。
ただ、問題のコードと下記のコードは、どちらも「super(的なもの)を実質上参照してない」んですよね。
そういう意味では、元々「多態の妙」は見えなくて当然なコードだと言えます。
なので逆にいえば、「妙」というのは何がどう妙なのかがサッパリ見えてこないもんでして…
bankAccount=Object.new
## *特異クラス*の書式。プロトタイプベースと呼べるかどうか怪しいが、
## それを言うならそもそもRubyの「プロトタイプベース」がイカサマだとも言えちゃうわけだし、
## 「クラスをInstance代わりに使わなくても」「一応プロトタイプベースで」「お手軽に」書けた(よね?)
## という意味では、少なくともこのページ的にはアリなんじゃないかと。
class << bankAccount
## ついでに、この書式だとattrが使える(^^;
attr :dollars, true
def deposit(x)
self.dollars=(self.dollars+x)
end
def withdraw(x)
self.dollars=[0,self.dollars-x].max
end
end
bankAccount.dollars=200 ## @dollarsを整える(そもそもIntegerにする等)のはココなので、まるでこれがinitializerであるかのようだ。
p bankAccount.dollars #=> 200
p bankAccount.deposit(50) #=> 250
p bankAccount.withdraw(100) #=> 150
p bankAccount.withdraw(200) #=> 0
myAccount=bankAccount.clone #=> 0
p myAccount.deposit(500) #=> 500
p bankAccount.dollars #=> 0
stockAccount=bankAccount.clone
class << stockAccount
attr :numShares, true
attr :pricePerShare, true
def dollars
self.numShares*self.pricePerShare
end
def dollars=(x)
@numShares=Float(x)/pricePerShare
self.dollars
end
end
stockAccount.numShares=10
stockAccount.pricePerShare=30
#=>300.0
p stockAccount.dollars #=> 300.0
p stockAccount.dollars=150 #=> 150
p stockAccount.numShares #=> 5.0
myStock=stockAccount.clone
p myStock.dollars #=> 150.0
p myStock.dollars=600 #=> 600
p myStock.numShares #=> 20.0
p myStock.deposit(60) #=> 660.0
p myStock.numShares #=> 22.0
p myStock.withdraw(120) #=> 540.0
p myStock.numShares #=> 18.0
…待てよ。
何かが変だと思っていたんですが、やっと判った(ような気がする)。
>deposit() や withdraw() のメソッドを起動して、そこでの多態でクローン(同)が正常に機能する
ここで「多態」という言葉を使ってるのが、変なんですよ。
だって、各コードには、 deposit() や withdraw()の定義は 1 回づつしか出てこないじゃないですか。
つまり、これは「多態」じゃなく、単に同じメソッドを使いまわしてるだけです。
deposit() や withdraw()は、どのオブジェクト(やクラス)で出てきたときも、「同じ」振る舞いしかしてないんです。
#ただ、他のメソッドや属性値がオブジェクトごとに違うので、オブジェクトそのものの挙動は変わり得ます。
#いわゆるテンプレートメソッドパターン。
道理で、superの出番も有り得ないわけです。なるほど。
で。本当に「多態」を使う状況に持ち込めば、
「クラスをInstance代わりに使う」以外に「お手軽」な書き方は全く無い、という状況が見えてくるかも知れません。
いや、単に多態(だけ)を要求するんじゃ、
各クラス(または特異クラス)で単純に勝手なOverrideをすれば済むんで、それじゃ意味無いですね。
真に意味が有るのは、やっぱりsuperが必要になるケースだと思う。
「クラスをInstance代わりに使う」ならばsuperが意図(?)通り使えるのは論を待たないでしょう。
一方、特異クラス(ひいてはRuby流プロトタイプベース)で同じことが出来るかどうかは、要確認かな。
#こういう時にこそ「論よりコード」という言葉が似合うと思う。要確認と言ってる対象が正にコードなんだから。
#で、すみません。今書く気力が無いの(^^; --戯
あ。間違えた。多態してるメソッドってのは、dollars と dollars= ですね。
でも、「superを使ってないから単なるヒネリ無しのテンプレートメソッドパターンだ」という話は、変わっていません。 --戯
>値スロットは組み込みでよくてもメソッドスロットはだめというのは
ええと。もっと本質的な話が有るのを忘れてました。
メソッドがプロトタイプ委譲でもたらさせようが、メソッドスロットのコピー(埋め込み)でもたらされようが、
多態とかテンプレートメソッドとかはキチンと動くんです。
差が出るのはそこじゃなく、「親を書き換えたとき」に「ただちに子にも波及する」か否か、です。
波及するのは恐らく委譲でしょう。埋め込みなら波及しない。
結局、以下のようなコードを「書かない限り」、その差は現れません。
子を作った「後」に親を変更すると、子に波及するかどうか?です。
class A
def self.x; "A"; end
end
class B < A
end
p A.x #=> "A"
p B.x #=> "A"
class A
def self.x; "AA"; end ## 親の変更
end
p A.x #=> "AA"
p B.x #=> "AA" ## 親の変更が子で直ぐに見える。
######
a=Object.new
def a.x; "A"; end
b=a.clone
p a.x #=> "A"
p b.x #=> "A"
def a.x; "AA"; end ## 親の変更
p a.x #=> "AA"
p b.x #=> "A" ## 親の変更が子で直ぐに見え…ない(T_T)
で、ごらんの通り、(Rubyの)特異メソッドの仕組みでは、波及しないようです。
問題(?)は、BankAccountにはそういうコードの出番が無い、ということなのだと思います。
(ですよね?)
(だとすれば) なので、主張(この場合は「クラスをInstance代わりに使うことの決定的な優位性」かな…)を表現できず、
何言いたいんだか判らなくなっちゃいます。
多分これ、「論よりコード」の裏返しです。
コードが主張を反映してないと普通以上に論が伝わりにくくなっちゃう、という例になってしまってるんです。 --戯
>コードが主張を反映してないと普通以上に論が伝わりにくくなっちゃう、
単に読み手の姿勢の問題かと(^_^;)。ハナから批判的姿勢でかかられたのでは、伝わるものも伝わりません。--sumim
まず、戯さんのコードは(私がすでに“使っては意味がない”というようなことを言っている clone() をなぜかあえて使っているので)たしかにプロトタイプへの変更は、クローンには影響を与えません。しごく当然の結果です。だからこそ、clone() 使ってしまっては、ここでクラスを使うこと(によって実現しようとしていることは組み込みによってキャンセルされてしまうので、クラスをあえて用いること自体)が意味をなさなくなる、というようなことを2度に渡って言っているわけですが、読みとって頂けていないようで…(^_^;)。
問題の(これだって“問題”なんてまるで欠陥品のような印象を持たせる言葉じゃなくて、百歩ゆずって仮に問題があったとしても、もう少しやんわりとした言い方ってもんがあるでしょう…。おんなじ問題を使うにしてもせめて「俺が問題にしている」とか。それだって「俺が疑問に思う」と言えるわけだし。さらに実際には指摘されるような問題もないし、問題にしていること自体、私の書いたことをろくに読んでもいないとんちかんなことなわけですから、それこそこっちとしては、ただただムカツクだけなわけですよ)コードは違います。プロトタイプにあたる、BankAccount の deposit() や withdraw() に変更をくわえれれば、そのクローンたちである MyAccount 、StockAccount 、MyStock にも即座に波及します。--sumim
たとえば、deposit() のときに 10% ピンハネされるようになったとしましょう。問題の(^_^;)コードでは、def BankAccount.deposit(x)
self.dollars=self.dollars+x*0.9 # self.dollars+=x*0.9
end
のように、プロトタイプで唯一そのメソッドを持っている BankAccount における deposit() の再定義で済ませられます。--sumim
ここで(実装上はそうではなくても。つまり、言語設計者のようなタイプの人の目からではなく)エンドユーザー側から見たプロトタイプベースっぽさを醸し出せたポイントは、
- クラスに手を直接手を下す(ような印象をエンドユーザーに与える)ことで、そのクラスをプロトタイプベース・オブジェクトとして扱っているかのごとき印象を持たせることができた。
- これは Smalltalk では、システムに手を加えて実現してしまったわけですが…。
- Ruby の場合は、特異メソッド追加の作業は、実際にも対象となるオブジェクトに手を下しているので文句なしかと。
ではないかと思います。あとは、
- プロトタイプの性質をクローンが受け継ぎ、
- クローンはスロットにプロトタイブとは別の、独自のオブジェクトを束縛でき、
- 必要ならクローン側で振る舞いの変更やスロットを加えてバリエーションを付けることができ、
- そうしたクローンの振る舞いや属性の変更はプロトタイプには波及せず
- 一方で、逆にプロトタイプでの振る舞いの変更は(必要なら)クローンにも即座に波及する
などなどが、できているのでよろしいかと。
さらに、なぜ「お手軽」にこれらが実現できたかというと、
- Smalltalk の場合…
- クラスはメタクラスの唯一のインスタンスであるため、メタクラスへのメソッド登録は、クラスそれ自体へのプロトタイプベースにおけるメソッド追加と(すくなくとも見た目は)同じと考えることもできる
- クラスの継承関係と、メタクラスの継承関係は必ず一致するので、スーパークラスをプロトタイプスロットに束縛されたプロトタイプと見なすことができる。(実際には、暗示的に設定されるメタクラスの継承関係を利用しているだけ。たとえば CLOS ライクなオブジェクトシステムでクラスを使った例では、クラスの継承関係は記述していない=これはインスタンスのためのものだから、インスタンスの出番のない本件では意味を持たない)。
- Ruby の場合…
- クラスメソッドは、クラスの特異メソッドである
- 名実ともに、プロトタイプベースにおけるメソッド追加と同じ。
- クラスメソッド(クラスの特異メソッド)はインスタンスのそれと異なり、特別な仕組みを設けることなしに継承が可能。
というような特徴が利用できたから…とまとめられると思います。--sumim
いろいろと小技を教えていただきましたので、第3版です。BankAccount=Class.new
class << BankAccount
attr :dollars, true
@dollars=200
def deposit(x)
self.dollars+=x
end
def withdraw(x)
self.dollars=[0,self.dollars-x].max
end
end
BankAccount.dollars=200
=> 200
BankAccount.dollars
=> 200
BankAccount.deposit(50)
=> 250
BankAccount.withdraw(100)
=> 150
BankAccount.withdraw(200)
=> 0
MyAccount=BankAccount.dup
MyAccount.deposit(500)
=> 500
BankAccount.dollars
=> 0
StockAccount=Class.new(BankAccount)
class << StockAccount
attr :numShares, true
attr :pricePerShare, true
def dollars
self.numShares*self.pricePerShare
end
def dollars=(x)
self.numShares=Float(x)/pricePerShare
self.dollars
end
end
StockAccount.numShares=10
StockAccount.pricePerShare=30
StockAccount.dollars
=> 300
StockAccount.dollars=150
=> 150.0
StockAccount.numShares
=> 5.0
MyStock=StockAccount.dup
MyStock.dollars=600
=> 600.0
MyStock.numShares
=> 20.0
MyStock.deposit(60)
=> 660.0
MyStock.numShares
=> 22.0
MyStock.withdraw(120)
=> 540.0
MyStock.numShares
=> 18.0
class << BankAccount
def deposit(x)
self.dollars+=x*0.9
end
end
BankAccount.deposit(100)
==> 90.0 # 0 + 100 * 0.9
MyAccount.deposit(100)
==> 590.0 # 500 + 100 * 0.9
StockAccount.deposit(100)
==> 240.0 # 150 + 100 * 0.9
MyStock.deposit(100)
==> 630.0 # 540 + 100 * 0.9
ん〜。どんどんよくなる。--sumim
…と一瞬(長い一瞬だな…>われ)思ったのですが、やはり、特異クラスを用いた定義は、「お手軽」には反しなくとも、「プロトタイプベース的」にはいけませんね。やはり、ここはコードの読みやすさという意味でも、クラスの定義によるのがスジかと思いますので、先祖返りの第4版です。BankAccount=Class.new
def BankAccount.dollars; @dollars end
def BankAccount.dollars=(x); @dollars=x end
def BankAccount.deposit(x); self.dollars+=x end
def BankAccount.withdraw(x); self.dollars=[0,self.dollars-x].max end
BankAccount.dollars=200
BankAccount.dollars
=> 200
BankAccount.deposit(50)
=> 250
BankAccount.withdraw(100)
=> 150
BankAccount.withdraw(200)
=> 0
MyAccount=Class.new(BankAccount)
MyAccount.dollars=100
MyAccount.deposit(400)
=> 500
BankAccount.dollars
=> 0
StockAccount=Class.new(BankAccount)
def StockAccount.numShares; @numShares end
def StockAccount.numShares=(x); @numShares=x end
def StockAccount.pricePerShare; @pricePerShare end
def StockAccount.pricePerShare=(x); @pricePerShare=x end
def StockAccount.dollars; self.numShares*self.pricePerShare end
def StockAccount.dollars=(x); self.numShares=Float(x)/pricePerShare; self.dollars end
StockAccount.numShares=10
StockAccount.pricePerShare=30
StockAccount.dollars
=> 300
StockAccount.dollars=150
=> 150.0
StockAccount.numShares
=> 5.0
MyStock=Class.new(StockAccount)
MyStock.numShares=10
MyStock.pricePerShare=30
MyStock.dollars=600
=> 600
MyStock.numShares
=> 20.0
MyStock.deposit(60)
=> 660.0
MyStock.numShares
=> 22.0
MyStock.withdraw(120)
=> 540.0
MyStock.numShares
=> 18.0
def BankAccount.deposit(x); self.dollars+=x*0.9 end
BankAccount.deposit(100)
==> 90.0 # 0 + 100 * 0.9
MyAccount.deposit(100)
==> 590.0 # 500 + 100 * 0.9
StockAccount.deposit(100)
==> 240.0 # 150 + 100 * 0.9
MyStock.deposit(100)
==> 630.0 # 540 + 100 * 0.9
1.8.0 では dup() だとうまく動かないので、Class.new() に変えました(つまり、値スロット内容の引き継ぎはいったんあきらめました)。--sumim
>これらのコードの中でsuperと書いたら何が起きる(何が参照される)のかが判ってない。
>道理で、superの出番も有り得ないわけです。なるほど。
>superから
>superに
こうした書き方を拝見するにつけ、どうも戯さんの super に対する解釈は、私のそれとはちょっと(というかかなり)異なるようですね。super は(継承ツリー中での多態を演出するのに必須の)オーバーライドとの関係こそありますが、コード上でのその出現の有無と多態とは直接は関係ないように思います。多態というよりは、メソッドコンビネーションとの関連のほうが深いように思います(CLOS の :before とか :after とかいうやつに端的に表れるあれですね。あれほど組織だってはいませんが、そんな風に使うイメージです)。
私の理解では、super はちょっと変わった(つまりメソッドサーチの順番をひとつだけ無視する) self にすぎません。ですからその self を含むメソッドを保持したクラスを示唆する目的で「self から」とか「self に」という言い回しをしないのと同様の理由で、(名前こそスーパークラスをイメージさせるものですが、その実体はどうあっても self なので)「superから」「superに」という言い回しに強い違和感を覚えます。これが Smalltalk をちょっとかじって(これは当てはまるかもしれませんが)訳知りのオブジェクト指向プログラミング初学者(ということは、あり得ない!)とかの言なら笑って聞き過ごすこともできるのですが…。なお、ばぶばぶに、スーパークラス、あるいはプロトタイプという概念がないのでしたら(すみません。白状すると仕様をもきちんと読んでいません(^_^;))話は別です。ごめんなさい。--sumim
>なお、ばぶばぶに、スーパークラス、あるいはプロトタイプという概念がないのでしたら
ばぶばぶにスーパー云々の概念があるかないか、という事と、戯のここでの主張と、どのような関わりがあるのでしょうか?--CUE
このページを編集 (33522 bytes)
|
以下の 1 ページから参照されています。 |
This page has been visited 5233 times.