Closed36

GoTour のお勉強をする

ganyariyaganyariya

エントリポイントの package は main を書く。

返り値の箇所は (int, int) のように括弧を付けるほか、複数を指定できる(1つのみなら括弧いらない)。

また、返り値に命名を先に付けておくことができる。
こうすると、先にその名前の変数があらかじめ確保される。
短い関数なら使ってもよいが、基本使わないほうが良さそう(返り値がなにかわからず、ぱっとみ return void にみえる)

package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func mul(x int) (y int) {
	y = x * 2
	return
}

func main() {
	fmt.Println(split(17))
	fmt.Println(mul(20))
	
}
ganyariyaganyariya

Go では (*p).a ではなく、p.a としてもうまく解釈して処理してくれる
アロー演算子はなく、同じ名前(シンボル)は1つしかないため、ポインタであろうがなかろうがうまく解釈される

type Vertex struct {
	a int
	b int
}

func solve() {
	v := Vertex{a: 10, b: 20}
	p := &v
	fmt.Println((*p).a)
	fmt.Println(p.a)
}
ganyariyaganyariya

var a [x]T とすると、型T サイズx の配列が確保される(型+長さ で新しい型が作られる、つまり [10]int という型)

ganyariyaganyariya

https://go-tour-jp.appspot.com/moretypes/7

Slice は可変長であり、[]T という型である。
以下のようにメモリは共有されており、かつスライスのインデックスは0からはじまる(配列 a から [3, 7) で取り出しても)

func solve() {
	var a [20]int
	for i := 0; i < len(a); i++ {
		a[i] = i
	}

	s := a[3:7]
	fmt.Println(s)
	s[0] = 1
	fmt.Println(a)
	fmt.Println(s)
}
[3 4 5 6]
[0 1 2 1 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]
[1 4 5 6]
ganyariyaganyariya

スライスは参照であり、他のメモリに確保されている領域を見ている
(内部構造的には、ポインタ, capacity, size で成り立っていたはず)

ganyariyaganyariya

Slice は「すでに確保されている他オブジェクトに対する len + cap が用意されたポインタ」と認識するとわかりやすいかも。
以下でつねに{1, 2, 3, 4, 5, 6} を認識しているのはこれがメモリ確保されていて、それをポインタ的に参照しているため

func solve() {
	s := []int{1, 2, 3, 4, 5, 6}
	printSlice(s)

	s = s[:0]
	printSlice(s)

	s = s[:4]
	printSlice(s)

	s = s[:2]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6 cap=6 [1 2 3 4 5 6]
len=0 cap=6 []
len=4 cap=6 [1 2 3 4]
len=2 cap=6 [1 2]
ganyariyaganyariya

https://go-tour-jp.appspot.com/moretypes/13

make動的に配列を構築 し、その配列のスライス(ポインタ)を返す関数。
直接スライスを作るのではなく、あくまで動的に配列領域を構築し、そのポインタスライスを返している。
配列の中身は 型T の初期値 で初期化される

ganyariyaganyariya

関数を関数の引数に与えられる。
このとき、func Name(argName func(x T) R) {} として定義する

func compute(call func(x, y int) int, x, y int) int {
	return call(x, y)
}
func solve() {
	x, y := 10, 20
	fmt.Println(compute(func(x, y int) int { return x + y }, x, y))
	fmt.Println(compute(func(x, y int) int { return x - y }, x, y))
}

ganyariyaganyariya

以下のように、特定のインターフェースを受け取るような関数をつくることが Go のよくあるパターンっぽい。

こうすると、インターフェースのシグニチャをもつ struct ならなんでも Some 関数に渡せる

func Some(a Abser) {
}
ganyariyaganyariya

var i I のように定義したあと、このインターフェースに何も入れず、nil のまま実行すると panic が発生する

ganyariyaganyariya

switch v := i.(type) で interface i の型がそれぞれ case T であった場合の直和側処理ができる
このとき、 i ではなく type でキャストした v を利用する

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Println(v * 2)
	case string:
		r := v + "Hello"
		fmt.Println(r)
	default:
		fmt.Println("other")
	}
}
ganyariyaganyariya

struct に interface のシグネチャを実装しておく。
そして、struct の値を interface を受け取る関数に与える というのがよくあるパターンらしい。
以下だと、Write 関数は String を実装しているあらゆるオブジェクトを取る

type Stringer interface {
	String() string
}

func Write(s Stringer) {
	fmt.Println(s.String())
}

type Point struct {
	x int
	y int
}

func (p *Point) String() string {
	return strconv.Itoa(p.x) + strconv.Itoa(p.y)
}

func solve() {
	p := &Point{10, 20}
	Write(p)
}
ganyariyaganyariya

https://go-tour-jp.appspot.com/methods/19

Go では Error は呼び出し元で管理する。
もし panic が発生すると Error() を実装した error オブジェクトが返される。
呼び出し元でこの error オブジェクトが nil かどうかをチェックする。

go の error は以下のような interface を持つものである。
よって、定義したエラーオブジェクトには、エラーメッセージを出す Error メソッドを定義すれば良い

type error interface {
    Error() string
}
ganyariyaganyariya

https://go-tour-jp.appspot.com/methods/21

io package には Reader, Writer インターフェースがそれぞれ定義されている。
それぞれには Read Write メソッドが宣言されており、このメソッドを実装したオブジェクトを reader, writer interface を引数にとる関数にわたす。

type Reader interface {
	Read(b []byte) (n int, err error)
}

