個人的Go雑感&メモ

Googleが Goという新しいプログラミング言語 を出したようで。早速、インストールして軽くドキュメントを流し読みしてみました。

英語なんて読みたくないよ、という人もいるかもしれないし、誰かの役に立つかもしれないので自分用メモおいときます。完全に自分用なんである程度他の言語の知識がある人向けな上、ざっくり流し読みなんで間違ってるかも。

どんな言語?

  • ネイティブコードを吐く、コンパイル型。

  • 速度はCレベル。

  • GC搭載。ポインタはあるけど、ポインタ演算はできません。

  • 各種アーキに最適化された、それぞれのコンパイラセットを持ちます。例:

    • 6g, 6l : amd64
    • 8g, 8l : i386
  • linux, mac, naclに対応。

  • 動的型言語と静的型言語のおいしいとこどり。

  • concurrent処理が組み込まれてます。

個人的雑感

  • こんな言語設計思想かなあと感じたり

    • とにかく、シンプルな言語に。

      • C++の複雑な部分などはできるだけはずしているような。
        • いわゆるクラスベースのオブジェクト指向はない。

          • 継承はない。
          • あるのはフラットなインタフェース空間のみ。
        • 例外もない。

    • 低レイヤからは離れすぎたくない。

    • concurrentを言語的にサポートしたい。

  • 以下の言語からの影響を感じたり

    • 言語コアはC。
    • C++はパッケージの書き方、記法に影響がみられる。
    • 命名規則や記法にPythonの影響がみられる。
    • concurrentな部分はErlangから影響をうけている。

言語仕様

自分なりに簡単にまとめて見ます。

変数への代入

下へ行くほど省略形。 var もあるから const もあるでよ。

1var s string = "";
2var s = "";
3s := "";

オブジェクト構造

値扱いの型と参照型があります。

  • 値扱い : 代入、関数への私はコピーとなる。配列もまるごとコピーされる。

    • 各種数値 : int , float といったプラットフォームごとにサイズがきまる型と int32 のようにサイズ固定の型。
    • string : 不変、UTF8、ただのバイト配列
    • 配列
    • struct : ユーザ定義型
    • などなど
  • 参照 : 3つのみ

    • map : 辞書
    • slice : 明示的なサイズを持たない、配列のようなもの
    • channel : concurrentで使う

値扱い型のアロケート方法 : new -> T* を返す

1var t *T = new(T);

参照型のアロケート方法 : make -> T を返す

1m := make(map[string]int);
2// これはダメ
3var m map[string]int;

配列とスライス

  • 配列は「値」。でユーザがメモリ構造などを読める。スライスはコンパイラがよしなに確保する、参照型。

例えば int 型配列のポインタを受け取る場合。

標準配列(&がいる)

1s := sum(&[3]int{1,2,3});
2s := sum(&[...]int{1,2,3});

スライス

1s := sum([]int{1,2,3});

辞書型

 1timeZone := map[string]int{
 2  "UTC": 0*60*60,
 3  "EST": -5*60*60,
 4  "CST": -6*60*60,
 5  "MST": -7*60*60,
 6  "PST": -8*60*60,
 7}
 8
 9seconds, ok = timeZone[tz]
10//値がなければokはfalse
11if seconds, ok := timeZone[tz]; ok {
12    return seconds
13}
14//消す場合
15timeZone["UTC"] = 0, false;

パッケージ、制御構造、型定義、関数とメソッド、インタフェース

命名規則に特徴があります。 大文字始まり以外は外部から見えない が原則。単なる命名規則ではなく、そういう仕様。

パッケージ

パッケージ文でファイルの先頭に書きます。これが基本的な単位となります。

1package file

制御構造

特徴は

  • ()がいらない。
  • ifswitch などの条件部に複数の文がかける
  • ループは for のみ( whiledo はない)
  • switch が強力に(内容的にif-else if-elseチェイン)
  • concurrent用に select がある

という点。かと。

ifの例

1if i % prime != 0 {
2    fmt.Printf("%d", i);
3}

switchの例

 1switch nr, er := f.Read(&buf); true {
 2           case nr < 0:
 3               fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
 4               os.Exit(1);
 5           case nr == 0: // EOF
 6               return;
 7           case nr > 0:
 8               if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
 9                   fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
10               }
11}

caseに条件式がかける。breakは自動でされる。

型定義

type , struct キーワードを組み合わせる。 メンバにメソッドは含みません。

1type File struct {
2    fd int; // file descriptor number
3    name string; // file name at Open time
4}

メンバ名が小文字なので、パッケージ外からは見えません。

関数とメソッド

  • 両方とも func キーワードで定義。
  • 違いはレシーバを指定するか否か。 レシーバを明示的に書くところはPythonっぽい。
  • 多値が返せる。

この多値がかえせる、というのがGoでは非常に重要な意味をもっています。

関数定義: 多値を返しています。大文字始まりなので、外部に公開されます。

