Ruby エンジニアで Kotlin に挫折したやつの Go にゅうもん
まずこれから
package の import はまとめて書けるの良さそう
パッケージをインポートすると、そのパッケージがエクスポートしている名前を参照することができます。 エクスポートされていない名前(小文字ではじまる名前)は、外部のパッケージからアクセスすることはできません。
メソッドとかクラスじゃなくて「名前」ってなにっていう気持ち
func add(x, y int) int {
return x + y
}
引数の型が同じ場合は省略可能
func add(x string, y, z int) int {
fmt.Println(x)
return y + z
}
func main() {
fmt.Println(add("Hellow", 42, 13))
}
こういう書き方もできる
初期化子が与えられている場合、型を省略できます。その変数は初期化子が持つ型になります。
便利
関数の外では、キーワードではじまる宣言( var, func, など)が必要で、 := での暗黙的な宣言は利用できません。
省略記法使うか使わないかはこのあたりで判断するのかな
var 過激派とかいないよな
組みこみの型すっくな。。。
これ連想配列とか使うとき、毎回 package import するってコト??
急に bit shift のサンプルコード出すのやめて、文系びっくりしちゃう
章終わり
だいぶ端折られてそうだけど、ひとまず雰囲気は分かった
For continued
初期化と後処理ステートメントの記述は任意です。
省略してもいいけど、セミコロンは書けよ
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
と思ったら、次のページでセミコロンが省略できるって
適当にサンプルコードいじってて気づいたけど、使ってない import があるとエラーになるのね
これめっちゃ便利でいいなぁ
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
defer いつ使うん?って気持ち
なんか調べてみたら、Goは色々と自分でメモリの管理とかしないといけないっぽい?
章おわり
defer いつ使うん?って気持ちはありつつ、for, switch, if らへんは分かりやすかった
というかGoって書き方簡単??
ポインタって何???????
言ってることは基本情報と応用情報の知識で薄くわかるけど使い所いつやねん
なんか1ページで終わったし、そこまで使わん??
Structだ
Goはクラスがないのか
あー少し分かってきた
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 エンジニアの疑問)
Array
組み込の型ほかにもあるやん
Slice のほうが使う頻度高そう
配列の中身は型推論できないのね
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)
}
この書き方あんまり好きじゃない
配列の雰囲気がちょっと独特
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、第二引数が要素
Indexだけ必要なときは第一引数だけでOK
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
クロージャ、あまり馴染みがない。。。
Ruby でいうところのブロックって解説で割と理解できた
実際にそんなに使うかな?
章終わり
関数の説明全然出てきてないのに、いきなりクロージャとか関数は変数で渡せるとか、唐突でちょっと困った
記法代わりとシンプルなのでわかりやすい
いきなりちょっと引っかかったけど、関数とメソッドは違うのね。
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())
}
メソッド定義絶妙に読みづらい
レシーバが必要なメソッドは同一パッケージで定義
Rubyみたいにどこでもメソッドはやせるわけではない
スクリプト言語じゃないしそりゃそうか
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())
}
ポインタをレシーバにしたメソッドをつかうことで、レシーバの状態(ポインタ先)を変更できる
ポインタ少しずつ理解してきた
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が内部でよしなにポインタに変換してくれてる
引数がポインタのメソッドはポインタを渡さないとエラーになる
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
値レシーバーをとるメソッドにもポインタを渡せるけど、その場合はポインタ先の値は変わらない
値レシーバの場合はレシーバをコピーしてるから
ポインタレシーバを使う2つの理由があります。
ひとつは、メソッドがレシーバが指す先の変数を変更するためです。
ふたつに、メソッドの呼び出し毎に変数のコピーを避けるためです。 例えば、レシーバが大きな構造体である場合に効率的です。
じゃんじゃん変数に展開しまくればええやん!!
そんなにメモリのこと考えないといけないのか??
すくなくとも Rails 使ってるときは、「クソデカARオブジェクトの配列作らないように」とか、「必要ないし pluck でメモリ節約するか」くらいしか気にしてなかった
シグニチャって何?って思った
いわゆるInterfaceのことでよさそう
このあたり、Kotlinで躓いたな
空の interface を使うことで動的型付けができる(実行時まで型はなぞ)
これめんどいな、というか毎回 nil チェックすることになるのか?
Kotlinが少し恋しい気がしてきた
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!
}
こういうこともできるのね
型switchの宣言は、型アサーション i.(T) と同じ構文を持ちますが、特定の型 T はキーワード type に置き換えられます。
空のインターフェース色々と便利そう
章終了
エラーの取り扱いがRubyともKotlinとも違うのね(Exceptionじゃない)
早期リターン毎回書いて、エラーがないこと確認してから処理を書くことになるのかな?
お前が噂の goroutine か
チャネルにも型があるのね
ch := make(chan int, 99999)
チャネルのバッファ
select ステートメントは、goroutineを複数の通信操作で待たせます。
select と case で goroutine の制御ができる
章終了
ぶっちゃけgroutine はあまり分かってないけど一旦雰囲気は掴めた
A Tour of Go
かかった時間: 約 4 時間
感想
急に知らないpackageが出てきたりするので、びっくりした
あとサンプルコードが理系すぎる。。。
なんとなく雰囲気掴むくらいはできた
Ruby ほど自由じゃなくて、Kotlinほどややこしくなさそうな気がした
気になったこといくつか
-
変数の初期値入るの結構独特な気がする
-
実際のWebサーバー開発でエラーの取り回しどうしてるのか
- 毎回毎回 nil ガード書く?めんどくさくない?
-
Kotlinとちがってnull安全じゃないんだ〜、まじで!?って気持ち
- nullエラーあってもコンパイル通るよね?
-
実際のWebサーバー開発で goroutine 多用する?
- 並列でAPI叩きたいみたいなときに、気軽に書けるのはよさそう
-
Named return valueが地味に気持ち悪い気がする
- 明示的に書けばええやんって気持ち(Ruby の return 省略は棚に上げる)
-
defer の使い所
-
空 Interface の使い所
- 任意の値入れれるのは分かったけど、それで何するん?
-
ポインタなんとなく分かった気になってるけど、実際に開発してみないとわからん
-
いちいち slice の capacity していする?めんどくさくない?
-
メモリ節約のためにポインタ使う?
- そんなきつきつ?
-
Generics ない時代は大変そう
-
外部パッケージの使い方、依存管理はまったくわからんまま
- 思想的に外部パッケージあんまり使わん?
-
キャッチアップは割と早くできそう
- ぶっちゃけサーバーサイドKotlinで必要だったJVM周りの知識とか、そういうめんどくささがなさそうで良い
勝手に抱いてたガチガチな言語仕様のイメージよりかはだいぶ柔軟な気がした
とはいえ、 Ruby エンジニア的には 構造体 + Interface の書き方が馴染みなさすぎるので、実際にバックエンド開発してみないと使いやすさはなんとも言えない
それでいうと Kotlin も言語自体はめちゃくちゃ書きやすかった(フレームワーク側でつらみが多すぎただけ)