2010年2月27日土曜日

型について その3 len()とrange

goのチュートリアル、型についての続きです。

len()


goでは文字列、配列、map、スライス、チャネルの要素の数をlen()という関数で取得できるようです。

for i := 0; i < len(a); i++ { ... }
* UTF-8のときは一文字ずつになるのでしょうか。たぶんなるのでしょう。 組み込み関数と書かれていますが、実際はどうなっているのか不明です。 sizeof的組み込み関数なのでしょう。

len()実験

簡単に、lenの動きを見てみました。
package main

import (
"fmt"
)

func main() {
var a[16]uint;
fmt.Printf("len(a)=%d\n",len(a))
str := "12345678901234567890"
fmt.Printf("len(str)=%d\n",len(str))

}
結果は
$ ./8.out 
len(a)=16 
len(str)=40 
アレ? UTF8はバイト数ででてますね。 ということはこれsizeofと同じということのようです。 あと、文字列の最後にnullとかはあるのかもしれないけど入ってないと。

range

おそらくは、このlen()を内部でつかっているrangeという指定ができて
for i, v := range a { ... } 
こんな書き方でforeachになる、と。 foreach気持ち悪いけどfor i:=0;i< sizeof(a);i++ はオッケーという Cっぽい思想をよく汲んでいると思います。 今っぽい書き方をさせつつもアセンブラを意識できる記述なのかな。 よくできている。 しかし今時の人に受けるのかな。そこはわかりません。

rangeと文字列

rangeを文字列で使うサンプルを書いてみました。
package main

import (
"fmt"
)


func main() {
str := "12345678901234567890"
for v := range str {
fmt.Printf("%c\n",v)
}

}
こちらも普通にbyteで処理されちゃいますね。
$ ./8.out 














! 
" 
# 
$ 
% 
& 
' 

ひどいす。
まあきっとなにか、文字列用の関数とかあるのでしょう。

型について その2 配列とスライスとmap

配列


goでは配列の定義はこうします
var arrayOfInt [10]int;
これもまた、C言語のようにIntのポインタとして扱うことはできません。
しかし要素の入れ替えは自由にできます。


スライス


スライスは概念的にはDから持ってきたのでしょうか。
配列の一部だけを参照できるポインタです。
考え方的にはPythonなどにもあったような...よく覚えてません。

チュートリアルの解説はちょっとわかりにくいのですが
var a[10]int;
という配列があったときに
as := a[3:5];
とすると、asはa[3], a[4], a[5] を参照できる配列のスライスとなるようです。
例題欲しいなぁ。


