Open48

Ruby エンジニアで Kotlin に挫折したやつの Go にゅうもん

HiroVodkaHiroVodka

パッケージをインポートすると、そのパッケージがエクスポートしている名前を参照することができます。 エクスポートされていない名前(小文字ではじまる名前)は、外部のパッケージからアクセスすることはできません。

メソッドとかクラスじゃなくて「名前」ってなにっていう気持ち

HiroVodkaHiroVodka
func add(x string, y, z int) int {
	fmt.Println(x)
	return y + z
}

func main() {
	fmt.Println(add("Hellow", 42, 13))
}

こういう書き方もできる

HiroVodkaHiroVodka

関数の外では、キーワードではじまる宣言( var, func, など)が必要で、 := での暗黙的な宣言は利用できません。

省略記法使うか使わないかはこのあたりで判断するのかな
var 過激派とかいないよな

HiroVodkaHiroVodka

章終わり
だいぶ端折られてそうだけど、ひとまず雰囲気は分かった

HiroVodkaHiroVodka

For continued
初期化と後処理ステートメントの記述は任意です。

省略してもいいけど、セミコロンは書けよ

func main() {
	sum := 1
	for ; sum < 1000; {
		sum += sum
	}
	fmt.Println(sum)
}

と思ったら、次のページでセミコロンが省略できるって


HiroVodkaHiroVodka

適当にサンプルコードいじってて気づいたけど、使ってない import があるとエラーになるのね
これめっちゃ便利でいいなぁ

HiroVodkaHiroVodka

switch の初期化では var を使った変数宣言ができないっぽい


package main

import (
	"fmt"
)

func main() {
	switch var hoge = "Hoge"; hoge {
		case "Piyo":
			fmt.Println("Piyo")
		case "Hoge":
			fmt.Println("Hoge")
		}
}

=> ./prog.go:8:13: syntax error: var declaration not allowed in switch initializer

HiroVodkaHiroVodka

章おわり

defer いつ使うん?って気持ちはありつつ、for, switch, if らへんは分かりやすかった
というかGoって書き方簡単??

HiroVodkaHiroVodka

あー少し分かってきた

type Vertex struct {
	X, Y int
}

var (
	v = Vertex{1, 2}  // has type Vertex
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v, p)
	fmt.Println(v.X)
	fmt.Println(p.X)
}

ポインタ経由で変数取得すると、構造体自体をメモリに格納しなくて良いから節約になるってことっぽい?
そこまでメモリのこと考えて使わないとだめなの??(Ruby エンジニアの疑問)

HiroVodkaHiroVodka
package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

この書き方あんまり好きじゃない

HiroVodkaHiroVodka
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Println(i)
		fmt.Println(v)
		fmt.Println(pow)
		fmt.Println("---")
	}
}

第一引数がIndex、第二引数が要素

HiroVodkaHiroVodka
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}

map の値は型省略OK

HiroVodkaHiroVodka

章終わり

関数の説明全然出てきてないのに、いきなりクロージャとか関数は変数で渡せるとか、唐突でちょっと困った
記法代わりとシンプルなのでわかりやすい

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

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}

メソッド定義絶妙に読みづらい

HiroVodkaHiroVodka
package main

import (
	"fmt"
)

type Vertex struct {
	X, Y int
}

func (v Vertex) Abs() (int, int) {
	v.X = v.X * 2
	v.Y = v.Y * 3
	return v.X, v.Y
}

func (v *Vertex) Scale(i int) {
	v.X = v.X * i
	v.Y = v.Y * i
}

func main() {
	v := Vertex{3, 6}
	v.Scale(2)
	fmt.Println(v.Abs())
	fmt.Println(v.Abs())
	v.Scale(2)
	fmt.Println(v.Abs())
}

ポインタをレシーバにしたメソッドをつかうことで、レシーバの状態(ポインタ先)を変更できる

HiroVodkaHiroVodka
package main

import "fmt"

type Vertex struct {
	X, Y int
}

func (v *Vertex) Scale(f int) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f int) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2)
	fmt.Println(v.X, v.Y)
	
	p := &v
	p.Scale(2)
	fmt.Println(v.X, v.Y)
	
	ScaleFunc(p, 10)
	fmt.Println(v.X, v.Y)
}

package main

import "fmt"

type Vertex struct {
	X, Y int
}

func (v *Vertex) Scale(f int) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f int) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2)
	fmt.Println(v.X, v.Y)
	
	p := &v
	p.Scale(2)
	fmt.Println(v.X, v.Y)
	
	ScaleFunc(p, 10)
	fmt.Println(v.X, v.Y)
}

6 8
12 16
120 160

レシーバがポインタのメソッドには変数 or ポインタの両方渡せる
で、Goが内部でよしなにポインタに変換してくれてる

