Skip to content

Instantly share code, notes, and snippets.

@satoryu
Last active May 23, 2019 08:47
Show Gist options
  • Save satoryu/72708a95f1ec0ac82a116f7710617745 to your computer and use it in GitHub Desktop.
Save satoryu/72708a95f1ec0ac82a116f7710617745 to your computer and use it in GitHub Desktop.
Memo about go lang

Go言語調査

言語仕様

  • 真偽値 bool
  • 文字列 string
  • 整数 int(8, 16, 32, 64)
  • 符号なし整数 uint(8, 16, 32 64)
  • バイト byte (uint8 の別名)
  • 文字 rune (Unicodeのコードポイントを指す。int32 の別名)
  • 浮動小数点 float32(64)
  • 複素数 complex64(128)

int や uintが示すビット数は、システムに依存する。 32bitの場合は32、64bitの場合は64。

変数

  • 宣言は変数名と型名の順: var x int
    • 型は省略可
  • 初期化 var x int = 100
    • 省略記法 x := 100
    • 初期値のリテラル値から変数の型が決まる。
  • 初期化しなかった場合、型に応じて初期値(ゼロ値)が与えられる。
    • 数値型(int,floatなど): 0
    • bool: false
    • string: ""(空文字)
  • 型変換: 値vを型Tに変換する場合 T(v)

リテラル

  • 真偽値: true, false
  • 文字列: "(ダブルクォート)で囲んだ文字列
    • "This is a String"
  • 整数: 123
  • 浮動小数: 0.1234
  • 複素数: 1 + 2.3i
  • 配列: [3]T{ t1, t2, t3 }
    • ここでTは型、t1t2t3は型Tの値
  • スライス: []T{ t1, t2, t3 }

ポインタ変数

var p *int
x := 100
p = &x
*p = 200
  • int型のメモリアドレスを指すポインタ変数を定義する際に、型名の前に*をつける。
  • 変数のメモリアドレスを参照する際に、変数名の前に&をつける。
  • ポインタ変数が指すメモリアドレスの変数を参照する際に、ポインタ変数の前に*をつける。

定数

  • constを使って宣言する。
    • const X = 100

関数

func add(x int, y int) int {
    return x + y
}
  • 戻り値の型を指定する必要がある。
  • return x, y のよう複数の値を返すことも可能
func add(x int, y int) (sum int) {
    sum = x + y
    return
}
  • 関数の戻り値には名前をつけることができ、関数内で参照することができる。
    • その際、returnに引数を与えなかった場合の戻り値としてsumの値が用いられる。

制御構文

for

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
}

Cのfor文と同様に、

  • 初期化
  • 条件式
  • 後処理

初期化、後処理は省略することができ、さらにセミコロンを略すことで、while文と同じになる。

sum := 1
for sum < 1000 {
    sum += 10
}

を記述する。 条件をなくすことで、無限ループを表現できる。

if

if x < 0 {
    fmt.Println('Negative')
} else if x > 0 {
    fmt.Println('Positive')
} else {
    fmt.Println('Zero')
}

goのif文はbooleanでしか条件を判断しないので、ゼロ値などで条件式に使うことはできない。

switch

x := "foo"
switch x {
    case "foo":
        fmt.Println("foo!")
    case "bar":
        fmt.Println("bar!")
    default:
        fmt.Println("boo!")
}

goのswitch文は、breakがない。 マッチするcaseで実行された以降のcase文は実行されない。 上の例では、foo!と出力されるのみ。

defer

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello,")
}

関数の終了後に事項される関数を登録する。 deferに登録された関数に与えられる引数は、deferを実行した時点で評価される。 deferを複数回実行した時、最後に登録された関数から順に実行される(LIFO)。 IOのクローズや例外処理で利用される。

構造体

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 100
    v.Y = 200
}

配列

var a [10]int

Goの配列は固定長。

スライス

primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] # <= Slice, [ 3, 5, 7 ]
  • スライスは配列の部分列で、スライスの要素を変更すると、下の配列の要素も変更される。また、その逆も。
  • スライスリテラル: []bool{true, false, true}
  • 配列var a [10]intがある時、スライス式 a[low:high] のそれぞれを省略した場合、配列の先頭および末尾として扱われる。下記の4つの式は等価。
    • a[0:10]
    • a[:10]
    • a[0:]
    • a[:]
  • make, append を使い、動的な配列(スライス)を生成し、要素を追加することができる。

マップ

PHPの連想配列、RubyのHashに相当する。

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

// リテラル
m = map[string]Vertex {
    "Foo": Vertex{},
    "Bar": Vertex{}
}

