ゼロから学ぶGo言語プログラミング(12) A Tour of Go 55~60

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 メソッドから。

55.Errors

なんとなく見てきたコードでも登場していましたが、Goではエラーの通知に複数の戻り値を活用しているようです。 こんな感じのコードでしょうか。

   f, err := foo()
    if err != nil {
        fmt.Println(err)
    }

これは errorインターフェースが文字列を返すよう定義されているから、と説明してますが、ここだけでは「へー、そうなのか」という感じ。 次で、自分でエラーを実装。

56.Exercise: Errors

エラーを扱う課題。 が、複素数は概念自体が身についていない。 仕方ないのでもっと単純な文字列の判定を課題代わりにしよう。

性別を表す文字列"M"または"F"のみを受け、maleやfemaleという文字列を返す関数を実装してみる。

package main

import (
    "fmt"
)

type ErrMonthNum uint

func (err ErrMonthNum) Error() string {
    return fmt.Sprintf("Need 1...12 uint. You gave %d.", uint(err))
}

func MonthName(m uint) (string, error) {
    if m < 1 || m > 12 {
        return "", ErrMonthNum(m)
    }
    ms := []string{
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    }
    return ms[m-1], nil
}

func main() {
    fmt.Println(MonthName(6))
    fmt.Println(MonthName(21))
}

これで一応期待通りにエラーが出力されるけど、これで合っているのかどうか…。 エラーを返す際に、自分でエラー用の型を作って、それにErrorメソッドを実装する、という辺りまでは理解できた。 まあ、色んなコード見て勉強しよう。

57.Web servers

やっと身近な話題が。 と思ったら、これはさすがにPlaygroundでは動かせない。 取り敢えずで作っておいたGoの環境を使って、ローカルで動かすことに。

ガイド通りコピーしてローカルで go run すると、localhost:4000 できちんと"Hello!"。 やはり恐ろしく手軽。

コードの方はというと、Hello型のstructを定義し、そこに ServeHTTPメソッドを実装してる。 ServerHTTPメソッドは、net/httpパッケージを使ったResponseWriterと、ポインタ渡しのRequestが引数。 処理はFprintのみ。

サーバーの実行はhttp.ListenAndServe()で、引数に"localhost:4000"を与えてポートを指定してる。 ポート込みのホスト名を指定って面白いな。 そしてHello型の変数も渡す。

多分http.ListenAndServe()は、ServHTTPメソッドを実装した構造体を受け取って、指定したポートで待ち受けるもの。 go runすると、ローカルの4000番で待ち続ける。

58.Exercise: HTTP Handlers

HTTPな課題。 任意の型にServeHTTPメソッドを実装し、任意のパスをそれらで受け取る、というもの。

型にServeHTTPメソッドを実装するのは、一つ前の解説通り。 問題はパスの扱いだが、課題の初期コードにそのまま「// your http.Handle calls here」とコメントがあり、解説中のハンドラ例をそのまま転記で問題なく動く。

http.ListenAndServe()の第2引数は、特定の型に結びつけたくない場合nilを渡す、っていうのはちょっとわかりにくいかな。

package main

import (
    "fmt"
    "net/http"
)

type String string

func (s String) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, s)
}

type Struct struct {
    Greeting string
    Punct    string
    Who      string
}

func (s Struct) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, s)
}

func main() {
    http.Handle("/string", String("I'm a String!"))
    http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
    http.ListenAndServe("localhost:4000", nil)
}

物凄く手軽。 でもこれ、Goの特徴っていうより、net/httpが手軽なんでないだろうか。 もちろんGo自体の言語設計がこういうパッケージを書きやすくはしてるんだろうけど、これだけの課題では、便利な組み込みパッケージがあるんだなー、程度。

59.Images

画像の取り扱い。

imageパッケージがGoでの画像の基本らしい。

そしてimageパッケージにはImageインターフェースが定義されていて、カラーモデルやカラー値の取得、矩形での画像サイズを満たす必要がある。 しかし通常は用意されているRGBAなどが使えるので、独自実装する必要はない。 また、image.NewRGBA(image.Rect(0, 0, 100, 100)) のような手軽な生成用関数も用意されている。

60.Exercise: Images

画像取り扱いの課題。

