この記事で後回しにしていた継承部分について書きます。
golang は継承がない!なんてこと言っていますが、ありますよ。構造体に対する埋込で可能になります。
package main import "fmt" type Parent struct { name string } func (p Parent) Print() { fmt.Println("Parent->Print:", p.name) } type Child struct { Parent } func main() { p := Parent{ "PARENT" } c := Child{ Parent{ "CHILD" } } p.Print() // Parent->Print: PARENT c.Print() // Parent->Print: CHILD }
Parent を Child に埋め込んでやるとメンバの変数参照はそれっぽく動きます。埋め込みの構造体の初期化の仕方が気持ち悪すぎですし、参照時との感覚のズレ感が半端ないですけどね。
動作的に通常の継承と違うのは
package main import "fmt" type Parent struct { name string } func (p Parent) PrintCore() { fmt.Println("Parent->PrintCore:", p.name) } func (p Parent) Print() { p.PrintCore() } type Child struct { Parent } func (c Child) PrintCore() { fmt.Println("Child->PrintCore:", c.name) } /* これが無いと Print で Child 側の PrintCore が呼ばれない func (c Child) Print() { c.PrintCore() } */ func main() { p := Parent{ "PARENT" } c := Child{ Parent{ "CHILD" } } p.Print() // Parent->PrintCore: PARENT c.Print() // Parent->PrintCore: CHILD }
子側で、Print をきちんと定義してやらないと、親の PrintCore を呼んじゃいます。上のコードでコメントを外すと、
c.Print() // Child->PrintCore: CHILD
こうやって、ちゃんと子側の PrintCore が呼ばれるようになります、が、同じもんを何度も書かなきゃならんのはひたすら不便です。Nim はこうです。
type Parent = ref object of RootObj name: string Child = ref object of Parent proc printCore(this: Parent) = echo "Parent->printCore: ", this.name proc print(this: Parent) = this.printCore proc printCore(this: Child) = echo "Child->printCore: ", this.name let p = Parent(name: "PARENT") let c = Child(name: "CHILD") p.print # Parent->printCore: PARENT c.print # Parent->printCore: CHILD
ほら、見事に親の printCore が呼ばれちゃってますね・・・。って、golang と同じじゃん!
と言いたいところですが、Nim はそんなにへっぽこじゃありません。
method printCore(this: Parent) = echo "Parent->printCore: ", this.name method printCore(this: Child) = echo "Child->printCore: ", this.name proc print(this: Parent) = this.printCore let p = Parent(name: "PARENT") let c = Child(name: "CHILD") p.print # Parent->printCore: PARENT c.print # Child->printCore: CHILD
printCore の proc を method に変えるだけで普通の継承の動作になります。楽勝です。
golang も毎度毎度子側にも同じメソッドを定義しなくても良いようになる方法があるんちゃうん?とお思いの方、正解です。
インターフェースと言う呪われたやつでどうにか出来ている勘違いをする事ならできます。
package main import "fmt" type Printer interface { PrintCore() } func Print(printer Printer) { printer.PrintCore() } type Parent struct { name string } func (p Parent) PrintCore() { fmt.Println("Parent->PrintCore:", p.name) } type Child struct { Parent } func (c Child) PrintCore() { fmt.Println("Child->PrintCore:", c.name) } func main() { p := Parent{ "PARENT" } c := Child{ Parent{ "CHILD" } } Print(p) // Parent->PrintCore: PARENT Print(c) // Child->PrintCore: CHILD }
さぁどうですこれ。Printer と言うインターフェースを定義して PrintCore を持たせます。そいつを引数に取る関数 Print を定義します。そいつに構造体を渡してやると、それっぽく動きます・・・。が、これを漢字1文字にするならば「キショイ!」「ウザい!」「ダルい!」ですかね。安倍ちゃんもびっくり漢字ですら無い、さらに3つ、ですね。
ないでしょぉ、これ・・・。呼び出す時に関数ってのが気持ち悪すぎですし・・・。こう書かせろ!と思いませんか。
func (printer Printer) Print() {
printer.PrintCore()
}
こうやって定義して
c.Print()
これで呼び出し。だいぶマシ、と言うか何故こうじゃないんだ?と思うでしょ、普通は。
さらに最悪なのが、インターフェースだけ見たって一体何の構造体を渡して良いのか判断ができません。メソッドの名前と引数、返り値しが一致してさえいればなんでも渡せると言うのは混乱の元でしか無いです。
このメソッドもっとったら渡してええんちゃうんかーーい!が必ず発生します。
こんなのでやりたい放題やったら、現場の人間が「どうなっとんじゃぁ~~!」泣いて叫んでいる姿しか目に浮かびません。
結局現場では名前をきっちりつけて管理するしか無いんでしょうね。
素直に
type Parent struct implements Printer { name string }
こう書ければ、あぁ Parent 渡せるんだ、とわかりますし、余計なものを間違って渡して死んでくれると多くの人が助かるんですがね。
一応、これげな書き方はできます。非常に気色悪いですが。
package main import "fmt" type iParent interface { PrintCore() } func Print(ip iParent) { ip.PrintCore() } type Parent struct { iParent name string } func (p Parent) PrintCore() { fmt.Println("Parent->PrintCore:", p.name) } type Child struct { Parent } func (c Child) PrintCore() { fmt.Println("Child->PrintCore:", c.name) } func main() { p := Parent{ name: "PARENT" } c := Child{ Parent{ name: "CHILD" } } Print(p) // Parent->PrintCore: PARENT Print(c) // Child->PrintCore: CHILD }
Parent にインターフェース iPranet を入れています。こんな感じで、構造体にインターフェースがぶち込めるんです。
構造体に関数の宣言は書けないのに、何故インターフェースが入れれるのかは謎です・・・。
こう書くと、そいつがインターフェースなのか構造体なのか全くわからんのでハンガリー記法でもやってお茶を濁すしかありません。
さらにこれで書いたとしても、間違った構造体をインターフェースにぶち込んでしまうミスからは逃げられません。さらにインターフェースを埋め込んだのに、そのメソッドの実装がなくてもエラーにもなんにもなりません。完全なる片手落ちと言うか両手がもげ落ちています。
どのインターフェースにぶち込めるかはこう書いとけばわかりはしますが、そのおかげで構造体の初期化でイチイチメンバー名を書かなきゃならなくなったりするので恐ろしくイマイチな代物だったりします。
p := Parent{ "PARENT" }
こう書いておけばよかったのが
p := Parent{ name: "PARENT" }
こうなります。ため息しか出ません。
だから、インターフェースは構造体のそばにコメントで書いておくのが一番なんじゃねぇかと思ったりしますね。
type Parent struct { // implements iParent name string }
こうですよ。馬鹿馬鹿しすぎて笑えませんが、わりかし本気だったりします。
もうね、素直に implements 書かせろや!と思いませんか。どころかこんな継承の真似事ができてるんだからそもそも extends で書かせろや、と思いません?
世の中にインターフェースなんて要らなくて、抽象クラスさえありゃ良いと自分は昔から言っていますが、このインターフェースは、今までのどの言語のインターフェースよりヒドい。他の言語との乖離が激しすぎてインターフェースと呼んじゃダメなんじゃないかと思いますよ。インターフェースとしての基本的な機能が丸ごと削げ落ちた単に複数のメソッドをまとめた型でしかありません。なんでも渡せるやつは interface{} とやっときゃ良いとかもう投げやりすぎますし。メソッド関係ないからなんでも渡せるよねぇーみたいな・・・。
普通コードを書く側としては、そのインターフェースを食うやつにマッチするように implements した側で忘れず抽象メソッドを全て実装するためのものとしても使うんですが、その用途として全く役に立ちゃしない。忘れても知らんぷりですわ。中途半端な実装しちゃって、いざ使う時にエラーになってイライラするんでしょ。
そもそも golang は何を頑なに「継承はないんだ!そもそも継承なんて要らないんだ!」と発狂しているのか全く理解ができない。
継承が要らないなんて同じコードを何度も書きたいマゾヒストの言う事です。似たようなコードをあちこちに散乱させ、メンテのしづらさ爆発させて毎晩徹夜させたいと言う絶対にお友達になりたくない方々の言う事ですって。
どのオブジェクト指向型言語にも継承が何故あるのかを考えたことすら無いんでしょうか。
何だかんだ言っても結局必要だから似たようなことができるようになっているものの、下手に継承要らない!なんて強情張っているからめっちゃくちゃ中途半端なものになってるじゃないですか。
もはや golang の継承は単に書き方の問題ですって、これ。後はちょっとエラー処理足せば良いだけでしょ。golang のあるべき正しい姿を書くと
package main import "fmt" type iParent interface { PrintCore() } type Parent struct implements iParent { name string } func (p Parent) PrintCore() { fmt.Println("Parent->PrintCore:", p.name) } func (p Parent) Print { p.PrintCore() } type Child struct extends Parent { } func (c Child) PrintCore() { fmt.Println("Child->PrintCore:", c.name) } func main() { p := Parent{ name: "PARENT" } c := Child{ Parent{ name: "CHILD" } } p.Print() // Parent->PrintCore: PARENT c.Print() // Child->PrintCore: CHILD }
こうありたい。ココまで来るとやはり抽象クラスも欲しいです。
abstruct type Base struct { PrintCore() name string }
こうやって抽象クラスが作れるとかで良いと思いますがね。
さぁ、ここまで golang の継承をボッコボコに批判してきましたが、ここからべた褒めします。
こっち系の原理主義者が大っ嫌いそうな多重継承が golang はできますからね。そこに関してはとても素敵です。
package main import "fmt" type Card struct { name string } type Kusoge struct { tears int } type ToyBox struct { Card Kusoge } func (k Kusoge) Print() { fmt.Println("Kusoge:", k.tears, "namida") } func (c Card) Print() { fmt.Println("Card:", c.name) } func main() { tb := ToyBox{ Card { "Pokemon" }, Kusoge { 100 } } tb.Card.Print() // Card: Pokemon tb.Kusoge.Print() // Kusoge: 100 namida }
おもちゃ箱にカードとクソゲーが入っています。クソゲーには涙が詰まっています。Print と言うメソッドがバッティングしていても、キャストじゃなくてこんな暴力的かつ華麗な方法で呼べます。
「え?なんでここだけこんなに粗暴なの?」とびっくりしちゃいますね。とても素敵です。
Nim は多重継承できません。ここは素直に負けですね。
オブジェクト指向は本来多重継承できるべきなんですよ。私たちだってとーちゃん、かーちゃんの多重継承ですよ。多重継承しないと表現できないオブジェクトだらけなんですよ、世の中は。
多重継承に助けられる事はあっても邪魔されることはないですって。嫌なら単一継承でやりゃぁ良いんですから。できることは多い方が良いです。
Perl の多重継承の考え方が一番好きですけどね。@ISA に突っ込まれたパッケージの順番でしか管理していなくて、それを実行時にガンガン入れ替えたり出し入れしたりもうやりたい放題できると言うね。あまりやりすぎるとある程度のレベル行った人しか理解できないコードになっちゃいますけど、Perl だし良いんです、それで。
と言うわけで、golang の継承は多重継承部分だけは最高です。Nim も出来るようになって欲しい。