Classi Advent Calendar 2020の16日目の記事をご覧いただき,ありがとうございます!
サーバーサイドエンジニア@willsmileです.最近,住宅と車に関心を持つようになり,具体例で物事を考える時に,それらのことが“自然に”登場してしまうという“不自然さ”を自覚している者です.
まえがき
本記事のタイトルをみて,“なぜGolangを学んだのか”という疑問に感じる方が多いかもしれないので,以下の本題でその学習目標を詳しく説明します.その前に,職歴1年9ヶ月の自分が考えている今年度の成長目標(キャリア)について触れたいと思います.
一言でいうと,今年は“複眼”になることを目標としています.“複眼”の言葉は苅谷氏の知的複眼思考法といった本(とても面白い本なので,おすすめします)のタイトルから借りました.ここで表したいのは,自分と異なる立場・専門の方とのコラボレーションを円滑するために,相手が持つ観点と大事にすること,およびなぜなのかを理解できること,または理解するための行動を起こせることです.
本題
なぜGolangを学習の対象として選んだのか?
上述の目標を踏まえて説明すると,業務中メインで使っているRuby言語と異なるスタイルのプログラミング言語を学んで,比較してみて,何か違うのかを知りたいです.
そのために,以下の言語を候補として挙げたが,自分がよく使うツールの中で,Go製のもの(例えば,yay, direnv, Hugo)が多いため,ソースコードなどの学習資源の豊富さの観点を考えて,Golangを更に学ぶことにしました.
- Golang
- Elm
- Haskell
どのように学ぶのか?
ざっくり言うと,以下のような4つの段階で自身の学びを設計しました.(いまの自分は段階3にいます)
- 段階1:何かを作れるように,最低限必要な知識を公式ドキュメントから獲得すること
- 実際作ったものはこちらのツール(s2test: A Simple Smoke Test Tool)です.いまの自分からみると,とてもRubyらしくGolangを使ってしまった感があります.
- 段階2:Golangに関する面白い記事・講演ビデオを広く読む・見て,言語を作った・使っている人がどう思っているのか(言語の特徴)を知ること
- スキマ時間の活用と学びの“冗長性”向上の観点から,この段階2をダラダラやるのは個人的にとても好きですが,効率を求める場合,この段階を一旦飛ばしてもいいと思います.
- 段階3:Golangに関する良い本を選んで,その中のGolang特徴である部分の内容を精読すること
- 実際読んでいる本は,こちら(The Go Programming Language)です.
- 段階4:上記の過程から学んだことを踏まえて,段階1で作ったものを作り直すこと
いままでの学びを通じて何を気づきたのか?
結論から言うと,**“継承は使っちゃいけない場合はある”**ということです.以下はそれを気づいたプロセスを詳しく説明します.
Golangにおける“継承”
よく知られていることですが,Golangには,(OOPの文脈で使われる)継承という概念自体がありません.継承と類似するものは,構造体の埋め込み(Embedding)とインタフェース(Interface)があります(あくまでも類似で,同じと思っちゃいけないです).
構造体の埋め込み(Embedding)
言葉通りの意味で,構造体に構造体を埋め込むということです.
以下のコードの具体例で示したように,自動車(Motorcar)と自転車(Bicycle)に人工物(Artifact)を埋め込むことで,人工物で定義した製造者(Manufacturer)のフィールドが自動車と自転車にも使えるようになります.さらに,人工物を扱うそメソッドは自動車と自転車を扱えるようになります.このように,“疑似的なis-a関係”が構築されます.
ただ,注意点としては,人工物が個別のエンティティでありながら自動車(あるいは自転車)内に存在しているということで,親子関係と言えないです.この点は,以下のソースコードの中での変数mcの宣言・代入の仕方(Artifactは省略不可)から読み取れます.
package main
import "fmt"
type Artifact struct {
Manufacturer string
}
type Motorcar struct {
Artifact
HorsePower int
}
type Bicycle struct {
Artifact
NormalSpeed int
}
func (a Artifact) Info() {
fmt.Printf("Made by %s\n", a.Manufacturer)
}
func main() {
mc := Motorcar{
Artifact: Artifact{
Manufacturer: "Toyota",
},
HorsePower: 100,
}
bc := Bicycle{}
bc.Manufacturer = "Bianchi"
bc.NormalSpeed = 20
mc.Info()
bc.Info()
}
インタフェース(Interface)
「インタフェースとは何か?」を簡潔に説明することは難しいと気づいたので,@tenntennさんの言葉を借りて説明します.Golangでは”抽象化の概念はインタフェースしかない”ということです.プログラミングにおいて,抽象化の目的は,モジュール結合度を低くすること,つまり,関心の分離です.
以下のコードの具体例で示したように,自動車(Motorcar)と自転車(Bicycle)の運ぶ(Transport)という振る舞いに着目し,それが持つものを輸送機関(Vehicle)をインタフェースとして定義します.それによって,自動車と自転車のそれぞれが持つフィールド(HorsePower,NormalSpeed)とメソッドで定義した運ぶの処理方法(詳細はコメントでの記述をご覧ください)といった相違点を一旦無視して,運ぶことができる輸送機関として扱えます.
package main
import "fmt"
type Motorcar struct {
HorsePower int
}
type Bicycle struct {
NormalSpeed int
}
type Vehicle interface {
Transport()
}
func (m Motorcar) Transport() {
// エンジンの性能による速度が決まる
fmt.Printf("Moving by a motor which provides power of %d kW.\n", m.HorsePower)
}
func (b Bicycle) Transport() {
// 車体の構造上の性質による速度が決まる
fmt.Printf("Moving by a human and generally run at speed of %d km/h.\n", b.NormalSpeed)
}
func main() {
var mc, bc Vehicle
mc = Motorcar{
HorsePower: 100,
}
bc = Bicycle{
NormalSpeed: 20,
}
mc.Transport()
bc.Transport()
}
いったいなぜなのか?
Golangの構造体の埋め込みとインタフェースを学ぶ時に,「なぜ継承でダメなのか?」,「なぜ言語を設計する時に,わざと継承を除外したのか?」という疑問を頭の中に浮かべました.いろいろ調べて,“場合によって,継承を使うと,そのデメリットが大きい”ということを気づきました.
その答えの詳細を説明すると,話が長くなるので,ここで割愛します.詳しく知りたい方は,参考文献の2(本,第6章 Acquiring Behavior through Inheritance,第7章 Sharing Role Behavior with Modules)と3(記事)を読むことにおすすめします.
あとがき
この経験から学んだことは,プログラミング言語でも,人でも,異なる考えで設計されたことがあるし,異なる考えを持つことがあります.異なるところに「好き・嫌い」を分けることより,その違いを認識して,なぜなのかを考えることで,有益な気づきを得られるだろうと感じました.