A Tour of Go を読む
はじめに
概要
今まで体系的に学んだことのある言語
JavaScript
TypeScript
Java
Scala
Rust
学習にあたって追加で読んだ資料
雑感
Golangの特徴
GCがある
interfaceによる構造的部分型のサポート
静的型付け
スタックやヒープを意識しなくて良い
(メモリ安全な) ポインタがある
null安全でない
ジェネリクスがない
式指向でない
Errors are values
ランタイムレベルの軽量スレッド
channelによるスレッド間通信
Golangの思想
スクリプト言語のように楽に書けるが, パフォーマンスを重視したプリミティブな書き方もできる
- 非知的なプログラマのためにデザインされている。
- 言語デザインとして正しく使うのが難しい、乱用するとプログラムを無意味に複雑にしてしまう機能が排除されている。
- 高度な型システム・継承・Generics・例外・イベントモデルによる並行処理など。
A Tour of Go 学習メモ
basics
型をvariable typeと書くの, 違和感あるけどコロンを省略しても曖昧にはならないので問題なさそう?
Named return values, 面白い. 戻り値に名前を付けたくてJSで関数の戻り値としてオブジェクトを返すのと似てる
naked returnは長い関数で使うと可読性に影響があると書かれており, 結構使い所が限られそう?
Short variable declarations
varとは別に用意されていて, 変数の宣言と初期化を同時にすることを強制されるが, varより短く書ける
関数の外では利用できないのは何故?
Rustなどでコンパイル時定数の型が省略できないのは何故ですかという問と似てそう
後置型注釈
Rust, Scalaと同じく後置型注釈
構文解析上の理由から前置ではなく後置になっている
ただし, let variable: Type = ...のように型と変数名と間にコロンを挟むRust, Scalaと違い, Golangはコロンを付けない
シンタックスからミニマムさ/可読性を強く意識している様子が伝わってくる
moretypes
ポインタ
そういえばGCがあって, かつポインタが実装されている言語触ったこと無い
リソースはGCによって自動解放され, 強制的に解放することは(多分)できないので, Golangのポインタは安全?
ポインタ演算は存在しないと書かれているので, unsafeな領域へのポインタを作成することは出来なさそう
構造体の型宣言はtypeを用いて行う
暗黙の型変換は存在しない
可読性への意識の高さを感じる
(*p).Xをp.Xと書けるのは, p型を*p型へと暗黙に型変換しているためではなく, そういうシュガーシンタックスがあるため?
仮にそうだとしても暗黙の型変換と同じように見える. どういう点が違うのだろうか
Rustと同じようにポインタかどうがを意識せずにメソッド呼び出しやメンバアクセスができる
配列は要素型と要素数を要求し, スライスは要素型のみを要求する
Rustと同じ. 最適化という点でコンパイル時にサイズが決まっていたほうが嬉しいし, 決まってないものもあると嬉しい.
[N]Tで配列, []Tでスライス
Rustは[T; N]で配列, [T]でスライス
Golangのほうが可読性が高い気がする
ちなみにRustではlet a = [0; 20]; // a: [i32; 20]のような記法ができる
lengthとcapacityを持つ
スライス
対象のスライス/配列のcapacityによって作成できるスライスが制限される
制限を超えた場合はランタイムエラー
append関数は破壊的
型変数
make関数のシグニチャはfunc make(t Type, size ...IntegerType) Type
変数に型を代入できる?
型は値としても使えるということ?
型情報, 一般に実行時には消えるのでは
ジェネリクスのある言語でいうところの型変数, 型パラメータっぽい
ジェネリクスのある言語では型変数は特殊化されるが, Golangではジェネリクスが無いので特殊化されない?
Mapはmutable
methods
メソッドは関数を直接型に紐付けることで実装する
関数がどのレシーバに結び付けられているのかを知っている
RustやScalaと同じ. JavaScriptとは違う.
type宣言によって生成された新しい型はRustやTypeScriptと違って, 元の型のaliasではない?
元の型と同じ構造を持つ別の型が作成される?
他のパッケージで定義されている型 (built-inの型を含む) にメソッドを追加する時に利用する
open classのようなことができる
ところで f < 0は暗黙の型変換なのでは…
変数レシーバとポインタレシーバ
変数レシーバ
レシーバをコピーして関数に渡す
コピーのコストが高いレシーバを渡す時は注意する必要がある
関数の引数としての振舞いと同じ
コピーが渡されるので, 関数からレシーバ自身を更新できない
ポインタレシーバ
レシーバのポインタを関数に渡す
ポインタが渡されるので, 関数からレシーバ自身を更新できる
こちらのほうが一般的
Methods and pointer indirection
variableがT型の時, variable.field/variable.method()は(*variable).field/(*variable).method()のシュガーシンタックスとして扱われる?
この操作に何か名前付いてないのかな?
func deref(val *T) Tに対してderef(variable)としてもderef(*variable)には変換されず, コンパイルエラーとなる
実引数にはシュガーシンタックスが用意されてないため?
interface
構造的部分型を宣言できる
implementsキーワードが不要なことから明らか
TypeScriptと同じ
どの具象メソッドが呼び出されるのかは動的ディスパッチにより, 実行時に解決される?
func (t T) method()とfunc (t *T) method()を同時に宣言するとコンパイルエラーになる
もし許可してしまうとvariableがT型の時, variable.method()と(*variable).method()のどちらに解決すれば良いか決定できないため
フィールドについてもメソッドと同様
Golangの型システム
構造的部分型
GCのおかげである型の変数に値を代入する時, 値がスタックのヒープどちらに置かれているか, メモリ上のサイズかどうなっているかを気にする必要がない
Rustで言うところのT/Box<T>, Sized/?Sizedを意識しなくて良い
type NewType BaseTypeでBaseTypeと構造が同じ新しい具象型NewTypeを宣言できる
type NewInterface interface {...}で抽象型NewInterfaceを宣言できる
抽象型のメソッド呼び出しは動的ディスパッチにより解決される
インターフェイスの値は(value, type)のような値と型から構成されるタプルと見なせる
interface{}
TypeScriptで言うところのany型
concurrency
goroutine
ランタイムレベルのスレッド
軽量
一方で, Rustの標準ライブラリのスレッドはOSレベル
データのやり取りの選択肢
mutex
channel
Rustと同じ
channel
送受信可能なチャネル
一方でRustは送信専用・受信専用チャネルに分かれている
selectで複雑な双方向通信を簡潔に実現できる?
linux のシステムコールのselectとやってることが似てる
もしかしてそこから命名されてる?
mutex
Rustと違い, 明示的にUnlockを呼ばないと解放漏れになるので注意する必要がある
defer m.Unlock()が便利