Open17

Go言語メモ

れやかれやか

入門する前のGoの感想

  • =>とか??とか記号使わないの!?
  • ジェネリクス[]なのか... まじか
  • スペースのセマンティクスが強そう
  • 全体的にPythonみたいだな
れやかれやか

Makefile

GoってMakefileを使うんだ
独自のTask Runnerあると思ってた

ゼロ値

滅茶苦茶納得した
JavaScriptだとundefinedが出てくるような場面でゼロ値が出てくる、という仕組みはとても納得できる

複素数型

いるか???
あってもいいとは思う

れやかれやか

変数宣言

var x int = 30

var x int // zero value

var (
    a    int
    b        = 20
    c    int = 30
    d, e     = 40, "Hello"
)

自由度が高い

使い分けの基準が載ってた

  • 変数をゼロ値にしたいならvar x int
  • リテラルのデフォルト値が欲しい型と異なるときにはvar x <type> = ...と書く。x := <type>(...)は避けたほうがいい
  • リテラルが欲しい型とおなじになるならx := ...を使う。短いし

ただし、x, y := ... , ...みたいなのは、カンマokイディオムについてのみ使うべき

x := ...のとき宣言と代入の構文が同じのは気になる

unused variable

未使用変数→エラー
結構思い切ったけど不便ではなさそう

れやかれやか

:=の推論について

package main

import "fmt"

func main() {
    a := 30
    var b int32 = 3
    fmt.Println(a / b) // invalid operation: a / b (mismatched types int and int32)
}

これはエラーが出る
aがintと推論されるので、int32であるbとは型が違う

暗黙的な型変換禁止!!!


fn main() {
    let a = 30;
    let b: i32 = 3;
    println!("{}", a / b); // 10
}

Rustでは大体同じように書いてもエラーが出ない

れやかれやか

RustとGoでは随分違いそう

Go

a := 30と書いた時点でintと推論

Rust

let a = 30;だけでは型が確定せず、4行目でi32の型と一緒に使われることによってi32と確定する。

Rustのほうがリテラルをuntyped(型無し)として扱うのが上手そう

れやかれやか

定数

Goの定数はリテラルに名前を付与するものです。変数がイミュータブルであることを宣言する方法はありません。

JSのconstとは大分勝手が違うみたい
配列・スライス・マップ・構造体すべてconstに代入できないみたい
コンパイル時に決定できる値のみ


const x  = 10

これは型がない(untyped)なので、他のint系の型[1]と相互的に変換できる

脚注
  1. int系の型 u?int, u?int(8|16|32|64), byte, rune, uintptr
    intisize(Rust)、uintusize(Rust)みたいな ↩︎

れやかれやか

配列とスライス

Goではあまり配列を直接使うことは少ないらしい
配列では長さまで型に組み込まれているので使い勝手が悪い

スライスが他言語の配列の雰囲気で使えるらしい

[]int{}はちょっとわかりにくい

Map

GoではMapはHashMap
for文で回すと順番がランダムになる←順番に並ぶことを前提としたコードを防ぐため

いちいちランダムにしてるなら乱数生成のオーバーヘッドがありそうな気もする

Set

Goに標準でSetなんてものはないので、Mapにキーの重複が許されないことを利用する

package main

import "fmt"

func main() {
	m := map[int]bool{}
	nums := []int{1, 3, 4, 5, 5, 7, 5, 1}
	for _, num := range nums {
		m[num] = true
	}
	fmt.Println(m) // map[1:true 3:true 4:true 5:true 7:true]

	if m[4] {
		fmt.Println("4は存在する")
	}
	if !m[10] {
		fmt.Println("10は存在しない")
	}
}

存在しないキー(=つまり集合に存在しない要素)にアクセスするとエラーではなく、値型のzero valueが返される。つまりこの場合はboolのzero value, falseが返される。これを利用して簡潔に存在判定を記述できると。
やっぱりzero value便利

ちなみに、パフォーマンスを極めるならmap[int]boolではなく、map[int]struct{}を使うらしい

package main

import "fmt"

func main() {
	m := map[int]struct{}{}
	nums := []int{1, 3, 4, 5, 5, 7, 5, 1}
	for _, num := range nums {
		m[num] = struct{}{}
	}
	fmt.Println(m) // map[1:{} 3:{} 4:{} 5:{} 7:{}]

	if _, ok := m[4]; ok {
		fmt.Println("4は存在する")
	}
	if _, ok := m[10]; !ok {
		fmt.Println("10は存在しない")
	}
}

