Go言語さわってみた
一年前(2014年5月末の作成日の.goなファイルがあった)くらいにGo Tourを途中までやったけど、ほとんど覚えてなかったです。
ディレクトリとか環境変数とかは、前若干コード書いたので、それっぽく残ってました。emacsのgo-modeも入ってた。
という状況から、ちょうど一週間くらいたった感じです。
作ったもの
https://github.com/ktat/go-coloring テキストを正規表現で色付けするもの。そういうツールってあるっけ?
https://github.com/ktat/go-pager ↑のやつにlessっぽくしようとしたpagerを組み込んでいたけど、分離した(go-termboxを利用)。
コードは、まぁ、まだまだアレというか間違ってる/分かってない可能性が高いです。
参考にしたところ/したいところ
- Goチュートリアル
- Go言語仕様
- Effective Go
- Go Language Programming
- Go Web プログラミング(別にWebプログラミングしたかったわけではないけどまとまってそう)
- Goでのエラーハンドリングについて
- Go言語 FAQ
- YAPC::Asia 2014での牧さんのトークとスライド
- mapの使い方
- Emacsの設定
golang.org には書いてるけど、golang.jp には書いてないこともあったので、
golang.org 見たほうが良いんじゃないかなとか思う。
作法
変数/関数の命名規則などが慣習も含め決まっているので先に読んだほうが良いと思う。
https://golang.org/doc/effective_go.html#names
例えば、セッターは
SetParamName
だが、ゲッターは
GetParamName
ではなく、単純に
ParamName
とする。
パブリックなものは大文字から、プライベートなものは小文字の名前をつける(メソッドも構造体の変数も)
gofmt というコマンドがあるので、それで整形を行う(必ず)。インデントはタブです。
ファイル構成
詳しくはこちらをご覧ください。
https://golang.org/doc/code.html
go/src/github.com/ユーザー名/プロジェクト名/ソースコード.go
にしておいて、
GOPATH=/home/USER_NAME/go
としておくと、$GOPATH/src/ $GOPATH/pkg/ 以下にソースコードやコンパイル済みのライブラリがあると想定する。
import ( "github.com/mattn/go-getopt" )
のように import すると、
/home/USER_NAME/go/src/github.com/*/ /home/USER_NAME/go/pkg/ARCHITECTURE/github.com/*/
以下の *.go, *.a ファイルが読まれるもよう。
クォート
シングルクォートはルーン(rune)というもの(int32のalias)で、ユニコードのコードポイント
fmt.Println('a') // 97 == 0x61
ダブルクォートはstring 。byte(uint8 の alias)の配列
"12345" "12345"[1] == '2'
以下のような比較はエラー
"あ"[0] == 'あ' // overflows uint8
"あ"[0] の0番目のバイト(uint8)と 'あ'(rune(int32のalias))の比較になるため
バッククォートは複数行にまたがって書ける。
fmt.Println(`... ... `)
main
実行したいというときは、main package に main 関数をつくればよい。
// test.go package main func main () { // なんか書く }
そして、以下で実行できる。
go run test.go
変数宣言
var hoge string = "abcd" hoge := "abcd"
:= は型推論してくれる。
最初の代入で使うものなので、同一スコープで := を再度使うとエラー。var hoge の後に := 使ってもエラー。
スコープはブロックスコープ(https://golang.org/ref/spec#Declarations_and_scope)なので、以下はセーフ。
var str string = "abcde" println(str[3:4]) { str := "eee" println(str) }
以下のように複数の宣言もできる。
var ( hoge string foo bool )
トップレベルで宣言するときは、:= は使えない。
var hoge string var hoge string = "aaa"
blank idenitifier
"_"はblank idenitifier(https://golang.org/ref/spec#Blank_identifier)であり、無視される。
i, _ := strconv.Atoi("12")
配列
array := make([]string, 0)
追加するときは
array = append(array, "hogehoge")
map
ハッシュ的なもの。
https://blog.golang.org/go-maps-in-action
dict := map[string]string { "key": "value" } result := make(map[string]string) result["a"] = "b"
キーの存在チェック
if _, ok := dict["key"]; ok { println("key exists") }
http://stackoverflow.com/questions/2050391/how-to-check-if-a-map-contains-a-key-in-go
キーの削除
delete(mapVar, "a")
map の値に関数
はもたせられない(nested func not allowed と言われる)
map[string]func
みたいな定義はできるけど、代入したらエラーになる。
定義できる意味ないんじゃ...?
牧さんからコメントいただいて、出来るということです。
http://play.golang.org/p/voWrwbQ2oe
test := map[staing]func() {} test["a"] = func () {println(1)} test["a"]()
みたいに、引数とかを書いてなかったからだそうです。言われてみれば、そらそうだーという感じがしました。
funcの定義を書くわけなので、別のタイプの関数は取れません。
test := map[string] func() {} test["aiueo"] = func() {println(123)} // これはいいけど test["akasa"] = func(x string) {println(x)} // これはダメ
最初にググってでてきた、interface を使う方法は、どんなタイプの関数でも入れれるといえば入れれる(使いドコロがあるのかは知らないけど)
test := map[string] interface{} {} test["aiueo"] = func() {println(123)} test["akasa"] = func(x string) {println(x)} test["aiueo"].(func () )() test["akasa"].(func (x string))("a")
追記終わり
interaface とやらを使うらしい(http://stackoverflow.com/questions/6769020/go-map-of-functions)。
colorFunc := map[string]interface{}{ "red": func(s coloring.String) string { return s.Red() }, "green": func(s coloring.String) string { return s.Green() }, "blue": func(s coloring.String) string { return s.Blue() }, "yellow": func(s coloring.String) string { return s.Yellow() }, "white": func(s coloring.String) string { return s.White() }, "cyan": func(s coloring.String) string { return s.Cyan() }, "black": func(s coloring.String) string { return s.Black() }, "purple": func(s coloring.String) string { return s.Magenta() }, }
これを呼ぶときは、
colorFunc["red"].(func (colorling.string) string)(val)
のように呼ぶ。
メソッドリストのない空のinterface は何が来てもOKだから、func も入るということかと思う。
型変換
[]byte(str) string(someByte)
数値 <-> 文字列
str := strconv.Itoa(12) i, err := strconv.Atoi("12")
文字列連結
"hoge" + "fuga"
関数
func 名前 (型) 戻り型 { return ... }
戻り値が複数の時は
func 名前 (型) (戻り型,戻り型) { return ... }
戻り値にも名前をつけられる
func 名前 (型) (名前 戻り型, 名前 戻り型) { return // 変数を渡さなくても良い }
パッケージの関数
packageName.FunctionName()
で呼べる。
パッケージの型とメソッド
package hogehoge type String string func (i String) Name String{ // ... }
とかすると
hogehoge.String("hoge").Name()
とかって使える。
ループ
for しかない
ノーマル
for i := 0; i < len(str); i++ { }
無限ループ
for {
}
break で止める
continue で次のループへ
goto ラベルへ
break, continue も ラベルを取れる。
ラベルは以下のように記述。
Loop:
配列の取り出し
indexのみを取る
for i := range array { }
indexと値
for i, v := range array { }
map取り出し
キーのみ
for k := range array { }
キー、バリュー
for k, v := range map { }
if
if len(str) == 0 { } else if ... { } else { }
条件は bool でないとダメ
switch
case のところは同一タイプじゃないとダメらしい。
switch c { case 'f': fileName = OptArg case 'h': usage() os.Exit(1) default: if color, ok := colorMap[string(c)]; ok { replace = append(replace, fmt.Sprintf("(?P<%s>%s)", color, OptArg)) } else { os.Exit(1) } }
三項演算子
ありません。
substring
スライスで取る。
var str string str = "abcde" println(str[0:3]) // abc println(str[3:4]) // d println(str[4:5]) // e
文字列分割
strings.SplitN(str, sep, -1)
最後の引数はsplitする数。-1だと以下と同じ。
strings.Split(str, sep)
正規表現
関数がいっぱいあるなー。
http://tip.golang.org/pkg/regexp/
http://tip.golang.org/pkg/regexp/syntax/
https://gobyexample.com/regular-expressions
regexp.Compile
変な正規表現渡しても死なないので、自分でハンドリング。
re,regexpErr := regexp.Compile(pattern) if regexpErr != nil { log.Fatal(regexpErr) // しかし、log.Fatal や os.Exit は使わないほうがいいらしい }
regexp.MustCompile
変な正規表現渡すとエラーで死ぬ。
どっかからもらったんじゃなくて、自分で書いた正規表現はこっちで良いでしょう。
re := regexp.MustCompile(pattern)
置換
以下、全て同じ
regexp.MustCompile("^(a)(b)").ReplaceAllString("abcdef", "$2$1") regexp.MustCompile("^(a)(b)").ReplaceAllString("abcdef", "${2}${1}") regexp.MustCompile("^(?P<first>a)(?P<second>b)").ReplaceAllString("abcdef", "$second$first")
(?P<名前>パターン)
は、名前付きキャプチャ。
マッチしたものを後で使う
re := regexp.MustCompile("^(?P<first>a)(?P<second>b)") match := re.FindSubmatchIndex([]byte("abcdefab")) var dst []byte dst = re.ExpandString(dst, "$second$first", "abcdef", match) fmt.Println(string(dst))
http://astaxie.gitbooks.io/build-web-application-with-golang/content/ja/07.3.html
regexp関係のFuncと付く関数でやっても意味ないと思われる。
フラグは先頭につける
(?フラグ)パターン
以下のような感じ。
(?im)^[a-z]+$
エラーハンドリング
error という型がある。log.Fatal に投げるとエラーメッセージを出してくれる
log.Fatal(err)
// 既述ですが、log.Fatal や os.Exit は使わないほうがいいらしい
以下を読むと良いのかもしれません。
http://blog.golang.org/error-handling-and-go
一行ずつ
scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { var l = scanner.Text() }
コマンドライン引数
go-getopt
mattnさんの mattn/go-getopt が使える
https://github.com/mattn/go-getopt
example に使い方あり。
https://github.com/mattn/go-getopt/blob/master/example/example.go
OptEror = 0
にすると、エラーメッセージ(おかしなコマンドライン引数を渡した時とか)がでなくなる。
おかしなコマンドライン引数を渡した時を判別する場合は、switch の default でやれば良いかも。
ポインタ
以下のような感じで戻り値を受け取らずに、files にファイルを突っ込める。
ただ、こういうケースでポインタ使うのは間違いらしい。
http://qiita.com/ruiu/items/e60aa707e16f8f6dccd8
seekDir(&files, "./") func seekDir (files *[]string, dirName string) { fileInfo, ioerr := ioutil.ReadDir(dirName) errCheck(ioerr) for i := 0; i < len(fileInfo); i++ { if fileInfo[i].IsDir() == false { *files = append(*files, dirName + "/" + fileInfo[i].Name()) } else { seekDir(files, dirName + "/" + fileInfo[i].Name()) } } }
&変数
が、ポインタ
*変数
で、デリファレンス
メソッド
型に対してメソッドを作れる
type S string func (s *S) aaaa () { fmt.Println("=== S aaaa ===") fmt.Println(s) } func main() { var str S = "Type S" S.aaaa() }
struct で名前なしで型を埋め込むことで、埋め込んだ型のメソッドを委譲できる。
type S2 struct { str string S } func (s *S2) bbbb () { fmt.Println("=== S bbbb ===") fmt.Println(s.str) } func main() { var str S2 S2.str = "Type S2" S2.aaaa() // 委譲したもの S2.bbbb() // S2のメソッド }
以下のように "*S2" じゃなくて、"S2" のようにもかけるけど、そうすると、メソッド内でメンバを書き換えたりしても、呼び出し元は変わらない。
func (s S2) cccc() string{ s.str = "cccc" println(s.str) // cccc return s.str } var s S2 s.str = "s2" println(s.cccc()) // cccc println(s.str) // s2
Go言語はオブジェクト指向かどうかという話は、以下。
http://golang.jp/go_faq#Is_Go_an_object-oriented_language
インターフェイス
インターフェースはメソッドの羅列。インターフェースに定義されているメソッドを実装すれば、
インターフェースを受け取れる関数を受け取れる。
※ちなみに以下は微妙な例になっておりますが、書き直すのがだるいのでそのままです。
Animalizer インターフェースを実装してみた
Moveメソッドがインターフェースのメソッドとして定義されている。
AsAnimal メソッドは引数に Animalizer インターフェースを受け取れる。
package animal import "fmt" type Animalizer interface { Move() } type Animal struct {} func (a Animal) Move (){ fmt.Printf("move as animal %T\n", a); } func AsAnimal (a Animalizer) { fmt.Printf("animal is passed %T\n", a) }
Humanizer インターフェースを実装してみた
Talk と Move メソッドがインターフェースのメソッドとして定義されている。
AsHuman メソッドは引数に Humanizer インターフェースを受け取れる。
package human import "fmt" type Humanizer interface { Talk() Move() } type Human struct {} func (h Human) Move (){ fmt.Printf("move as human %T\n", h); } func (h Human) Talk (){ fmt.Printf("I'm human %T\n", h) } func AsHuman (h Humanizer) { fmt.Printf("human is passed %T\n", h) }
使ってみる
package main import ( "github.com/ktat/test/animal" "github.com/ktat/test/human" ) var a animal.Animal; var h human.Human; func main () { a.Move() h.Move() h.Talk() animal.AsAnimal(a) animal.AsAnimal(h) human.AsHuman(h) }
結果
move as animal animal.Animal move as human human.Human I'm human human.Human animal is passed animal.Animal animal is passed human.Human human is passed human.Human
仮に、
human.AsHuman(a)
なんてことをすると、
./test.go:18: cannot use a (type animal.Animal) as type human.Humanizer in function argument: animal.Animal does not implement human.Humanizer (missing Talk method)
と怒られる。
インターフェイスに定義されたメソッドを実装することで、同一のインターフェースのものということになる。
ロボット型に人のインターフェースをもたせれば人として扱える。
なんか面白いですね。
ちなみに、命名規則的に interface は、-er でつけて、- なメソッドを持つ(Stringer は、Stringを持つ)となってるので、上記は変な例だと思います。
例えば、Stringer インターフェースは、
func String string {}
なメソッドを持っています。
ついでに説明すると、適当な型に
type AnyType { str string } func (a AnyType) String string { return a.Str }
のようなメソッドを作ってやれば
fmt.Printf は、Stringer インターフェースを受け取ることができるため、
var a AnyType a.Str = "1234" fmt.Printf("%s", a)
と渡すことが出来ます。
なお、以下のようなメソッド定義だと、なんでも受け取れるようになります。
func (h Human) Any (i interface{}) { }
なお、インターフェースを満たすというのはメソッドを満たせばいいだけなので、単純に型を埋め込んでやるだけでも、インターフェースのメソッドを満たすことが出来ます。
type Human struct { animal.Animal }
インターフェースを満たすだけであれば、これだけでも良いわけですが、結果はこうなります。
move as animal animal.Animal move as animal animal.Animal // さっきは、 move as human human.Human I'm human human.Human animal is passed animal.Animal animal is passed human.Human human is passed human.Human
メソッドは委譲されていますが、もともと、
func (a Animal) Move (){ fmt.Printf("move as animal %T\n", a); }
という定義なので、a は Animal なのです。
次は、gorutine とかチャンネルとか触ってみると思います(書くかは分かんないけど)。