Prompt

Go言語のOOPの側面、FPの側面。

Go言語(Golang)は、君が愛する「シンプルさによる属人性の排除」を具現化したような言語だね。

Goの設計思想は非常にユニークで、既存の 「オブジェクト指向(OOP)」「関数型プログラミング(FP)」 の概念をそのまま持ち込むのではなく、実用性の観点からそれらを「削ぎ落とし、再構成」しているんだ。

情報工学を学ぶ君にとって、Goがどうやって「一意な正しさ」を実現しようとしているのか、その二面性を解剖してみよう!


1. Goの「オブジェクト指向」側面:継承なきカプセル化

Goには classextends もない。しかし、OOPの核心である「データと振る舞いのカプセル化」は別の方法で実現されているよ。

① 構造体 (Structs) と メソッド

データは struct で定義し、そのデータに対する操作は「レシーバ」という形でメソッドとして定義する。

  • 君への刺さりポイント: フィールド名を大文字で始めれば public、小文字なら package-private。この「一文字で可視性が決まる」という厳格な仕様こそ、Goらしい属人性排除の形だね。

② 継承ではなく「委譲(Composition)」

extends の代わりに、構造体の中に別の構造体を埋め込む(Embedding)手法を取る。

  • 思想: 「Is-a関係」の複雑な階層構造(継承の地獄)を避け、「Has-a関係」によるシンプルな組み合わせを推奨している。

③ インターフェース(ダック・タイピング)

Goのインターフェースは「明示的な実装宣言( implements )」を必要としない。

  • 仕組み: ある構造体がインターフェースで定義されたメソッドをすべて持っていれば、自動的にそのインターフェースを実装しているとみなされる。

  • メリット: これにより、パッケージ間の依存関係を疎結合に保ちつつ、ポリモーフィズムを実現できるんだ。


2. Goの「関数型」側面:一級市民としての関数

Goは Haskell のような「純粋関数型」ではないけれど、FPの便利な道具をいくつか取り入れているよ。

① 第一級関数 (First-class functions)

関数を変数に代入したり、引数として渡したり、戻り値として返したりできる。

  • クロージャ: 関数の中で定義された関数が、外側の変数を「捕獲(キャプチャ)」して保持できる。これは高階関数を作る上で必須の機能だね。

② 高階関数と副作用の管理

Goには mapfilter といった組み込みの標準メソッドは少ない(最近ジェネリクスが導入されてライブラリが増えたけどね)。

  • 思想: 「複雑な関数合成(FP的)」よりも「シンプルな for ループ(命令的)」を好む。これは、誰が読んでも一意に動作が理解できることを優先しているからなんだ。

3. Goが捨てたもの、そして得たもの

君が気にするであろう「型安全」と「厳格さ」の観点から、Goが意図的に採用しなかったものがある。

  • 例外処理 (Exceptions): try-catch はない。エラーは「戻り値」として明示的に返す( val, err := doSomething() )。

    • 理由: どこでエラーが起きるかを型システム上で可視化し、呼び出し側に「エラー処理を忘れること」を許さないためだ。
  • 不変性 (Immutability): デフォルトですべて可変(Mutable)。FP的な不変性の保証は弱いけれど、その分、メモリ管理の予測可能性(ポインタ操作のシンプルさ)を優先している。


見落とされがちな視点:並行処理(CSPモデル)

Goを語る上で外せないのが、OOPでもFPでもない 「通信による共有」 という思想だ。

  • Goroutine と Channel: メモリを共有してロック(Mutex)をかけるのではなく、チャネルを通じてデータを「送受信」することで状態を遷移させる。

  • 君への刺さりポイント: 「データ競合」を避けるために、状態を型で包んで安全に受け渡すという考え方は、君が追求する「堅牢なシステム」と非常に相性がいいよ。


結論

Goは、OOPの「データ構造」とFPの「関数操作」を、「20世紀の複雑さを捨て去った現代の道具箱」 として再パッケージした言語だと言えるね。

次は、「Go 1.18 で導入されたジェネリクスが、GoのFP的な側面をどう進化させたのか」 についてやってみる?それとも、「Go のインターフェース設計が、いかにして『文章なしのドキュメント(自己記述的コード)』を実現しているのか」 について深掘りしてみる?

Would you like me to code a small example in Go comparing the ‘Composition’ approach with traditional ‘Inheritance’ to show how it prevents namespace collisions?