「コンポジット型」
書籍:プログラミング言語Go
第4章 「コンポジット型」 の要点と思われる箇所を自分のメモ用にまとめました。
4.1 配列
- 配列は特定の型の0個以上の固定長列。Goで配列が使われることはまれ。
- [...] が長さの場所に書かれた場合、初期化子の配列リテラルの要素数になる。
- 次のようにインデックスと値の組のリストを指定することも可能。
.go
type Currency int
const (
USD Currency = iota
EUR
GBP
RMB
)
symbobl := [...]string{USD: "$", EUR: "C", GBP: "L", RMB: "\"}
- 上記記法でインデックスを省略してもよく、省略時はゼロ値が要素に入る。
- Goで配列を関数に渡すとコピーが渡される。関数内での配列操作が呼び出し元の配列に影響しない。大きい配列だと非効率になるので注意。
- 配列を関数に渡す場合、ポインタ渡しもできる。その場合、関数内での配列操作が呼び出し元の配列に影響する。
.go
func foo(ptr *[32]byte) {
4.2 スライス
- 大きさがない配列のようなもの。可変長列。
- cap(s)を超えてスライスを作成するとパニックになるが、len(s)を超えてのスライスの作成はそのスライスを拡張する。
- スライスは配列の要素へのポインタを含んでいるので関数へスライスを渡すことでその関数は基底配列の要素を変更することができる。
.go
func reverse(s []int) {
...
}
.go
reverse(a[2:]) // スライスaの要素に対する操作がreverseで行われた場合、呼び出し元のaにも影響する。
- 配列と異なりスライスは比較可能ではない。ただしバイトスライス([]byte)は最適化されたbytes.Equal関数がある。
- 唯一正当なスライスの比較はnilとの比較のみ。空のスライスかどうか見分ける場合はlen(s) == 0を使う。
4.2.1 append関数
- append関数はスライスに要素を追加する。
.go
runes = append(runes, r)
- 配列の拡張ごとに配列の大きさを倍にするのは実行効率の点でよい。appendは以下のappendIntより洗練された拡張方法を使っているかもしれません。
.go
func appendInt(x []int, y int) []int (
var z []int
zlen:= len(x) + 1
if zlen <= cap(x)
z = [:zlen] // capに追加の余地がある。
} else {
zcap := zlen
if zcap< 2*len(x) {
zcap= 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x) // 組み込み関数。
}
z[len(z)] = y
return z
}
- スライスを使うとstackが簡単に実装できる。要素削除のremove実装例は以下。
.go
func remove(slice []int, i int) slice[] {
copy(slice[i:], slice[i+ 1:])
return slice[:len(slice) - 1]
}
4.3 マップ
- マップのキーは == で比較可能である必要がある。浮動小数点は == (精度の問題などあり)比較するのに適さないのでキーにするのは悪い考え。NaNがある値は更に悪い。
- マップの要素のアクセスはインデックス表記で、削除はdelete()を使う。要素アクセスやdeleteは要素がない場合も安全に動作する。
.go
ages := map[string]int{
"alice": 32
"charlie": 34
}
fmt.Println(ages["alice"]) // 32
delete(ages, "alice") // "alice" を削除。
- マップの要素はアドレスを得られない。マップが大きくなる際にハッシングされ新たなメモリに移動しアドレスが無効になりうるため。
- rangeによるマップの繰り返し順序はランダム。
- マップ型のゼロ値はnil。nilのマップにlenを使うと 0 が返る。
- nilのマップのdelete, len, range は行えるが、nilマップへの保存操作はパニックになる。makeしておけばパニックを回避できる。
.go
var ages map[string]int // nil
if len(ages) == 0 { // true
...
ages["carol"] = 21 // パニック: nil マップのエントリへの代入
- マップ要素の取得は常に値を生成する。マップ内にキーがあればその値を、なければゼロ値を得る。要素のあるなしを知りたい場合は2つ目のboolの戻り値を見る。
.go
if val, ok := ages["alice"]; ! ok { // "alice" がなければ ok は false。
- スライス同様マップ同士を比較できない。nilとの比較のみ可能。
- Goはsetを提供しない。重複を許さないコンテナを用いたい場合はマップを利用する。
4.4 構造体
- 構造体は合成データ型。0個以上の任意の型の名前付き値を持つ。
.go
type Employee struct {
ID int
...
Position string
}
- ドット表記はポインタでも使える。
.go
var dilbert Employee
var employeeTheMonth *Employee = &dilbert
employeeTheMonth.Position += " (proactie team player)"
? p.113のEmployeeByIDの出力が実装がないのにPointy-haired bossとなっているのはなぜ?
- 同じ型の連続するフィールドはまとめることができる。ただし、普通は関連するフィールドの宣言のみまとめる。
.go
type Employee struct {
ID int
Name, Address strint
...
- 構造体も大文字で始まると公開される。フィールドを公開するものと公開しないものを混在させることができる。
- 構造体は自身の型をフィールドに持てないが、自身の型のポインタならフィールドに持てる。
- 構造体に対するゼロ値は各フィールドのゼロ値で構成される。
- struct{}と書くとフィールドを持たない空構造体。
- 空構造体をセットを表すマップの値の型に使い、キーだけに意味があることを強調する人もいる。が、記述は面倒になる。
4.4.1 構造体リテラル
- 構造体リテラルで全フィールドを指定する場合フィールド順が正確である必要があり、後のフィールド追加やフィールド順変更によりコードが脆弱になる。
- 上記理由で全フィールドの値のみを指定する構造体リテラルはパッケージ内だけか、フィールド順が明らかな小さい構造体のみで利用する。
- フィールド名を指定する構造体リテラルではフィールドの省略が可能。省略されたフィールドはゼロ値となる。フィールド順は問題とならない。
- 大きな構造体を関数に渡す、関数から返す場合、ポインタを利用したほうが効率的。
- ポインタでない構造体を関数に渡すとコピーになる。(関数内の構造体の操作が呼び出し元の構造体に反映されない。)
4.4.2 構造体の比較
- **構造体のフィールド全てが比較可能であるなら構造体も比較可能。**比較可能な構造体はmapのキーになりうる。
4.4.3 構造体埋め込みと無名フィールド
- Goでは型を持つが名前を持たない無名フィールドを宣言できる。無名フィールドの型は名前付き型か名前付き型へのポインタ。
- 無名フィールドは誤った呼び名。
4.5 JSON
- JSONは同じ構造化された情報を送受信するための標準表記のXML, ASN.1, Google Protocol Buffersに比べ簡潔性、可読性、サポートの多さで優れる。
- 上述のフォーマットの変換機能をencoding/json, encodint/xml, encoding/asn1パッケージで行っている。
- JSONは数値、ブーリアン、文字列を基本とし、文字列はUnicodeコードポイント列。ただし、JSONの\Uhhhhの数値エスケープはルーンではなくUTF-16。
- 基本型はJSON配列とオブジェクトで再帰的に組み合わせ可能。
- Goのデータ構造をJSONに変換することをマーシャリングと呼び、json.Marchalにより行われる。
- json.MarshalIndentを使うとインデントされたJSONが生成され、人が読みやすい形になる。
- 構造体のフィールドは公開されているフィールドだけをマーシャリングされる。
- 構造体のフィールドの型の後に、フィールドタグを指定するとマーシャリングでその指定に従うencodingがされる。代替のJSON名利用など。
- フィールドタグは key:"value"の空白区切りのリストで解釈され、" を含むのでたいてい生文字列リテラルで書かれる。
- jsonフィールドタグの最初の値はJSON名として使われる。omitempty指定すると、そのフィールドがゼロ値か空の場合JSONの出力を生成しない。
.go
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false, Actors] []string{"Humphrey", ...}},
{Title: "Cool Hand Luke", Year: 1967, Color: true, Actors] []string{"Paul Newman"}},
...
}
data, err := json.Marshal(movies) // JSONへの変換。
dataの例:
[{"Title":"Casablanca","released":1942,"Actors":..}]
- JSONのGoデータ構造への変換をアンマーシャリングと呼びjson.Unmarshalで行う。JSONのデコードする部分、破棄する部分を指定可能。
.go
var titles []struct{ Title string }
if err := json.Unmarshal(data); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} ...]"
- json.Decoderはストリーミングデコーダで同じストリームから複数JSONエンティティを順にデコードできる。対はjson.Encoder。
- アンマーシャリングの過程でJSONの名前をGoの構造体の名前と関連付ける一致処理は、大文字小文字を区別しない。
- アンダースコアがJSONの名前にありGoの名前にない時だけフィールドタグを使う必要がある。
■4.6 テキストテンプレートとHTMLテンプレート
- Printfより複雑なフォーマットを扱いたい場合には text/template と html/template パッケージのテンプレートを使う。
- テンプレートには {{...}} で囲まれたアクションを含む。テンプレートの文字列のほとんどはそのまま表示されるが、アクションは他の振舞を引き起こす。
.go
const templ = `{{.TotalCount}} issues:
{{range .Items}}--------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{end}}`
- アクション内の . は最初にテンプレートのパラメータを参照する。(上記は IssueSearchResult を対象としており、{{.TotalCount}} は TotalCount フィールドが展開される。)
- {{range .Items}} と {{end}} はループを生成し、その間のテキストは複数回展開される。
- Ageの操作はCreatedAtのtime.Time の値を | で daysAgo関数に渡し経過時間に変換する操作を表す。daysAgo関数は以下。
.go
func daysAgo(t time.Time) int {
return int(time.Since(t).Hours() / 24)
}
- テンプレートはパースして適切な内部表現にする必要がある。また、Funcsでテンプレートにアクセス可能な関数の集まりを追加する。
.go
report, err := template.New("repost").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ)
if err != nil {
log.Fatal(err)
}
- template.Must を使うとテンプレートのパースのエラーを検査 (エラー時にパニックになる) し、テンプレートを返す。
.go
var report = template.Must(template.New("issuelist").
Funcs(template.FuncMap({"daysAgo": daysAgo}).
Parse(templ))
func main() {
result, _ := github.SearchIssues(os.Args[1:]) // err 処理省略。
if err := report.Execute(os.Stdout, result); err != nil {
...
- http/template は text/template と同じ表現を使い、HTML, JavaScript, CSS, URL内に出現する文字列を自動で文脈上適切にエスケープ処理する機能が追加されたもの。
- http/template は HTML のインジェクション攻撃を避けるのに役立つ。"<" が '<' として表示されたり、"\" がリンク要素になるHTMLドキュメントの構造を変えるセキュリティ問題を防げる。
練習問題解答例