きみは Generics がとくいなフレンズなんだね,または「制約は構造を生む」
今回は戯れ言モードなので「プログラミング言語 Go」ではなくこちらで書いてみる。 コードは1行も書かないのでご安心を(笑)
私は出自が組込みエンジニアで(今は何でも屋),アセンブラや C/C++ から始まり Java などの制御に向いていると言われる言語を遍歴している(PHP を機器制御に使うとかいうこともやったが)。 Go 言語もその流れから興味を持っているが,あいにく私が住んでいる地方都市で Go 言語の出番はまだない。
そういう経歴を持つ私から見て Go 言語が特異だと思ったのは以下の2点である。
- 例外処理がない
- 明示的なクラス定義構文がない
私だけでなく C++ や Java などから来た人は大抵これで面食らうらしい。
このうち1番目については「プログラミング言語 Go」で記事にしたので割愛する。
さて,2番目の「明示的なクラス定義構文がない」について。
そもそも「クラス」とはなにか。 クラスとは以下の要素をひとまとめの「モノ(object)」として定義したものである。
- 名前(必ず1個)
- 属性(0個以上)
- 操作(0個以上)
Go 言語では明示的なクラス定義構文がない代わりに type と struct,およびメソッド・レシーバを組み合わせることでクラスの要素である名前,属性,操作を定義できる。
そしてクラス定義で重要なのは「クラス間の関係」を定義することである。 クラス間の関係としては大雑把に以下の2つがある。
- 汎化・特化(継承 等)
- 関連(集約,依存 等)
このうち2番目の関連は定義しやすい。 あるクラスの属性として別のクラスを定義するか,操作によって関連付けるかすればいいからだ。 問題は1番目の汎化・特化をどうやって定義するかである。
Go 言語の場合は interface を使った「構造的部分型(structural subtyping)1」を採用した。 構造的部分型とはクラスの振る舞いに注目してクラス間の汎化・特化関係を帰納法的に定義することである。 例を挙げると,それが「にゃーん」と鳴くのなら机器猫だろうが猫耳メイドだろうがサーバルキャットだろうが全部「猫」である,ということだ。
クラス間の関係を定義するのは意外に大変である。 皆さんは「クラス設計」をどのように行っているだろうか。 まずは具体的なクラスを列挙していき,それらの関係を考察していくのではないだろうか(「ユーザ」や「管理者」を定義するのに 動物→人間→… と考えていく人はいないだろう)。 考察する過程で(クラスとクラスを繋ぐ)不可視のクラスを発見したり複数のクラスがひとつの概念で括れることに気づいたりすることもある。 つまり設計する過程では「具象→抽象」へと遡っていく。
一方,実装する際には, C++ や Java では最初にテンプレート・クラスやインタフェース・クラスを作ってからインプリメント・クラスに落とし込む。
たとえば,最初に「猫」という抽象クラスを作っておいて,それを継承する形で机器猫や猫耳メイドやサーバルキャットといった具体的なクラスを実装していく。 つまり「抽象→具象」へと作業していくわけだ。 そしてその過程において Generics2 は,ほとんど必須と言えるほど利用価値の高い機能と言える。
これが Go 言語による実装ではひっくり返る。 たとえば,最初に机器猫や猫耳メイドやサーバルキャットといった具体的なクラスを作っていって「これってみんな『にゃーん』って鳴くじゃん」と気がつけば後付けで「猫」という抽象クラスを実装できるのである。
どういうことかというと, Go 言語においては設計と実装を同時進行で「具象→抽象」へと考察していくことができる,ということである。 このような思考過程においては Generics の有無はさして重要ではなくなる。 だって具象化されたオブジェクトから作り始めるのだから。
「抽象→具象」へと実装する人にとっては Generics のない Go 言語はとてもまだるこしく見えるかもしれない。 「なんで Generics がねーんだよ。いちいち全部書かせる気か。このポンコツ言語が!」となること請け合いである。 しかし一度構造的部分型に慣れた人にとっては抽象クラスから書かなければならない C++ や Java こそが面倒くさい。 何故なら,脳内では「具象→抽象」で思考していくのに実際に書くときには「考え終わらないと書けない3」からである。 Go 言語なら「考えながら書ける」のに。
これはどちらが正しいかという問題ではない。
たとえばウォータフォール型4 の開発スタイルでは実装を開始するまでに設計が終わることが(建前上は)保証されているため「抽象→具象」へと書き進めることが容易な言語が向いている。 一方,要件が絶えず変わったり実験的な製品の場合は設計が終わるまで待っていられないため Go 言語のような言語が向いてるかもしれない。 まぁ設計と実装を同時にやろうとするとリファクタリングが頻繁に発生するのでコピペ・プログラマにはキツい作業になるかもしれないが。
個人的には「プログラマは要件定義の段階から参加してコードを書くべき」と思ってるので,これを容易にするであろう Go 言語には注目している。
2018-10-11 追記
“Go 2 Draft Designs” において将来バージョンにおける Generics 対応の言及がある。
ブックマーク
- Why Everyone Hates Go · npf.io
- Why Go? | Dave Cheney
- Go 言語における「オブジェクト」
- JavaScriptで継承を使わないプログラミングスタイル - JavaScript勉強会 : オブジェクト指向設計について上手くまとめている
- Go にジェネリクスがなくても構わない人たちに対する批判について - methaneのブログ
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。
- 数学ガール/フェルマーの最終定理
- 結城 浩 (著)
- SBクリエイティブ 2008-07-29 (Release 2014-03-12)
- Kindle版
- B00I8AT1CM (ASIN)
- 評価
「フェルマーの最終定理」というサブタイトルをみたとき「なんちう大風呂敷を広げるねん」と思ったものだが,実際に読んでみるとぐいぐい引き込まれる。ひっさびさに頭を使ったような気がする。
-
URL を見るとわかる通り最初は duck typing と表記していたが正しくは「構造的部分型」と言うらしい。 duck typing は主に動的型付け言語における型推論方式(のひとつ)で,クラス間の関係を記述するものではないようだ。ちなみに構造的部分型に対する言葉として「公称型(nominal subtyping)」というのがあって, C++ や Java におけるテンプレート・クラスやインタフェース・クラスを使った汎化・特化関係を指す場合に使うそうだ。 ↩︎
-
知らない人のために Generics について簡単に説明しておくと,変数の型あるいはインスタンス(instance)に対するクラス(class)に関係なく単一の記述で変数ないしインスタンスを扱うことのできる仕組みである。汎化の一種と考えてもよい。いわゆる多態性(polymorphism)とは異なり,継承関係の異なるクラスでも一緒くたに扱うことが可能なかなり強力な仕組みである。 Generics は特にコンテナ(container; オブジェクトの集まりを表現するデータ構造,配列など)操作で威力を発揮する。 ↩︎
-
私はこれを「写経」と呼んでいる。はっきり言ってプログラミングでもっとも苦痛なのがコーディング=写経だったりする。ちなみに一番好きなのはデバッグ。特に他人の書いたコードをデバッグするのは大好物。あれは極上の数理パズルである(締切さえなければね)。 ↩︎
-
「ウォータフォール型」とは滝の水が上から下へと落ちていくように 要件定義→設計→製造 と上流工程から下流工程へ順番にプロセスを進めていく開発スタイル。工程ごとにマイルストーンを設けてチェックを行い,各工程が完了しないと先に進めないようにする。まぁ実際にはスケジュールやらの関係でチェックを端折って先に進めてしまうことが多く,下流工程に入ってから致命的な欠陥に気づいて抜き差しならない状況に陥ることもしばしばある(笑) ↩︎