type Writer interface {
	Write(b []byte) (n int, err error)
}

func solve() {
	// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/strings/reader.go;l=160
	// Reader Object を返す https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/strings/reader.go;l=17
	// Reader Object は https://pkg.go.dev/io#Reader の Read メソッドを実装している
	r := strings.NewReader("Hello, Reader!")
	var ior io.Reader = r

	b := make([]byte, 8)
	for {
		n, err := ior.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}
}
ganyariyaganyariya

https://go-tour-jp.appspot.com/methods/22
https://github.com/golang/tour/blob/master/reader/validate.go#L13

validate のほうが重要そう。

MyReaderio.Reader interface が宣言している Read を実装している。
そのため、io.Reader を引数にとる validate に MyReader のオブジェクトを渡すことで、Readvalidate 側が自由に実行できる。

ライブラリ側は interface を引数に取る。
これによって、ライブラリはとりあえずこの interface を実装しているあらゆるオブジェクトを実行でき、振る舞いを共通化できる

func (m MyReader) Read(b []byte) (int, error) {
	for i := 0; i < len(b); i++ {
		b[i] = 'A'
	}
	return len(b), nil
}

func Validate(r io.Reader) {
	b := make([]byte, 1024, 2048)
	i, o := 0, 0
	for ; i < 1<<20 && o < 1<<20; i++ { // test 1mb
		n, err := r.Read(b)
ganyariyaganyariya

https://go-tour-jp.appspot.com/methods/23
https://pkg.go.dev/io#example-LimitReader
https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/io/io.go;l=455

難しい...

io.Reader interface を実装したオブジェクト(つまり Readを実装したもの)に対して、さらに特殊な操作を加える Reader でオーバーラップすることがある。

特殊操作を加える Reader のオブジェクトを A とする。
また、もとの io.Reader のインターフェース(もしくはそれを実装したオブジェクト)を B とする。

このとき、 A は Read メソッドを実装し、その Read は byte 配列を受け取る。
ここで、A の Read メソッドの内部では、A の初期化時にフィールドとした B で Read を実行する。
これで本来の io.Reader の B の読み取りが行われる。
その後、A特有の処理を Read メソッドに書く。
ここで、A は io.Reader の interface func (T) Read(b byte[]) (n int, err Error) を実装していれば、同じインターフェースで処理できる

func rot13(c byte) byte {
	switch {
	case ('A' <= c && c <= 'Z'):
		return (c-'A'+13)%26 + 'A'
	case ('a' <= c && c <= 'z'):
		return (c-'a'+13)%26 + 'a'
	default:
		return c
	}
}

type Rotted13Reader struct {
	r io.Reader
}

func Rot13Reader(r io.Reader) io.Reader {
	return &Rotted13Reader{r}
}

func (rot *Rotted13Reader) Read(b []byte) (n int, err error) {
	n, err = rot.r.Read(b)
	if err != nil {
		return 0, err
	}
	for i := range b {
		b[i] = rot13(b[i])
	}
	return n, err
}

func solve() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	rr := Rot13Reader(s)
	io.Copy(os.Stdout, rr)
}
ganyariyaganyariya

https://go-tour-jp.appspot.com/methods/25

インターフェースを引数に用意しておく。
そして、そのインターフェースを実装した struct オブジェクトを構築して、引数にわたす

package main

import (
	"golang.org/x/tour/pic"
	"image"
	"image/color"
)

type Image struct {
	w int
	h int
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.w, i.h)
}

func (i Image) ColorModel() color.Model {
	return color.RGBAModel
}

func f(x, y int) uint8 {
	return uint8(x + y)
}

func (i Image) At(x, y int) color.Color {
	v := f(x, y)
	return color.RGBA{v, v, 255, 255}
}

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

ganyariyaganyariya

Channel を用意する。そして、チャネルに ch <- v, v = <- ch のようにして値をパイプ的に取り出す

ganyariyaganyariya

https://go-tour-jp.appspot.com/concurrency/8

chan で管理する

const M = 10

func Walk(t *tree.Tree, ch chan int) {
	if t.Left != nil {
		Walk(t.Left, ch)
	}
	ch <- t.Value
	if t.Right != nil {
		Walk(t.Right, ch)
	}
}

func Same(t1, t2 *tree.Tree) bool {
	ch1, ch2 := make(chan int), make(chan int)
	go Walk(t1, ch1)
	go Walk(t2, ch2)
	for i := 0; i < M; i++ {
		if <-ch1 != <-ch2 {
			return false
		}
	}
	return true
}

func main() {
	fmt.Println(Same(tree.New(1), tree.New(1)))
	fmt.Println(Same(tree.New(1), tree.New(2)))
}
ganyariyaganyariya

go にも mutex があり、複数スレッドからアクセスされる変数を lock できる
ロックしたいときは Lock、終わったら Unlock すればいい。

defer Unlock すると return が書きやすい

type SafeCounter struct {
	mu sync.Mutex
	a  int
}

func (c *SafeCounter) Inc() {
	c.mu.Lock()
	c.a++
	c.mu.Unlock()
}
func (c *SafeCounter) Get() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.a
}

func solve() {
	s := SafeCounter{a: 0}
	for i := 0; i < 10; i++ {
		s.Inc()
		fmt.Println(s.Get())
	}
}
ganyariyaganyariya

DONE!!!!

Go シンプルで Nim っぽいのでお勉強していく
(Nimがあとなのかな)

このスクラップは2022/02/13にクローズされました