1func Open(name string, mode int, perm int) (file *File, err os.Error) {
2     r, e := syscall.Open(name, mode, perm);
3     if e != 0 {
4         err = os.Errno(e);
5     }
6     return newFile(r, name), err
7}

返す型に (file *File, err os.Error) と変数名がついてますね。これをつけておけば return; ってかくだけでその名前の変数を返してくれます。

1func Open(name string, mode int, perm int) (file *File, err os.Error) {
2     r, e := syscall.Open(name, mode, perm);
3     if e != 0 {
4         err = os.Errno(e);
5     }
6     file = newFile(r, name)
7     return;
8}

こんな風にもかけるってことです。おそらく、エラー処理が絡む場合とかこっちのほうが書きやすかったりするんじゃないですかね。

クロージャにもなります。いわゆる関数ポインタをとるようなところで、関数がそのままかけて外部変数も見えます。

1startServer(func(a, b int) int { return a + b });

メソッド定義。 (file *File) がレシーバの指定。

 1func (file *File) Close() os.Error {
 2    if file == nil {
 3        return os.EINVAL
 4    }
 5    e := syscall.Close(file.fd);
 6    file.fd = -1; // so it can't be closed again
 7    if e != 0 {
 8        return os.Errno(e);
 9    }
10    return nil
11}

メソッド定義が struct での型定義時ではないことに注目。つまり 組み込み型などに対してもあとからメソッドを作成できるのです 。こんな感じ。

1type IntArray []int
2func (p IntArray) Len() int { return len(p); }

インタフェース

これ重要。Goはいわゆるクラスがないので、継承などもなく インタフェースによるダックタイピングでそれらを片付けますinterface キーワードで定義。

1type reader interface {
2     Read(b []byte) (ret int, err os.Error);
3     String() string;
4}

このように、 ReadString が定義されていればそれは reader なんだ、と考えます( ダックタイピング )。

実行時、動的にインタフェースが実装されているかも検査できます。

1s, ok := v.(Stringer);

vStringer インタフェースを満たしていれば

  • sStringer オブジェクトとしてのv
  • oktrue

がかえってきます。

Concurrency

concurrentプログラミングはGoの大きな特徴。 CSP(Communicating Sequential Processes) に基づいてます。並行して動く「goroutines」という軽量プロセスが「channel」を介してやりとり。ガードと多重化のためにselect文があります。

channelは単体ではなくchan 受け渡しする型という感じで書きます。以下はチュートリアルのコードまんまです。

1func generate(ch chan int) {
2    for i := 2; ; i++ {
3        ch <- i // Send 'i' to channel 'ch'.
4    }
5}

int を扱う channel を受け取ってそれにiを送っていきます。

1func filter(in, out chan int, prime int) {
2    for {
3        i := <-in; // Receive value of new variable 'i' from 'in'.
4        if i % prime != 0 {
5            out <- i // Send 'i' to channel 'out'.
6        }
7    }
8}

送られた i が一定の条件を満たしていたら、 int を扱う out というchannelに送ります。

 1func main() {
 2    ch := make(chan int); // Create a new channel.
 3    go generate(ch); // Start generate() as a goroutine.
 4    for {
 5        prime := <-ch;
 6        fmt.Println(prime);
 7        ch1 := make(chan int);
 8        go filter(ch, ch1, prime);
 9        ch = ch1
10    }
11}

channelは参照型なので make で作ります。 go で実行します。これは丁寧に書いた感じ。クロージャを使えばもっとシンプル。

1func generate() chan int {
2    ch := make(chan int);
3    go func(){
4        for i := 2; ; i++ {
5            ch <- i
6        }
7    }();
8    return ch;
9}

多重化とガード

複数のチャンネルをとりあつかって、それらをガードにより振り分けられます。ErlangやScalaでおなじみの書き方です。

 1func server(op binOp, service chan *request, quit chan bool) {
 2    for {
 3        select {
 4        case req := <-service:
 5            go run(op, req); // don't wait for it
 6        case <-quit:
 7            return;
 8        }
 9    }
10}

request 型のポインタを扱うchannelと bool 型を扱うchannelを使って、多重化しています。 quit チャンネルに値が送られてくるまでは、送られてきたものから良しなに処理してくれる、という感じですね。

というわけで

なぐり書きしたメモでした。変なことかいてたらすみません。まぁこんな感じな言語かなあ、という程度で。

繰り替えしになりますが、C言語を元にシンプルに保ちながらconcurrentプログラミングしやすくしてます、って感じですね。完全にダックタイピングベースで多値を多様するスタイルはおもしろいですね。なんとなく見た目がキモく感じるのは私の気のせいでしょう。

というか数ヶ月もブログ放置してたのかー。コード書いてないわけじゃないんですけど、たいしたもんかいてないんですよね。割合的には8割がたCかな。月一くらいはブログ書いていきたいなあ・・・

comments powered by Disqus