見栄えはあまりよろしくない

結局はこれ使え

https://github.com/deckarep/golang-set

れやかれやか

for文

whileは全てforに置き換えられている
確かに2つのキーワードは必要はないね
Simple is best

goto ... ?

Go To Statement Considered Harmful ー Edgar Dijkstra

ブロック単位でのジャンプしかできない模様
gotoを使ったらわかりやすいコードも稀にあるらしい、稀に

れやかれやか

関数

概ね普通、func f(a, b int)は良いね

blank return

書籍曰く、

Go言語の重大な欠陥

らしいです。

package main

import "fmt"

func calc(a int, b int) (r1 int, r2 int) {
	r1 = a + b
	r2 = a - b
	return
}

func main() {
	fmt.Println(calc(3, 1))
}

これがコンパイル通るのか...

reviveというlinterでは禁止されているらしい。
使ってはいけません、と

関数の型はfunc(<type>) <type>

defer

自動実行されるクリーンアップ関数

JSのusingやPythonのwithみたいなもの
関数を出るときに必ず(関数がpanicしても)実行される
JS, pythonではブロックを出るときに実行される

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Open("sample.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// process f ...
    // fは自動で閉じられる
}
れやかれやか

Goは値渡し

関数の引数は値として渡されて、元の値には変更を与えない
ただし、マップやスライスはポインタとして実装されているため、関数内での変更が反映される
関数内でもとの値を変更するにはポインタとして値を渡す→ポインタはミュータブルの印

ポインタを取得するには&a、ポインタの値を参照するには*aとかく。
ポインタ型は*intのように書く。Rustでは&i32なので異なる。

どうやらC言語の流れを汲んでそう
https://www.cc.kyoto-su.ac.jp/~yamada/programming/pointer.html#pointer

この宣言の書き方にはとまどうことがあるかと思う。しかし,この宣言「 int *p 」を,「 p の左に * を付けたら int 型になる」と読むと,なるほどと理解できる。実際,int 型のポインタの左に * を付けたら int 型の変数になるからだ。

れやかれやか

ポインタ

リテラルに直接&をつけることはできない

値を変更するにはポインタに再代入するのではなく、ポインタの指す先に再代入する。

package main

import (
	"fmt"
)

func failedUpdate(px *int) {
	a := 20
	px = &a
}
func update(px *int) {
	*px = 20
}
func main() {
	x := 10
	failedUpdate(&x)
	fmt.Println(x) // 10
	update(&x)
	fmt.Println(x) // 20
}

マップやスライスポインタ

既存の要素の更新は反映されるが、要素を追加した場合にはその変更は反映されない

package main

import "fmt"

func main() {
	s1 := []int{1, 2, 3}
	s2 := s1
	fmt.Println(s1, s2) // [1 2 3] [1 2 3]

	s2[0] = 0
	fmt.Println(s1, s2) // [0 2 3] [0 2 3]

	s2 = append(s2, 5)
	fmt.Println(s1, s2) // [0 2 3] [0 2 3 5]

    fmt.Println(len(s1), len(s2)) // 3 4
}

この場合、s2のlenが増えても、s1のlenは増えない。つまり、配列のうちのs1が見ている範囲は反映されない

れやかれやか

メソッド

ポインタ型レシーバーと値型レシーバ

使い分けについて以下引用

  • メソッドがレシーバを変更するならポインタレシーバを使わなければならない
  • メソッドがnilを扱うする必要があるなら、ポインタレシーバを使わなければならない
  • メソッドがレシーバを変更しないなら、値レシーバを使うことができる

ポインタレシーバはnilを扱うことができる。

ポインタレシーバへのメソッド

func (c *Counter) Increment() { ... }

var c Counter
c.Increment()

IncrementメソッドはCounterのポインタ型を取るが、&cとする必要はない。暗黙的にcのポインタへと変換される

れやかれやか

型宣言

typeは型に別名をつけるのではなく、別の新しい型を作る

type Score int

// untyped literalは代入可能
var i int = 30
var s Score = 10

// s = i <- Error [ cannot use i (variable of type int) as Score value in assignment ]
s = Score(50)

// 相互変換は可能
si := int(s) + i
is := s + Score(i) + s

Branded Typeというような工夫がいらない