自分でImage型を実装し、必要なメソッドを用意する。 必要なメソッドは以下。

  • 画像のカラーモデル:color.Modelを返すColorModel()
  • 画像のサイズ:Rectangleを返すBounds()
  • 任意の座標のカラー:color.Colorを返すAt(x, y int)

image - The Go Programming Languagecolor - The Go Programming Languageを見つつ、課題のコメントや解説を参考に完成。

package main

import (
    "code.google.com/p/go-tour/pic"
    "image"
    "image/color"
)

type Image struct{
    W int
    H int
    color uint8
}

func (m Image) ColorModel() color.Model {
    model := color.RGBAModel
    return model
}

func (m Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, m.W, m.H)
}

func (m Image) At(x,y int) color.Color {
    return color.RGBA{uint8(x), uint8(y^x), uint8(x^y), uint8(255)}
}

func main() {
    m := Image{255,255,100}
    pic.ShowImage(m)
}

型を作り、インターフェースに基いてメソッドを実装し、それを投げればあとはよしなに…、という感覚が少し進んだ気がする。

でもこのコード、メソッドの実装を全部値渡しでしてるけど、負荷を考えるとポインタ渡しにすべきなんじゃないだろうか。 単純にポインタレシーバに書き換えただけでは動かないのは、何かを見落としているか、勘違いをしているんだろうなあ。

ゼロから学ぶGo言語プログラミング(11) A Tour of Go 49~54

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 メソッドから。

49.Methods and Interfaces

オブジェクト指向というのでなく、「メソッドとインターフェース」となっている。 クラスベースではないということ?

50.Methods

いきなり答えが。

Goには、クラス( class )のしくみはありませんが、struct型にメソッド( method )を定義できます。

どうやらstructが重要らしい。

structはfieldをもったデータ構造、という漠然としたイメージ。 このシンプルな構造体に何らかの形で紐付けられた関数が、メソッドということだろうか。

メソッドレシーバ ( method receiver )は、 func キーワードとメソッド名の間で、それ自身の引数リストで表現します。 (訳注:この例では、 (v *Vertex) のことを言っている)

んー、このコードらしい。

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func の後、関数名との間に (v *Vertex) という見慣れない書き方。 これはその関数が、何らかのstructに属することを表現するんだろうか。 関数名の前に来る辺り、それを意図している気がする。 この部分のことを「メソッドレシーバ」と呼ぶらしい。

メソッドレシーバに書いてあるのは、vという値名と、*VertexというVertexへのポインタ。 これはどう理解すべきなのか。

ポインタの概念しか知らず、きっちり実用した経験がないため、メモリの番地としか認識していなかった。 でも調べてみると、どうもメモリの操作を直接するためというより、値の渡し方の制御のために、Goではポインタが採用されているらしい。

Goでは関数への値は常に参照ではなく値渡しになる。 とすると、もしメソッドレシーバにポインタを使わなければ、メソッドを使うたびにそのstruct自体がコピーされて、メソッドに渡るということだろうか。

Goでポインタが使われるのは、原則値渡しのGoで参照渡しを実現するため、というのがひとつの大きな理由にありそうだ。

51.Methods continued

なんと、structでなくてもメソッドを持てるらしい。

試してみる。

package main

import (
    "fmt"
)

type Foo string

func (f Foo) bar () {
    fmt.Println(f)
}

func main() {
    ff := Foo("asdfghjkl")
    ff.bar()
}

実行すると"asdfghjkl"が出力された。 けど、 type Foo string ではなく単なる int にメソッドを割り当てようとすると、エラーが。

cannot define new methods on non-local type int

これが以下の記述で述べていることのようだ。

しかし、他のパッケージからの型や基本型にはメソッドを定義できません。

基本型だけでなく、他のパッケージで定義されている型も、例えエクスポートされていようがメソッドは定義できない。 確かに基本型や他のパッケージの型には触れない方が不要な上書きのトラブルを防げる。

さらに、このコードをポインタで渡すように変えてみると、今度は出力がおかしい。 メモリの番地を吐いているっぽい。

ところがこれを以下のように、structを用いると、正しく出力される。

package main

import (
    "fmt"
)

type Foo struct {
    a float64
}

func (f *Foo) bar () {
    fmt.Println(f.a)
}

func main() {
    ff := Foo{10}
    ff.bar()
}