C++的にこれも使いたいその場で生成可能で次に出てくる例題はこの、
その場で生成するタイプとなっています。
func sum(a []int) int {   // returns an int
s := 0;
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
という配列を引数に持つ関数の実行の際に、 あらかじめ配列をべつに定義する必要は無くて、定義する場所で
s := sum(&[3]int{1,2,3})
としてスライスを生成して直接代入すると、 参照する側ではint s={1,2,3} が見えると。 ユーザー定義型でこれができたらかなりいろいろ楽になりそうです。 システムコール周りが天国になりそうな雰囲気。 スライスの生成で変数が使えるのでしょうか。たぶん、できるのでしょう。 やってみたいけど今は先に進みます。 スライスって言う呼び名は日本語的にイマイチだなぁ。 配列の部分参照みたいな言い方にするともっといまいちですがやや直感的と思います。

map

mapはrubyではおなじみのmap関数ではなく perlの方のmapのようです。 ここには説明が無いので不明ですが、ハッシュや連想配列にあたる物みたいです。
m := map[string]int{"one":1 , "two":2}
map関数はすごく便利なのできっとそれもあるのだろうと思います。
(知りませんが)

もうひとつ、チャネルという言葉が出てきますけど、
何の説明も無いので次いきます。
おそらくはstreamかsocktのことをいっているんじゃないかとおもいますが
わかりませんねー。

型について その1 String

チュートリアルの型についての説明は、いきなりハードルがあがります。
文字列、配列、ポインタ、スライス、mapなど
既存の概念を知らないとこの解説だけでついていくのは難しそうに思います。

String型


goの文字列型はバイト列ではなく固定の物で、
StringをbyteであつかったりIntのポインタに変換したりはできないそうです。
おそらくは文字列表現へのポインタで
単純にそれを入れ替える作りなのかなぁと今のところ考えています。
そのうちヘッダを読んでみます。


s := "hello"
if s[1] != 'e' { os.Exit(1) }
s = "good bye"
var p *string = &s
*p = "ciao"

こういう文字列の入れ替えは可能ですが

s[0] = 'x';
(*p)[1] = 'y';

文字列をポインタとして扱うことは不可能ですと。
文字列全体を入れ替えることはできるので
置換用の関数やらメソッドみたいなもので入れ替えろと言いたいのでしょう。

In C++ terms, Go strings are a bit like const strings, while pointers to strings are analogous to const string references.


これでわかるひととわからん人にわかれそうな解説...。
まあそういうことはとりあえず見なかったことにして先に進みましょう。

echoの作成

チュートリアルのEcho作成をやってみましたよ。
package main

import (
	"os"
	"flag" // command line option parser
)

var omitNewline = flag.Bool("n", false, "don't print final newline")

const (
	Space = " "
	Newline = "\n"
)

func main() {
	flag.Parse() // Scans the arg list and sets up flags
	var s string = ""
	for i := 0; i < flag.NArg(); i++ {
		if i > 0 {
			s += Space
		}
		s += flag.Arg(i)
		}
	if !*omitNewline {
		s += Newline
	}
	os.Stdout.WriteString(s)
}
NArgっていうのはどうやらArgvのことを言うようです。
このechoは引数を渡すとそれを標準出力に出すように見えます。

やってみましょう。
$ 8g echo.go
$ 8l echo.8
$ ./8.out
$
素で実行すると何もおこりません。
引数に文字列を渡してみましょう。
$ ./8.out echoのテスト
echoのテスト
$
そのままですね。

constキーワードについて解説がありますがこれはツッコミどころなし。
const 文字列 = 定義
で定数を定義できます。echoみたいに
const (
	Space = " "
	Newline = "\n"
)
もできるようです。

興味深いのはvar での変数定義方法です。
通常は
var s string = "";
としますが、""で括った物は文字列であることがコンパイラは知っているので
var s = "";
と書くこともできて、さらに明示的な定義&代入を
s := "";
とも書けるようにしました、と。

:=はmakefileみたいですね。

最後にセミコロンがついているのはおそらくC言語的に"" は連結できるから、なのでしょうか。
そのうちわかるでしょう。

flag.Boolの解説はここにはなさそうです。単にboolだと。三つ引数がありますけど。まあいっか。
* forは Cの()がなくなったもの。
* +=で文字列結合可能。
* flag.NArg()はコマンドライン引数の数が
* flag.Arg(num)はnum番目の引数を取り出すのでしょう。
* 配列を渡してこないあたり賢くなってますね。

中身を読んだのでもう一度実行してみます。
コマンドラインなので半角スペースが引数の区切りになるはずです。
ちょっとだけ改造してみました。
package main
import (
	"os"
	"fmt" /* Printを使うため */
	"flag" // command line option parser
)

var omitNewline = flag.Bool("n", false, "don't print final newline")

const (
	Space = " "
	Newline = "\n"
)

func main() {
	flag.Parse() // Scans the arg list and sets up flags
	var s string = "";

	for i:=0 ; i < flag.NArg(); i++ {
		if i > 0 {
			s += Space
		}
		s += flag.Arg(i)
	}
	if !*omitNewline {
		s += Newline
	}
	os.Stdout.WriteString(s)
	fmt.Printf("%d\n",flag.NArg()) /* 引数の個数を出力*/
}
for で:=を使って宣言したiはスコープがforの中に制限されるのですね。
PerlとかC++的には当然のことですが
PHPだと違和感ありますね。Cはそういうことできません。

fmt.Printfの中はテキトウに書いたのですがこれでよいようです。
コンパイルして実行してみましょう。
$ ./8.out

0
動いていそうです。
引数を渡してみると
$ ./8.out echoのテスト その2 その3
echoのテスト その2 その3
3
引数を変えてみましょう。
$ ./8.out echoのテスト その2\ その3
echoのテスト その2 その3
2
期待通り動作しています。

かなり沢山のことがこのechoに詰まっていますが
Cよりユルくて、PHPより固い仕様だとおもいました。
CとかC++とかPythonをよく研究していますね。
どちらかというと既存の言語をあんまり知らない人向けの言語かなぁと。
我ながらひどいまとめですがいまのところはそんなものでいいということにしておきます。

Gccgo

Gccgoという、GoをGCCでコンパイルするバックエンドがあるそうです。
環境はまあもうちょっとわかるようになってからでいいや。
割愛!

セミコロン

いろいろあるけど、全部の行にセミコロンはいらないよ。と理解。
次!

hello.goの解説

チュートリアルのhello.goの下になんだか書いてありました。

Every Go source file declares, using a package statement, which package it's part of. It may also import other packages to use their facilities. This program imports the package fmt to gain access to our old, now capitalized and package-qualified, friend, fmt.Printf.

Functions are introduced with the func keyword. The main package's main function is where the program starts running (after any initialization).

String constants can contain Unicode characters, encoded in UTF-8. (In fact, Go source files are defined to be encoded in UTF-8.)

The comment convention is the same as in C++:

/* ... */
// ...

Later we'll have much more to say about printing.


* fmtって言うパッケージを読むとPrintが使えるんだぜ
* 関数は func で始めるんだぜ
* 文字列はUTF-8なんだぜ
* コメントはC++と一緒なんだぜ
* Printはあとでみっちり解説するから覚悟しとけ
って書いてあると思います。

どれもこれも見たら明らかですね。
一見当たり前のことを当たり前に解説するって言うところを押さえるあたり、
すごくよくわかって書いてるんだなーとそこは安心感。
ぼちぼちいきますよ。

hello.go

こんばんわ。

ようやくインストール/コンパイルしたGoを動かしてみることにしましょう。
最初で何にもわかりませんので、とりあえずはお約束のhellow worldでもやってみましょう。

チュートリアルの最初のページにこんな例題が出ています。


package main
import fmt "fmt"
func main() {
fmt.Printf("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n")
}


これを hello.goで保存して

$ 8g hello.go
$ 8l hello.8

でコンパイルとリンク。
エラーがあるときだけエラーが出ます。実行すると

$ ./8.out
Hello, world; or Καλημέρα κόσμε; or こんにちは 世界


はい、動きました。

これだけで何かわかる訳ではないですし
いろんな方が分析されていますがそういうのは基本的に他に譲ります。
個人的にはpythonとかC++ぽいC言語なのかな、と思いました。
Printの先頭が大文字なのはちょっと気持ち悪いですけど
まあそんなもんでしょう。

コンパイルが速いのはうれしいです。
むかし8bitのマイコン(パソコンの古代名)にCのコンパイラを入れて
わくわくhello world入力して、翻訳だと信じて疑わなかったコンパイルをかけたら
あら不思議、30分ぐらいディスクアクセスしっぱなしで
泣きそうになったことをしみじみと思い出しました。
コンパイラは15000円ぐらい出して買ったんだと思います。
30分は大げさかもしれませんが。。よく覚えていません。
結局BASICに戻った懐かしいあの頃。

お約束でチュートリアルでもやりましょうかね。

Google goをインストール

こんばんわ。

Googleがこないだジョリジョリ言ってたGoogle-goをインストールからやりました。

11月に発表されてから、主にpythonの開発者がなだれこんだり、
GCCがサポートするって言ったり、
当初発表した文法をこそっと変更したり、
なんだかいろいろあったのが先月ぐらいようやく落ち着いたようです。

それでそろそろあそんでみようかな、と。


インストールはインストール方法に書かれている通り、
Mercurialでダウンロードしてgccでコンパイルするだけです。
理想的です。
パスの設定とかはまあお約束通りやってください。

通常どおりコンパイルして、勝手にテストが走って、終了なのですが
少なくともMacOSXではテストでエラーが出ます。
対処方法はGoを試す - fukuitの日記さんとかを参考に
$GOROOT/src/pkg/Makefileを修正します。
NOTEST=\
のとこにhttpとnetを追加して

NOTEST=\
debug/proc\
go/ast\
go/doc\
go/token\
hash\
image\
image/jpeg\
rand\
runtime\
syscall\
testing/iotest\
xgb\
http\
net\

みたいにすればOkです。

コンパイルすると
$ ./all.bash
全部略
--- cd ../test
0 known bugs; 0 unexpected bugs
みたいに出たら終了です。
$GOBIN、通常は~/binに8a 8c 8l 8g gofmt などの実行ファイルができます。


早速もいちど実行しようとおもったけど時間の都合で今日はここまで。

とうこうてすると

じょびじょb

$ cat >hello.go <