id:JAPLJと二人でGoを勉強しました@パソコン甲子園

俺達のパソコン甲子園は終わりました。賞金もらえるといいな。

会津若松ワシントンホテルに泊まりました。インターネットが使えます。幸せ。

まだ日付は越していませんが、昨日の敵は今日のダチです。ということでid:JAPLJを無理に誘い、Goを勉強しました。

インストール

早速 Go 言語を試してみる! - IT戦記
うちの環境の場合

export GOOS=linux
export GOARCH=386

GOARCHが386なのでコンパイラの名前も6gではなく8gとかになります。

名前の由来

眺めてたら発見したのですが、GoのデバッガはOgleって言うんですね。

構文

ブロックの波括弧は省略できなくなったようです。

whileとforとforeachとwhile(true)は全て「for」で書きます。

for {}//while(true)
for i < j {}//while(i<j)
for k,v := range m {}//foreach(k,v in m)
for i := 0; i < 100; i++ {}//おなじみの

括弧が無くなって非常に不思議な感じです。

ifも何だか難しくなった。

if i < j {}//おなじみ
if {}//trueになるらしい?
if i := f(); i < j {}//i<jの評価前に実行される文

変数宣言

型が後になるのが特徴的です。
変数宣言

var variable type;

変数の代入と宣言を同時に行う。

variable := expression;

「変数 型」という記法と「総称型 型引数」という記法は類似していていいと思います。

var out chan int;//chan int型
var ary []int;//[]int型

関数

書きかたが微妙にJavaScriptっぽい。

func nanika(a int,b int) {}//普通の関数
func(a int, b int) {}//無名関数
func are(a int, b int) int {}//戻り値の型の指定
func (self *Bar) clone() *bar {}//インスタンスメソッド

goroutine

channelは、同期機能つきQueue。
goは非同期実行。

UNIXに例えると、channelはパイプライン、goはforkに近いように感じました。

goで分離したルーチンは非同期である、つまりgoの後の処理との順序を入れかえてもよい、という指示になります。ただそれだけではだめなので、同期のための仕組みとしてchannelを使います。

関数型言語に例えると、遅延評価に非常に近いと思いました。関数型において遅延評価が可能なのは副作用が生じないという保証があるからです。Goにおいてはgoとchannelを用いて局所的に保証を行うことで、手続き的な処理の中で似たようなことを実現しているわけです。遅延評価に例えるなら、channelは遅延リストっぽいです。

ここで、goの内側の処理をchannelから要求がないうちは行わないようにすると遅延評価になるのですが、先行評価もできるようにするとこれが並列化になるわけですね。

そんなわけで、goroutineの練習をするなら、遅延評価っぽいことをすればいいと思います。

成果物

結局primesを書きました。

package main

import "fmt"

func filter(in chan int, prime int) chan int {
    out := make(chan int);
    go func() {
        for {
            num := <-in;
            if(num % prime != 0) {
                out <- num
            }
        }
    }();
    return out;
}


func primes() chan int {
    out := make(chan int);

    go func() {
        nums := func() chan int {
            nums := make(chan int);
            go func() {
                for i := 2; ; i++ {
                    nums <- i;
                }
            }();
            return nums;
        }();
        for {
            prime := <-nums;
            out <- prime;
            nums = filter(nums, prime);
        }
    }();
    return out;
}

func main() {
    p := primes();
    for i := 0 ; i < 100 ; i++ {
        fmt.Printf("%d\n", <-p)
    }
}

filterは与えられたchannelからprimeで割りきれないものを抽出します。
2以上の整数を列挙するchannelであるnumsを生成して、
「numsの先頭をとりprimeとし、outに挿入する。numsからprimeで割りきれないものを抽出し、新たにnumsとする。」
という作業をする感じです。

うーむ、かっこいい。