intやstringで型を宣言して、それをポインタ渡しでメソッドに関連付け、メソッドから型を参照すると、それは当然ポインタ。 出力すればポインタの番地が返ってくる。 ということは、structが特殊なのか。 色々読んでみる。

そうか、ポインタは var という風に、付きでアクセスすると、そのポインタが示す変数への参照となる。 メソッドレシーバで渡されたintのポインタが示す値を使うには、*が必要だった。 そしてstructの場合、. がそれに似た役割を果たしている、と。

わかったようなわかっていないような。

52.Methods with pointer receivers

メソッドは、名前付きの型( named type )や、名前付きの型へのポインタに関連付けられます。

名前付きの型とは、typeキーワードで宣言するもののことだろう。 メソッドはそうやって宣言した型そのものか、そのポインタに関連付けることが出来る。

解説にしたがってポインタへの参照に書き換えると、確かにScaleメソッドでの変更が反映されなくなった。

メソッドを型に関連付ける際、実体で渡せば値渡しとなり、メモリの消費が増え、変更の直接反映ができない。 ポインタで渡せば参照渡しとなり、メモリ消費を抑え、変更も直接反映される。

なんか色々わかった気がする。

53.Interfaces

インターフェースも、これまでの経験でしっかり扱ってこなかったもの。 満たすべき実装を示しているとか、ポリモーフィズムに使えるとか、概要程度は理解していても活用はしていない。

インターフェース型( interface type )は、メソッド群で定義されます。

というのが以下のことだろう。

type Abser interface {
    Abs() float64
}

structを使って型を宣言する場合と同じようだけど、structの代わりにinterface。 そして中身は関数名と戻り値の型?

コード全体は interface に慣れていないため追いかけにくい。 a = v で、aがAbserインターフェースとなり、そこにMyFloatやVertexを代入しようとして、Vertexの方ではエラー。 ここまでは解説やコメントにある通りなのでわかる。 しかしなぜ、Vertexの方のメソッドレシーバが (v *Vertex) でポインタ渡しだと、エラーになるのか。

ポインタレシーバだからエラーだということは、インターフェース時点で、これが値渡しで実装されるよう定義されているはずなのに、それがどの箇所かわからない。

interfaceはGoで重要なはずだけれど、ここだけ見ていても進みそうにないので、次へ。

54.Interfaces are satisfied implicitly

言ってることはなんとなくわかる。

インターフェースという多態性を担保する仕組みがあり、Goではメソッドの実装そのものが即ちインターフェースの実装であって、別途宣言は要らない。

しかし、なんとなく止まり。

インターフェースは非常に重要そうなわりに、このツアーではこれで終わり。 どこかでみっちり理解できるよう取り組まないと。

ゼロから学ぶGo言語プログラミング(10) A Tour of Go 40~48

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 mapの途中から。

40.Mutating Maps

m := make(map[string]int)

という風に、mをstringがキーでintが値のmapとして初期化したとして、

m["foo"] = 100 //挿入・更新
v := m["foo"] //取得
delete(m, "foo") //削除

という操作が出来る。

キーの確認は

v, ok := m["foo"] //取得

という風に、取得時の書き方に2番めの値を付けて真偽を受け取る。

辞書的なデータ構造の扱いは、非常に多用しそうなので、自然と書けるようになっておきたいです。

41.Exercise: Maps

mapを使った課題。

ヒント通り、strings.Fieldsのリファレンスを見ると、与えられた文字列に含まれる単語を、空白文字で分割したsliceを返す関数だった。

切り出したsliceを受けて、後はforでmapに単語単位でカウントしていけば、テストは全てパスできた。

package main

import (
    "code.google.com/p/go-tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    fs := strings.Fields(s)
    ret := make(map[string]int)
    for i := 0; i < len(fs); i++ {
        ret[fs[i]] = ret[fs[i]] + 1
    }
    return ret
}

func main() {
    wc.Test(WordCount)
}

日本語利用者としては、strings.Fields()の挙動が気になったので、日本語を与えた場合も試してみた。

package main

import (
    "code.google.com/p/go-tour/wc"
    "strings"
    "fmt"
)

