Googleが Goという新しいプログラミング言語 を出したようで。早速、インストールして軽くドキュメントを流し読みしてみました。
英語なんて読みたくないよ、という人もいるかもしれないし、誰かの役に立つかもしれないので自分用メモおいときます。完全に自分用なんである程度他の言語の知識がある人向けな上、ざっくり流し読みなんで間違ってるかも。
どんな言語?
-
ネイティブコードを吐く、コンパイル型。
-
速度はCレベル。
-
GC搭載。ポインタはあるけど、ポインタ演算はできません。
-
各種アーキに最適化された、それぞれのコンパイラセットを持ちます。例:
- 6g, 6l : amd64
- 8g, 8l : i386
-
linux, mac, naclに対応。
-
動的型言語と静的型言語のおいしいとこどり。
-
concurrent処理が組み込まれてます。
個人的雑感
-
こんな言語設計思想かなあと感じたり
-
とにかく、シンプルな言語に。
- C++の複雑な部分などはできるだけはずしているような。
-
いわゆるクラスベースのオブジェクト指向はない。
- 継承はない。
- あるのはフラットなインタフェース空間のみ。
-
例外もない。
-
- 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
制御構造
特徴は
- ()がいらない。
if
やswitch
などの条件部に複数の文がかける- ループは
for
のみ(while
やdo
はない) 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}
このように、 Read
と String
が定義されていればそれは reader
なんだ、と考えます( ダックタイピング )。
実行時、動的にインタフェースが実装されているかも検査できます。
1s, ok := v.(Stringer);
v
がStringer
インタフェースを満たしていれば
s
にStringer
オブジェクトとしてのvok
にtrue
が
がかえってきます。
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かな。月一くらいはブログ書いていきたいなあ・・・
- Scalaでスタック指向言語をサクッと実装する