引数がポインタのメソッドはポインタを渡さないとエラーになる

HiroVodkaHiroVodka
package main

import (
	"fmt"
)

type Vertex struct {
	X, Y int
}

func (v Vertex) Hoge() {
	v.X = v.X * 2
	v.Y = v.Y * 3
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.X, v.Y)
	
	v.Hoge()
	fmt.Println(v.X, v.Y)
	
	p := &v
	p.Hoge()
	fmt.Println(v.X, v.Y)
}

3 4
3 4
3 4

値レシーバーをとるメソッドにもポインタを渡せるけど、その場合はポインタ先の値は変わらない
値レシーバの場合はレシーバをコピーしてるから

HiroVodkaHiroVodka

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

ポインタレシーバを使う2つの理由があります。
ひとつは、メソッドがレシーバが指す先の変数を変更するためです。
ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。

じゃんじゃん変数に展開しまくればええやん!!
そんなにメモリのこと考えないといけないのか??

すくなくとも Rails 使ってるときは、「クソデカARオブジェクトの配列作らないように」とか、「必要ないし pluck でメモリ節約するか」くらいしか気にしてなかった

HiroVodkaHiroVodka

空の interface を使うことで動的型付けができる(実行時まで型はなぞ)

これめんどいな、というか毎回 nil チェックすることになるのか?

Kotlinが少し恋しい気がしてきた

HiroVodkaHiroVodka
package main

import "fmt"

type Speaker interface {
	Speak()
}

type Duck struct {}

func (d Duck) Speak() {
	fmt.Println("Quack!")
}

type Person struct {}

func (p Person) Speak() {
	fmt.Println("Hello!")
}

func makeSpeak(speaker Speaker) {
	speaker.Speak()
}

func main() {
	var duck Duck
	var person Person
	
	makeSpeak(duck)    // 出力: Quack!
	makeSpeak(person)  // 出力: Hello!
}

こういうこともできるのね

HiroVodkaHiroVodka

型switchの宣言は、型アサーション i.(T) と同じ構文を持ちますが、特定の型 T はキーワード type に置き換えられます。

空のインターフェース色々と便利そう

HiroVodkaHiroVodka

章終了

エラーの取り扱いがRubyともKotlinとも違うのね(Exceptionじゃない)
早期リターン毎回書いて、エラーがないこと確認してから処理を書くことになるのかな?

HiroVodkaHiroVodka

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

select と case で goroutine の制御ができる

HiroVodkaHiroVodka

章終了
ぶっちゃけgroutine はあまり分かってないけど一旦雰囲気は掴めた

HiroVodkaHiroVodka

A Tour of Go
かかった時間: 約 4 時間

感想
急に知らないpackageが出てきたりするので、びっくりした
あとサンプルコードが理系すぎる。。。
なんとなく雰囲気掴むくらいはできた

Ruby ほど自由じゃなくて、Kotlinほどややこしくなさそうな気がした

HiroVodkaHiroVodka

気になったこといくつか

  • 変数の初期値入るの結構独特な気がする

  • 実際のWebサーバー開発でエラーの取り回しどうしてるのか

    • 毎回毎回 nil ガード書く?めんどくさくない?
  • Kotlinとちがってnull安全じゃないんだ〜、まじで!?って気持ち

    • nullエラーあってもコンパイル通るよね?
  • 実際のWebサーバー開発で goroutine 多用する?

    • 並列でAPI叩きたいみたいなときに、気軽に書けるのはよさそう
  • Named return valueが地味に気持ち悪い気がする

    • 明示的に書けばええやんって気持ち(Ruby の return 省略は棚に上げる)
  • defer の使い所

  • 空 Interface の使い所

    • 任意の値入れれるのは分かったけど、それで何するん?
  • ポインタなんとなく分かった気になってるけど、実際に開発してみないとわからん

  • いちいち slice の capacity していする?めんどくさくない?

  • メモリ節約のためにポインタ使う?

    • そんなきつきつ?
  • Generics ない時代は大変そう

  • 外部パッケージの使い方、依存管理はまったくわからんまま

    • 思想的に外部パッケージあんまり使わん?
  • キャッチアップは割と早くできそう

    • ぶっちゃけサーバーサイドKotlinで必要だったJVM周りの知識とか、そういうめんどくささがなさそうで良い
HiroVodkaHiroVodka

勝手に抱いてたガチガチな言語仕様のイメージよりかはだいぶ柔軟な気がした
とはいえ、 Ruby エンジニア的には 構造体 + Interface の書き方が馴染みなさすぎるので、実際にバックエンド開発してみないと使いやすさはなんとも言えない
それでいうと Kotlin も言語自体はめちゃくちゃ書きやすかった(フレームワーク側でつらみが多すぎただけ)