func WordCount(s string) map[string]int {
    st := "私はGo言語を学習している者です。"
    fs := strings.Fields(st)
    ret := make(map[string]int)
    for i := 0; i < len(fs); i++ {
        ret[fs[i]] = ret[fs[i]] + 1
    }
    fmt.Printf("%s", ret)
    return ret
}

func main() {
    wc.Test(WordCount)
}

結果は、単語単位でsliceされず、人連なりの文字列が単独の要素になったsliceを得られただけだった。 UCAや漢字コードなどを元に、単語の自動分かちを期待したが、stringsという文字列処理の基本的パッケージらしいものがカバーすべきとは思わないので、当然かも知れない。

ちなみに、strings.Fields()は、全角の空白文字であっても分割対象にしている。 基本がUnicodeベースになっている恩恵で、楽で嬉しい。

42.Function values

関数の値渡しをサポートしている、という話。 これは少し前、関数を引数に取る画像生成の課題があったため、予測していた。

43.Function closures

クロージャは、JavasScriptでしっかり踏み込まずにやり過ごしてきた概念。 せっかくなのでGoできちんと学ぼう。

サンプルコードと、ツアーからもリンクされていたWikipediaの解説を見ながら進めます。

まずサンプルコード。

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

adder()がクロージャを実現している関数。 戻り値としてintを引数に取る関数を使うことが宣言されている。

adder()の中では変数sumが0で初期化されている。 さらにadder()は、returnで無名関数を返している。 adder()から返す無名関数は、adder()が持つ変数sumに引数xを加算し、変数sumを返すもの。

main()の中で変数posとnegは、"adder()"で初期化されている。 つまりadder()の戻り値である無名関数で初期化されていることになる。 この2つの変数それぞれをforループの中でintを渡しながら呼び出すと、次の結果が出力される。

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

関数adderがもつsumの値は、0で初期化されるだけで、後は無名関数で加算されるだけ。 なのでposの出力は 0,1,2,3,4... となっていく筈。 しかし実際にはsumの値を保持しており、0,1,3,6,10... となっている。 また、negの値はposの場合と異なる結果を返しており、posとnegそれぞれが独立したsumを保持していることになる。

これがGoのクロージャのようである。

漠然とした印象では、オブジェクト指向におけるインスタンスの挙動のように見える。 これが関数のスコープで実現できるメリットは、まだいまいち気付けていない。 クロージャオブジェクト指向の関係を探すと、MDNの以下の記述があった。

感じた類似性そのものは誤りでないらしい。 そしてここにあるように

メソッドを 1 つだけ持つオブジェクトを使いたくなるような状況ならば、どんな時でもクロージャを使う事ができます。 という点も、returnを使ってクロージャが実装されている点から、理解できた気がする。

44.Exercise: Fibonacci closure

早速クロージャを使った課題。 フィボナッチ数をクロージャで返そう、というもの。

初期のコードはこれ。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

fibonacci()は関数を返すようになっている。 ここで返す関数が、クロージャということだろう。 Wikipediaを見ると、こういったクロージャの外側のスコープをエンクロージャと呼ぶらしい。

エンクロージャクロージャを実現するためのスコープをまず用意し、クロージャを無名関数として実装してエンクロージャから返すことで、あたかもオブジェクト指向インスタンスのように、返された無名関数ごとに状況を引き継ぐ、独立した環境が手に入る、という理解を現時点ではしておく。

最終的に課題はこう書いた。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    a := 0
    b := 1
    return func() int {
        fib := a + b
        a = b
        b = fib
        return fib
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

期待通りの結果にはなるが、これがクロージャの用途として適しているのか…。 扱っている関数がクロージャかどうか、注意が必要そうだという事は理解できたし、シンプルにインスタンスっぽいものを持ちたい場合、確かに楽な状況もありそう。

クロージャが活躍する状況は大いにあるのだろうから、もっと積極的に使って慣れておきたい。

45.Switch

ifやforからしばらく経ったここにきて、制御構造switch。

pythonにはswitchが無く、欲しいなあと思っていたけど、breakを忘れてバグに繋がりやすいというのが理由らしい。 goではどうなんだろう。

switch部分だけ抜き出すと、こうなっている。

    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }

重要なのは以下の解説。

Goでのswitchは、 case の最後で自動的にbreakします。 もし、brakeせずに通したい場合は、 fallthrough 文をcaseの最後に記述します。

