2014年11月30日(日)に、楽天タワーにてGo Conference 2014 Autumnが開催されました。Go Conference(GoCon)は2013年に始まり、年に2回、春と秋に開催される日本最大のGo言語のカンファレンスで、今回で4回目となります。今回は基調講演にGo言語の父と呼ばれるRob Pike氏と日本のGo言語の第一人者の鵜飼氏を招いていることもあり、今までで最も大きい規模となりました。
この記事では1人目のRob Pike氏の基調講演についてレポートを書きたいと思います。この基調講演では「Simplicity is Complicated」というタイトルで、Go言語の成功の要因となっている単純さについて話がされました。
Go言語が成功している理由
はじめにGo言語の成功している理由について話がされました。Go言語が成功している理由として、
- コンパイルの速さ
- 実行の速さ
- デプロイの容易さ
- ツール(go tool)
- 標準ライブラリ
などのGo言語のエコシステムがよく挙げられるそうです。
また、言語の特徴としては、
などが挙げられるそうですが、どれも本当の理由ではないということでした。 同氏が考える成功の理由は、Go言語のSimplicity(単純さ)です。
言語の収束と相対性
多くの言語は、新しいバージョンで言語に新しい機能を入れようとしています。同氏が参加した、Lang.Nextというカンファレンスでの話題は、Java、JavaScript(ECMAScript)、TypeScript、C#、C++、Hack(PHP)の新しいバージョンについてだったそうです。これらの言語は他の言語から機能を取り込むことで、新しい機能を提供しようとしているそうです。そして、同氏はこれらの言語は、1つの大きな言語に収束しているように見えると指摘していました。
自然言語では、言語が思想に影響するという説(サピア=ウォーフの仮設)があります。プログラミング言語にも同様のことが言え、さまざまなパラダイムの言語が存在しています。たとえば、
- 論理プログラミング
- 手続き型プログラミング
- 関数プログラミング
- オブジェクト指向プログラミング
- 並行プログラミング
などがあり、数学の分野のように多様性を持っているとのことでした。
同氏は、多くの言語が機能追加をすることで競争力を高めようとすると、その多様性が失われると指摘していました。異なる問題には異なる考え方を持つ言語が必要だと考えているそうです。そのため、Go言語は言語機能を追加することで他の言語と競争するようなことはしませんでした。Go言語を始めたばかりの人たちは、他の言語にある機能を入れて欲しいと求めることが多いそうですが、Go言語はそれらの言語機能を増やすことはしませんでした。
また、2012年3月にリリースされたGo1で言語機能は固定されているため、今後のマイナーバージョンアップでは言語機能が増えることはないでしょう。同氏はGo言語に新しい言語機能を追加することは、Go言語の改善につながるとは考えてません。安易な言語機能の追加はGo言語を肥大化させ、他の言語との差異をなくし、つまらない言語にしてしまうと考えています。
可読性
Go言語の言語機能は、同氏と異なるバックグラウンドを持ったRobert Griesemer氏、Ken Thompson氏の同意のもとに正しく設計されているそうです。同氏は、可読性が最重要項目と考えており、そのためには言語機能をできるだけ単純にしたいと考えています。また、複数の選択肢があると、どの機能を使用するか選択するために時間を使ってしまうと指摘していました。そして、実装してしばらくするとなぜそのコードが動くのか書いた本人でもわからなくなってしまうことがあると述べていました。同氏は、単純なコードでも、言語の複雑さにより理解しづらいものになると考えているそうです。そのため、機能を増やすことは複雑さが増すことにつながるため、あることを実現する方法はできるだけ少なく、単純であるべきだと主張していました。
同氏は、可読性のあるコードは、信頼できるコードだと述べていました。そして、信頼できるコードは、理解しやすく、取り組みやすく、壊れてしまっても簡単に修正できるとのことでした。同氏は、言語が複雑であれば、コードを読んだり、コードに取り組むために多くのことを理解する必要があり、デバッグや修正のためにはさらに多くのことを理解しなければならないと指摘していました。そして、そこには「書くことが楽しいか?」と「保守が楽か?」という重要なトレードオフが存在していると述べていました。
表現力
Go言語に対して、「表現力」を補うための機能が提案されることがあるそうです。しかし、同氏は簡潔であることは表現力を高めることになる場合もあるが、可読性が高いとは限らないと指摘していました。例として、APLでライフゲームを実行する以下の1行のコードが挙げられていました。
同氏は、単純な考えを強力すぎる基本機能で実装すると実行コストが上がってしまう場合もあり、パフォーマンスの予測もしづらくなると指摘していました。一方で、コードが冗長だと本来の意図を曖昧にしてしまい、可読性が下がってしまうと述べていました。よく知られた馴染みのある考え方に基づいて実装し、しかしその考え方だけにとらわれないようにするべきとのことでした。つまり、表現力を残しつつも簡潔に表現するべきだということです。
正しい言語機能
同氏は、正しい言語機能とは「機能のための機能」ではなく、「隙間を埋める機能」であると主張していました。 「隙間を埋める機能」とは、解空間を覆う基底ベクトルのような、予想したとおりに作用する直交する機能で、単純に動作する単純な機能であるそうです。 同氏は、単純さは直交性と予測可能性に依るものであると述べていました。 そして、言語の目的を常に肝に命じて、言語機能を決めるべきだと主張していました。
Go言語の目的
同氏は、Go言語はスケーラブルなクラウドソフトウェアのために設計されたきれいな手続き型言語であると述べていました。そして、以下のような単純で組み立て可能な要素から構成されているとのことでした。
- 具象データ型
- 関数とメソッド
- インターフェース
- パッケージ
- 並行性
また、これらに加えて良いツール(go tool)と速いビルドを提供していると述べていました。
同氏は、Go言語のマスコットであるGopherを使って、細かく描写されたGopherと比較することで、単純さの重要性を訴えていました。
Go言語が提供する単純さ
Go言語は裏にある複雑さを隠し、利用する人に単純さを提供しているそうです。そして、各機能はお互いにシームレスに調和し、驚きがないように作られているそうです。Go言語では、以下の機能が裏にある複雑さを隠して、単純さを提供しているとのことでした。
- ガベージコレクション
- ゴルーチン
- 定数
- インターフェース
- パッケージ
ガベージコレクション
ガベージコレクションは、複雑さを隠す単純さの最たる例であるとのことでした。同氏は、ガベージコレクションをうまく実装するのは難しいが、実装する価値があり、コードが単純に書けるのはガベージコレクションのおかげだと主張していました。そして、ガベージコレクションがあることで、設計に「所有権」を含める必要がなく、free
を使うこともないと述べていました。
ゴルーチン
Go言語では、並行性を提供しています。並行性とは、プログラムを独立して実行している部品のように書ける能力のことだそうです。Go言語の並行性は以下の3つの要素から構成されています。
- ゴルーチン(実行)
- チャネル(コミュニケーション)
- select(調整)
ゴルーチンは、キーワードのgo
を使うことで起動できます。
これは通常の関数呼び出しに、余分に3つのキー「g」、「o」、「スペース」を押すだけで、非常に単純であると同氏は述べられていました。しかしながら、ガベージコレクションのように、プログラマの心配からくる以下のような考えごとをなくしてくれると主張していました。
- スタックサイズがない
- returnや終了ステータスがない
- 管理する機構がない
- ゴルーチンのIDがない
特に
これらの機能は、他のシステムでは提供されていますが、Go言語では、最小設計にしているそうです。しかし、その実装は複雑でスタックの管理はガベージコレクションに依存しているそうです。
定数
同氏は、Go言語の定数はただの数で、たとえ強く型付けがされていても同様だと述べていました。これは単純な考えですが、実際にそのように決めるには1年ほどかかったそうです。特に難しかった点は以下だったそうです。
- 「無限」精度整数型
- 「無限」精度浮動小数点型(試してみて、有理数で失敗した)
- 昇格規則(
i := 2; f := 2.0; g := 1/2; h := 1/2.0
)
- シフトのようなコーナーケース(
fmt.Printf("%T %T", 2.0, 2.0<<0
)
これらは十分に満たしているというわけではないそうですが、定数を数字のように扱えるようになっているため、Go言語の使いやすさに貢献しているとのことでした。定数については、この記事を見ると良いそうです。
インターフェース
同氏は、Go言語のインターフェースは以下のようにただのメソッドのまとまりで、データではないと述べていました。これは単純な考えのように見えますが、実装は予想以上に複雑であるそうです。
たとえば、インターフェースを言語機能として提供するには、当然インターフェースを型とする変数が必要です。これらの変数は静的型付け言語に動的型付けの要素を追加しているそうです。以下の例では、r
にx
を代入するには明示的に型を指定して、r = x.(Reader)
のように代入する必要があり、これは動的に型がチェックされます。
このように、インターフェースの代入は実行時チェックするように実装する必要があり、その実装は注意深く設計する必要があるそうです。たとえば、代入が失敗した場合はどうするべきかを考える必要が出てきて、それは型アサーションと「カンマOK」イディオムの話につながるそうです。型アサーションと型スイッチは本来の計画では存在していなかったそうで、さらに複雑な実装になっているそうです。
同氏はインターフェースはGo言語において最も特徴的で強力な機能の1つだと述べていました。インターフェースの存在は、標準ライブラリ設計において非常に大きな効果を見せたそうです。インターフェースは真のコンポーネントアーキテクチャを可能にし、その最たる例がio.Reader
とio.Writer
であると述べていました。これらはUNIX系OSのパイプの考え方を一般化したものだそうです。
パッケージ
パッケージの設計には長い時間がかかったそうで、さらにその実装はガベージコレクションに次いで、見た目以上の複雑さを裏に持っているそうです。パッケージは複雑さを隠す単純さを持っており、コンポーネント化、スケーラビリティ、共有、データの隠蔽と隔離を可能にしています。これは、プログラムの設計、構文、命名、ビルド、リンク、テストなどに影響を与えているそうです。たとえば、パッケージのパス("math/big"
など)とパッケージ(big
など)を別にしたことにより、go get
機構を可能にしたそうです。
単純さの例と隠された複雑さ
最後に、Go言語の単純さの例と隠された複雑さについての説明がされました。説明は以下のコードを基に行われました。
同氏が挙げた上記のコードに隠された複雑さは以下のとおりです。
- UnicodeとUTF-8はシームレスに扱われる
- パッケージは簡単にインポートして使うことができる
Fprintf
を直接ネットワーク接続に使うことができる
HandleFunc
のように、関数をメソッドに昇格させることができる
- 真の並行サーバはブロックしない
同氏は、これらはすべて非常に単純に扱えるにもかかわらず、プロダクション環境にも耐えうると主張していました。
まとめ
同氏は単純さを提供するには、複雑な設計と実装をする必要があるが、それを行うだけの価値はあると主張していました。単純さを正しく作ることができたら、その単純さは使いやすさにつながると信じているそうです。Go言語の成功がそれを証明していると述べていました。
この基調講演では、Go言語の哲学とも言える単純さについて、その裏側にある複雑さも含めて話がされました。聴講した多くのGo言語ユーザは、Go言語の使い勝手の良さはこの単純さに依るものだと納得したはずです。そして、使い勝手の良い単純さを提供するために、綿密な設計と複雑な実装が裏側にあり、よく考えられて作られている言語だということを肌で感じたかと思います。