GoTour のお勉強をする
Go が好きになりそうになったので、 Go を第一言語にできるようにがんばる。
まず、 GoTour を久しぶりに丁寧にやる
これ今後詳しくなったらもっと理解したほうがいい
エントリポイントの 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))
}
次はここから
defer
statement は関数を遅延実行する.
ただし,引数は defer statement の位置で評価される
次はここから
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)
}
var a [x]T
とすると、型T サイズx
の配列が確保される(型+長さ で新しい型が作られる、つまり [10]int
という型)
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]
スライスは参照であり、他のメモリに確保されている領域を見ている
(内部構造的には、ポインタ, capacity, size で成り立っていたはず)
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]
make
は 動的に配列を構築 し、その配列のスライス(ポインタ)を返す関数。
直接スライスを作るのではなく、あくまで動的に配列領域を構築し、そのポインタスライスを返している。
配列の中身は 型T の初期値 で初期化される
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
m := make(map[string]int)
for _, v := range strings.Split(s, " ") {
m[v] += 1
}
return m
}
func main() {
wc.Test(WordCount)
}
関数を関数の引数に与えられる。
このとき、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))
}
Go にはクロージャがあり、定義されたときに「外側にある名前の変数のメモリ」を常に参照する。
type New Old
で Old の型を New という型で新しく命名する
type Vertex struct {}
は struct{}
型を Vertex という名前にしている
interface は以下のように定義される。
var i interface = Point{10, 20}
のように、interface のメソッドを実装している変数を受け取れる
type Abser interface {
Sum() int
}
以下のように、特定のインターフェースを受け取るような関数をつくることが Go のよくあるパターンっぽい。
こうすると、インターフェースのシグニチャをもつ struct ならなんでも Some 関数に渡せる
func Some(a Abser) {
}
ポインタ型のレシーバであれば nil
が与えられる可能性があるため、それに対処できるような実装をする必要がある
(interface に限った話じゃないように感じる)
var i I
のように定義したあと、このインターフェースに何も入れず、nil
のまま実行すると panic が発生する
variable.(T)
で interface の値が T であると仮定できる。
ただし、ret, ok := R.(T)
のように変換が成功したかが第2引数 ok の位置に入る
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")
}
}
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)
}
type IPAddr [4]byte
func (ip IPAddr) String() string {
ret := ""
for i := 0; i < 4; i++ {
ret += strconv.Itoa(int(ip[i]))
}
return ret
}
Go では Error は呼び出し元で管理する。
もし panic が発生すると Error()
を実装した error オブジェクトが返される。
呼び出し元でこの error オブジェクトが nil かどうかをチェックする。
go の error は以下のような interface を持つものである。
よって、定義したエラーオブジェクトには、エラーメッセージを出す Error
メソッドを定義すれば良い
type error interface {
Error() string
}
エラーが起きたときを呼び出し元に伝えたいときは Error
メソッドを実装したオブジェクトを返すのが良い
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
}
}
}
validate のほうが重要そう。
MyReader
は io.Reader
interface が宣言している Read
を実装している。
そのため、io.Reader
を引数にとる validate に MyReader のオブジェクトを渡すことで、Read
を validate
側が自由に実行できる。
ライブラリ側は 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)
難しい...
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)
}
インターフェースを引数に用意しておく。
そして、そのインターフェースを実装した 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)
}
go func
とやるだけで、その関数をスレッドを立てて実行する
Channel を用意する。そして、チャネルに ch <- v
, v = <- ch
のようにして値をパイプ的に取り出す
通常はチャネルを送信側が明示的に close することはしない。
主に使うときは、 v := range c
のように yield のように値を返す場合。
終了したら close する
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)))
}
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())
}
}
DONE!!!!
Go シンプルで Nim っぽいのでお勉強していく
(Nimがあとなのかな)