つまりバグの温床になりやすいbreakを自動化し、むしろ意図的な特殊な制御の方をfallthroughという文で実現したらしい。 非常に明確で素晴らしい仕様だと思う。

46.Switch evaluation order

caseは上から順に評価、一致で停止、全てのcaseで一致しなければdefault。 うん明確。

Goのswitchは、評価に式が書けるらしい。 あっちにfallthroughは無いけれど、FileMakerのCase()に似ている。

47.Switch with no condition

switchの条件文を省略することで、caseが比較すべき対象が無くなり、caseの式の真偽で評価される。 確かに条件の多いifを連ねるなら、こちらの方が見通しが良さそう。

48.Advanced Exercise: Complex cube roots

complexに関するAdvancedな課題。

complex型は、複素数だったらしい。 しかし複素数虚数の素養が、せいぜい概念程度の理解であり、この課題そのものを大きく誤解してしまいそう。 ということで、ひとまずここはスキップする。


次はメソッドから。

ゼロから学ぶGo言語プログラミング(9) A Tour of Go 34~39

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 rangeとmap。

34.Range

これもpythonっぽいrange機能。 mapはまだツアーに登場してないけど、辞書みたいなものかな。

for i, v := range foo {
}

これでfooが持つインデックスがiに、値がvに渡される。

ただ、"range フォーム"という表現が引っかかる。 翻訳の揺れなどではなく、元の英文でも"range form"となってる。

35.Range continued

インデックスや値を " _ "(アンダーバー) へ代入することで破棄することができます。

破棄することで、受け取って使わないよりも不要なリソースを確保せず済むんでしょうか。

for _, _ := range foo {
}

こうすると、fooの長さ分処理ができると思ったら、":="での型推論がひっかかった。 コロンを外せば動いたので、アンダースコアも減らす。

for _ = range foo {
}

さてこれが、len()でカウント取ってのループとどっちが効率良いのか。 range使った方が好みだけど、"_"を省略できないのはちょっと残念。

36.Exercise: Slices

バイト列を使った画像の生成の課題。 色々わかりにくい。

まず、"code.google.com/p/go-tour/pic"からのimport。 恐らく受け取ったバイト列を画像として出力するためのパッケージ。 pic.show()がそれに当たるらしい。

pic.show()はPic関数を引数として受け取るようだけど、課題の開始時点でPicには引数がない。 と思ったら、pic.show()にはPicを関数として渡していた。 つまり、pic.show()側で渡されたPicに、生成するサイズとなるdxやdyが渡されるということらしい。

次に分からないのが、"uint8"というPicの戻り値の指定。 まだまだ目がなれないので一見でわからないけれど、sliceはの後に、そのsliceが保持する中身の指定を行うようなので、Picの戻り値はuint8が連なったslice、ということになるようだ。 と思ったのだけど、それだとsliceを用意するmakeの書き方がわからない。 結局試行錯誤して、コードは次のようになった。

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {
    ret := make([][]uint8, dx, dy)
    for i := 0; i < dy; i++ {
        row := make([]uint8, dx)
        for k := 0; k < dx; k++ {
            row[k] = uint8(i ^ k)
        }
        ret[i] = row
    }
    return ret
}

func main() {
    pic.Show(Pic)
}

"uint8"のように、""を連ねると、多次元のsliceを示すようで、make()での長さ指定は引数を増やして対応するらしい。 多次元配列を"[]uint8"という風に初期化しないといけないのは、動的に次元の増減は難しいということだろうか。

ついでに、呼び出しているpic.Show()のコードも見てみました。

やはり、int2つを引数に取る関数がShow()の引数でしたが、引数として関数を与える場合、

func Show(f func(int, int) [][]uint8) {
}

というように、引数の場所に関数名、引数となる関数の引数、そして引数となる関数の戻り値まで記述するようです。 こういう風に関数を引数に取れるのは、Goがモダンなところなんでしょうか。

Sho()の結果は、imageパッケージを使ってRGBA画像としておき、それを更にShowImage()でbase64として返しています。 最終的な出力時に"IMAGE:"という文字列を先頭に付加していますが、これはplaygroundが画像としてbase64のデータを出力するためのフラグのようです。

sliceによる多次元配列の扱いは、まだ全然慣れませんが、使いこなせると強力そうです。

37.Maps