m["Hoge"] = Vertex{}
delete(m, "Hoge")

キーに対する要素の有無の確認

elem, ok = m["Bar"] のように2つの値を返す。 ここで、ok に有無を表すboolean型の値が入る。

if elem, ok := m["Bar"]; ok {
    fmt.Println("Bar's value is ", elem)
} else {
    fmt.Println("No Bar's value")
}

値としての関数

func compute(fn func(float64) float64) float64 {
    return fn(3)
}

func main() {
    sqr := func(x float64) float64 {
        return x * x
    }
    fmt.Println(compute(sqr))
}
  • 関数はクロージャーであり、それ自体を値として利用することができる。
    • その時の型の定義は、上記の例のfunc(float64) float64 のように、引数と戻り値の型を指定する。

A Tour of Go中のExerciseに出てくるFibonatti関数を返す関数は以下のように書ける。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	sequence := map[int]int{
		0: 0,
		1: 1,
	}
	n := 0

	return func() int {
		defer func() { n++ }()

		if v, ok := sequence[n]; ok {
			return v
		}
		sequence[n] = sequence[n-1] + sequence[n-2]

		return sequence[n]
	}
}

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

メソッド

Goにはクラスは存在しないが、構造体にメソッドを定義することができる。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

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

func (v *Vertex) setX(x float64) float64 {
    v.X = x

    return x
}

func main() {
    v := Vertex{4, 29}
    fmt.Println(v.Sqrt())

    p := &Vertex{11, 29}
    p.setX(10)
    fmt.Println(p.Sqrt())
}
  • メソッドの定義は、関数の定義にレシーバーとなる構造体(もしくは構造体のポインタ)を指定することでできる。
  • 上の例のpのように、構造体のポインタであれば、構造体自体に定義されたメソッドを呼ぶことが出来る。
  • Goの関数の引数は値渡しであり、メソッドのレシーバーもまた引数として扱われる。
  • 後述するインターフェースの定義によって、構造体もしくは構造体のポインタのどちらかにレシーバーの定義を統一することが推奨される。
    • 上の例のsetXのように構造体に変更を与えるメソッドを定義する場合は、ポインタでないと出来ない。
    • また、フィールドを多く持つ構造体の場合は、値渡しのためのコピーにコストがかかる。

インターフェース

Goのインターフェースは、指定されたメソッドを持つ

type I interface {
    M() int
}

type A struct {
    X int
}

func (a A) X() int {
    return a.X
}

type B struct {
}

func (b *B) X() int {
    return 10
}

func main() {
    var i I
    i = A{100}
    i = &B
    // i = B fails
}

上の例では、インターフェースIは、「メソッドXを持つ型」として定義されている。 インターフェースIの変数iに、構造体Aの値は代入できる。 また、構造体BのポインタにメソッドXが定義されているので、構造体Bのポインタ&Biに代入できる。 しかし、構造体Bの値自体はiに代入できない。

インターフェースは変数の定義時にのみ指定できる。 そのため、実際の値が入るまではnilを取り、 また、構造体のポインタにメソッドを定義する場合、値を持たないままメソッドを呼ぶことができるため、内部でnilを適切に取り扱う必要がある。

package main

import "fmt"

type I interface {
    M()
}

type S struct {
    Str string
}

func (s *S) M() {
    if s == nil {
        fmt.Println("Nil")
        return
    }

    fmt.Println("Str is ", s.Str)
}

func main() {
    var i I
    var s *S

    i = s
    i.M()

    i = &S{"Hello"}
    i.M()
}

空のインターフェース

interface{} は、全ての型と構造体を表すインターフェースとして使える。 しかし、このインターフェースで定義されているべきメソッドは存在していないので、代入することしかできない。

var i interface{}
i = 100
i = "hoge"

型アサーション

インターフェースの変数の値が、実際にどの型なのかを調べる方法として、i.(T)を使う。

v, ok := i.(T)

iの実際の型がTの時、oktrueであり、そうでないときはfalseが入る。 いきなり、i.(T)を参照し、Tでなかった場合は実行時エラーになる。

また、別の方法として、型スイッチがある。

switch v := i.(type) {
    case int: fmt.Println("This is int")
    case T: fmt.Println("This is T")
    default: fmt.Println("Other")
}

インターフェースを用いたパッケージの例

Stringerインターフェース

fmtパッケージが提供するフォーマット出力に対応させるためのインターフェースとしてStringerインターフェースが定義されている。 fmt.Printlnなどは、与えられた引数の構造体のStringメソッドから戻ってきた文字列を出力する(JavaのtoStringメソッドと同様)。

package main

import "fmt"

