A Tour of Goを試す(続き)

4 min読了の目安(約3900字TECH技術記事

昨日のA Tour of Goを試すの続き。
"A Tour of Go"の「Methods and interfaces」と「concurrency」を読んだメモ。

メソッド

  • Goにはクラスがない!!
  • 型にメソッドを定義できる
  • funcキーワードとメソッド名の間にレシーバ引数としてレシーバを記述する
    引数なので通常の関数として記述できる
  • 任意の型(組み込み型)にもメソッドを宣言可能。
    • typeで定義する必要がある。
    • レシーバ型が同じパッケージにある必要がある。

ポインタレシーバ

  • ポインタレシーバを持つメソッドはレシーバが指す変数を変更する。
  • 変数レシーバは元の変数のコピーを操作する
  • メソッドがポインタレシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができます
    • ポインタレシーバは引数として変数が渡された時にポインタとして解釈する
  • メソッドが変数レシーバである場合、呼び出し時に、変数、または、ポインタのいずれかのレシーバとして取ることができます
    • 変数レシーバは引数としてポインタが渡された時に、変数にアクセスする

ポインタレシーバを使う理由

  1. レシーバが指す先の変数を変更するため
  2. メソッドの呼び出し毎に変数がコピーされるのを避けるため

インターフェース

interface(インタフェース)型は、メソッドのシグニチャの集まりで定義します。

暗黙的

  • 明示的に実装を宣言する必要はなく、型にメソッドを定義することでインターフェースを満たす

インターフェース値

  • インターフェースの値のメソッドを呼び出すと、その基底型の同じ名前のメソッドが実行される

具体的な値がnilの場合のインターフェース

  • インターフェースの具体的な値がnilの場合、例外を引き起こさずにnilをレシーバとして呼び出される

nilインターフェース

  • インターフェースがnilのものはメソッドを呼び出すとランタイムエラーになる

空インターフェース

interface{}:ゼロ個のメソッドを指定されたインターフェース型

  • 任意の型の値を保持できる
  • 未知の型の値を扱うコードで使用される

型アサーション

インターフェースの基になる値を利用する

t := i.(T)
インターフェースの値iが具体的な型Tを保持し、基になるTの値を変数tに代入する。
iTを保持していない場合、この文はpanicを引き起こします。

t, ok := i.(T)
インターフェースの値が特定の型を保持しているかテストする。
iTを保持していれば、tは基になる値、oktrueになります。
そうでなければ、okfalsetは型Tのゼロ値になりpanicは起きません。

型switch

型switchのcaseは型を指定し、それらの値は指定されたインターフェースの値が保持する値の型と比較されます。

switch v := i.(type) { // `type`キーワードを使用する
    case int:
        // process
    default:
        // caseに一致しなかった場合
        // 変数`v`は同じインターフェース型で値は`i`
}

Stringers

stringとして表現することができる型で、fmtパッケージに定義されている。

type Stringer interface {
    String() string
}

演習(Stringers)

type IPAddr [4]byte

func (ip IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

エラー

エラーの状態をerror値で表現

type error interface {
    Error() string
}

通常、関数がerror変数を返し、呼び出し元はエラーがnilか判定してハンドリングする。

演習(エラー)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return x, ErrNegativeSqrt(x)
    }
    ...
}

Readers

io パッケージは、データストリームを読むことを表現する io.Reader インタフェースを規定しています。
Goの標準ライブラリには、ファイル、ネットワーク接続、圧縮、暗号化などで、このインタフェースが実装されている

// データを与えられたバイトスライスへ入れ、入れたバイトのサイズとエラーの値を返します。
// ストリームの終端は、`io.EOF`のエラーで返します。
func (T) Read(b []byte) (n int, err error)

演習(Readers)

func (r MyReader) Read(b []byte) (int, error) {
    var e error
    n := 0

    for n < len(b) {
        b[n] = 'A'
        n++
    }
    return n, e
}

演習(rot13Reader)

image

// imageインターフェースの定義
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

演習(image)


ここから 「concurrency」 の章

goroutines

goroutine (ゴルーチン)は、Goのランタイムに管理される軽量なスレッドです。

go f(x, y, z)

で新しいgoroutineが実行される。

f, x, y, zの評価は実行元(current)のgoroutineで実行され、fの実行は新しいgoroutineで実行されます。

goroutineは、同じアドレス空間で実行されるため、共有メモリへのアクセスは必ず同期する必要があります。

チャネル

チャネル(Channel)型は、チャネルオペレータの <- を用いて値の送受信ができる通り道です。

ch <- v    // `v`をチャネル`ch`へ送信する
v := <-ch  // `ch`から受信した変数を`v`へ割り当てる

チャネルはmake関数で生成。

ch := make(chan int)

片方が準備できるまで送受信はブロックされ、goroutineの同期を可能にする。

バッファチャネル

バッファを持つチャネルを初期化

// 2つ目の引数はバッファの長さ
ch := make(chan int, 100)

チャネルのクローズ

  • 送信側はcloseを呼び、受信側はcloseされているか確認する
// 受信する値がない、かつチャネルが閉じているなら、`ok`は`false`
v, ok := <-ch

select

  • selectステートメントは、goroutineを複数の通信操作で待たせます。

  • selectは、複数あるcaseのいずれかが準備できるようになるまでブロックし、準備ができたcaseを実行します。

  • 複数のcaseの準備ができている場合、caseはランダムに選択されます。

  • どのcaseも準備ができていないのであれば、selectの中のdefaultが実行されます。

演習(二分木)

排他制御

  1. sync.Mutex
  2. Lock、Unlock

演習(Web Crawler)