mapはやはり辞書のようです。

mapの初期化はsliceと同じmake()で、キーへのアクセスは"m["キー"]"です。 mapもsliceと同じく、保持する値の型を、make時に指定する必要があるようです。

38.Map literals

リテラルとしてのmapは"キー: 値"というペアでダイレクトに指定でき、make()で準備する必要がないようです。 stringのmapであれば、こんな風に定義できました。

var m2 = map[string]string{
    "a": "b",
    "c": "d",
    "e": "f",
}

39.Map literals continued

これ、38で試したコードではすでにstringを個々のmap要素に書いてませんが、Vertexの場合と同じように考えていいんでしょうか。

"トップレベル"とは、Vertexがmainパッケージの直下に指定されている事と同義と捉えていいのかどうか…。

ゼロから学ぶGo言語プログラミング(8) A Tour of Go 25~33

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 データ型や構造あたりから。

25.Structs

(また、 type 宣言は皆さんが知っているものです。)

struct というのが、構造を持ったデータのようです。 内容は名前と型を並べたシンプルなものですが、もっと複雑な入れ子なども可能なのでしょうか。 これを定義し、型として扱うための宣言が"type"のように見えます。

type宣言の出てこないスクリプト言語ばかりやってきたため、残念ながら知っていません。 Goで慣れましょう。

26.Struct Fields

type 宣言した structは、以下のように変数で使うようです。

v := Vertex{1, 2}
v.X = 4

{}による内容の指定は、名前付きでは出来ないんでしょうか。

ドットシンタックスでのアクセスは、パッケージへのそれと同じですが、実際には何がどう違ってくるのか。

27.Pointers

ポインタ。 Cですこーし触れて以来、概念は理解していても、まともに使った経験がありません。

さらにこの事例では、ポインタを利用するメリットがピンとこない。

func main() {
    p := Vertex{1, 2}
    q := &p
    q.X = 1e9
    fmt.Println(p)
}

このポインタを通じた間接的なアクセスで、とてもわかりやすくなります。

とあるんですが、なぜp.Xへのアクセスでは分かりにくいのか。 structが持つ、まだ言及の無い性質に関係あるんでしょうか。

28.Struct Literals

structを使って宣言した型Vertexが、変数へ割り当てる際に、リテラルを渡して初期化できることは理解出来ました。 またリテラルを渡す際、"名前: 値" という構文を用いれば、特定のフィールドだけ指定できることや、名前付きの割り当てと名前なしの割り当てが混在できないことも、確認しました。

問題は前項に引き続きポインタで、

q = &Vertex{1, 2} // has type *Vertex

とすることで、初期化したVertexのポインタが直接手に入るのは良いんですが、このメリットが見えません。

29.The new function

golangにはnew()があり、これを使うと"ゼロ初期化"したタイプが手に入る。 タイプと型を分けて考えないと、おかしいのかな。

なんにせよ"ゼロ初期化"は、structが持つfieldを、数値であればゼロ、文字列であれば空っぽといったデータで初期化すること。 newはそういった初期化を行った構造体の、ポインタを返すという感じでしょうか。

またポインタ絡みなので、飲み込み切れていません。

30.Slices

golangでの配列的な構造は、sliceということのようです。

sliceは

p := []string{}

という風に取得でき、{}にはカンマ区切りで値を入れられる。 [] がsliceであることを表し、後ろに型を続け、更にその内容を {} で示す。

ということは、型の混在したsliceはだめということでしょうか。

31.Slicing slices

p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p[1:4] ==", p[1:4])

こういった書き方で、"p[1:4] == [3 5 7]"が出力されるのは理解できます。 しかし解説がしっくりこない。

sliceは、同じ配列を参照する新しいsliceを作成することで、再sliceすることができます。

これはつまり、p[x:y]という書き方で、新たなsliceが作成されていることを示しているんでしょうか。

32.Making slices

make が現れました。

sliceは、make 関数で生成します。 これは、ゼロに初期化した配列をメモリに割り当て、その配列を参照したsliceを返す働きをします。

これを読む限り、配列とsliceは別物のようです。

33.Nil slices

slice はgolangでかなり重要そうですが、このツアーだけではいまいち飲み込めませんでした。

別途解説などを見て、じっくり理解しようと思います。


次は 「 34.Range 」から。