type Vertex struct {
    X, Y int
}
func (v Vertex) String() string {
    return fmt.Sprintf("(X, Y) = (%v, %v)", v.X, v.Y)
}

func main() {
    v := Vertex{7, 29}
    fmt.Println(v)
}

並行プログラミング

Goの特徴として、並行プログラミングを比較的容易に実装できるためのgoroutineやそれをサポートするパッケージがある。

Goroutine

go f(x) のように、関数f�を呼ぶことでgoroutineとして関数fを実行できる。 呼び出し元の処理とは別の処理として並行して実行される。 関数fの引数xは、このgo文の時点で評価された値が与えられる。

チャネル

goroutineとのデータの送受信�にチャネルを使う。

package main

import "fmt"

func Names(c chan string) {
    names := []string{"Go", "JavaScript", "PHP"}

    for _, name := range names {
        c <- name
    }
    close(c)
}

func main() {
    ch := make(chan string)

    go Names(ch)

    for {
        name, ok := <-ch
        if ok {
            fmt.Println(name)
        } else {
            break
        }
    }

    ch = make(chan string)
    go Names(ch)
    for name := range ch {
        fmt.Println(name)
    }
}
  • チャネルの生成は、make関数で行う。
    • make(chan string, 100) のようにチャネルのサイズ(バッファ)を指定できる。
  • チャネルの型は、chan string のようにチャネルで送受信を行う型を指定する。
  • 送信: ch <- v
  • 受信: v = <-ch
  • チャネルの送受信の評価で処理は一旦ブロックされ、値をチャネルから受信(または送信)できるようになったら時点で再開される。
  • チャネルは送信側(上の例だと関数Names)のみチャネルをクローズすることができる。
    • 受信側では、name, ok := <-chのようにチャネルが閉じているかどうかを真偽値として受け取ることができる。
    • またrange chのようにrangeを使う場合は、送信側がcloseを実行しないと実行時エラーになる。

select

複数のチャネルを利用する場合に、selectを使って制御できる。

package main

import (
    "fmt"
    "time"
)

func Greeting(names chan string, control chan bool) {
    for {
        select {
            case name := <-names:
                fmt.Printf("%v DEATH!\n", name)
            case <-control:
                break
            default:
                fmt.Println("... Waiting")
                time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    names := []string{"YUIMETAL", "MOAMETAL", "SU-METAL"}

    ch := make(chan string)
    c := make(chan bool)
    go Greeting(ch, c)

    for _, name := range names {
        ch <- name
        time.Sleep(4 * time.Second)
    }
    c <- true
}

Go Modules

Go v1.12から標準で依存管理の仕組みとしてGo Modulesが導入された。 モジュールとはGoパッケージの集まりであり、GitHubなどソースコード管理ツールから公開配布される。

go.mod ファイル

開発しているモジュールが依存するモジュールのパスを記述する。 Go Modulesの依存解決がセマンティックバージョニングに基づいているため、モジュールのバージョニングもこれに従うことが求められる。

go.modファイルの例を以下に示す。

module M
require (
    A v1
    B v1.0.0
    C v1.2.3
)
exclude C v1.2.3

moduleはモジュール名とバージョンを指定する。 requireは、モジュールMが依存するモジュールのパスとバージョンを指定する。 excludeは、requireで指定された依存モジュールの中で、使用しないバージョンを指定する。

上の例の場合、

モジュールMは、以下のモジュールに依存している。
    モジュールA v1.0.0以降
    モジュールB v1.0.0以降
    モジュールC v1.2.3以降、且つv1.2.3以外

実際にどのバージョンになるかは、go modコマンドを実行し、モジュールを取得する時点で上記の条件を満たすものに決まる。

フロー

開発するGoモジュールのワークスペースに移動し、go mod initコマンドを実行する。 すると、go.modファイルが生成され、依存関係を記述するための準備が完了する。

go.modに依存関係を記述した後、go.modと同じディレクトリ上へ移動し、go mod downloadを実行する。 すると、go.modに基づいて依存モジュールの依存解決が行われ、該当のバージョンのダウンロードが実施される。 実際にどのバージョンをダウンロードしたのかは、go.sumに記録される。

参考


TODO

  • 言語
  • IDE
  • Web framework
  • パッケージマネージャ
    • bundler, gem みたいなもの探す。 -> Go modules
  • パッケージ配布
    • rubygems.org みたいなものを探す。
  • テストフレームワーク
    • test-unit 、RSpec みたいなもの
  • デプロイ
    • デプロイの方法
  • ホスティングサービス
    • とりあえず動かせる場所としてよく使われてそうなもの(Heroku?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment