ラベル go の投稿を表示しています。 すべての投稿を表示
ラベル go の投稿を表示しています。 すべての投稿を表示

日曜日, 12月 27, 2015

LLDBでGoのデバッグをする その2 test編

その1ではgo buildしたLLDBでデバッグしてみましたが、その2ではgo testでのdebugの仕方です。

今回のデバッグ対象として以下のようなプログラム(main.go)とそのテスト(main_test.go)を書きました。

package main

import "fmt"

func RectArea(w, h int) int {
    return w * h
}
package main

import "testing"

func TestRectArea(t *testing.T) {
    s := RectArea(6, 8)
    if s != 48 {
        t.Error("error")
    }
}

testをビルドする

go test には -c というオプションがあり、これを使うとtestのバイナリをビルドできます。これと -gcflags を組み合わせてデバッグ可能なtestバイナリを作ります。

# -o は出力結果のファイル名を指定するオプション
$ go test -c -gcflags '-N -l' -o test
# 実行するとgo testと同じ結果が得られる
$ ./test
PASS

デバッグする

後は、前回同様lldbに、このtestバイナリを渡すだけです。

$ lldb test 
(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb)

せっかくなのでプログラム本体とテスト両方にブレイクポイントを置いてみます。

(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb) b main.go:4
Breakpoint 1: where = test`_/Users/kmtr/misc/c.RectArea + 9 at main.go:6, address = 0x000000000007dc09
(lldb) b main_test.go:7
Breakpoint 2: where = test`_/Users/kmtr/misc/c.TestRectArea + 58 at main_test.go:7, address = 0x000000000007dc5a
(lldb)

後は run するだけです。
main.go側で止まったところで、変数を確認。

   3    func RectArea(w, h int) int {
   4        return w * h
   5    }
(lldb) fr v
(long) w = 6
(long) h = 8
(long) ~r2 = 0
(lldb)

c で継続して、次はmain_test.goで止まるので、こちらでも変数を確認。
そして c で終了させます。

   4    
   5    func TestRectArea(t *testing.T) {
   6        s := RectArea(6, 8)
-> 7        if s != 48 {
   8            t.Error("error")
   9        }
   10   }
(lldb) fr v
(long) s = 48
(testing.T *) t = 0x000000c82009c000
(lldb) c
Process 25022 resuming
PASS
Process 25022 exited with status = 0 (0x00000000) 
(lldb)  

まとめ

-c オプションでtestバイナリを作るのがポイントで、後は前回と同じですね。

また続くかもしれない。

土曜日, 12月 26, 2015

LLDBでGoのデバッグをする その1

環境

環境は以下の通りです。

  • OS X El Capitan 10.11.2
  • lldb-340.4.119
  • go version go1.5 darwin/amd64

LLDBはxcodeについてきたものです。

やってみる

デバッグ対象のコードは、やっつけですがこんな感じです。ファイル名はmain.goとしました。

package main

import "fmt"

func main() {
   a := 10
   fmt.Printf("Add1(10) == %d\n", Add1(a))
}

// Add1 ...
func Add1(i int) int {
   return i + 1
}

LLDBでデバッグするために、-gcflags '-N -l' というオプションをつけてビルドします。

$ go build -gcflags '-N -l' main.go
$ ls
main    main.go

lldbコマンドに作成されたファイルを渡してみます。

$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) 

ここから先はlldbの操作になります。とりあえず q と打つとlldbから抜けられることだけは覚えてください。

デバッグを開始するには run または r と打ちます。

(lldb) run
Process 22044 launched: '/Users/kmtr/misc/c/main' (x86_64)
Add1(10) == 11
Process 22044 exited with status = 0 (0x00000000) 
(lldb)  

無事実行されました。
これではデバッグにならないので、ブレイクポイントを打ちます。
試しに6行目と12行目に打ってみます。ブレイクポイントを打つ方法はいくつかありますが、行番号指定の場合 b ソースファイル名:行番号 が楽です。

(lldb) b main.go:6
Breakpoint 1: where = main`main.main + 31 at main.go:6, address = 0x000000000000205f
(lldb) b main.go:12
Breakpoint 2: where = main`main.Add1 + 9 at main.go:12, address = 0x0000000000002199
(lldb) 

br list もしくは b と打つと、現在のブレイクポイントの一覧が表示されます。
br delete ブレイクポイント番号 で対象のブレイクポイントを削除できます。
2番のブレイクポイントを消した後、再度ブレイクポイントを一覧表示して削除されることを確認してみてください。

ブレイクポイントを打った状態で再び run すると、こんな感じに止まります。

   3    import "fmt"
   4    
   5    func main() {
-> 6        a := 10
   7        fmt.Printf("Add1(10) == %d\n", Add1(a))
   8    }
   9    
(lldb)      

ステップオーバーは n です。打ってみるとカーソルが7行目に移ると思います。fr v 変数名 で変数の中身を表示できるので打ってみます。

(lldb) fr v a
(long) a = 10
(lldb) 

なぜかlongですが、とりあえずの確認には十分です。
現在のブレイクポイント周辺のソースを再表示する場合は f と打ちます。

ソースレベルでのステップインは s です。7行目で実行すると次のようにAdd1関数の中に入ると思います。

   9    
   10   // Add1 ...
   11   func Add1(i int) int {
-> 12       return i + 1
   13   }
(lldb) 

fr v と変数名を指定せずに打つと、現在のフレームの変数が全て表示されます。

(lldb) fr v
(long) i = 10
(long) ~r1 = 0
(lldb) 

(~r1 てなんだろう、、、)

c と打つと、次のブレイクポイント、もしくは終了するまで停止せずに実行されます。

(lldb) c
Process 22103 resuming
Add1(10) == 11
Process 22103 exited with status = 0 (0x00000000) 
(lldb) 

LLDBの使い方は、The LLDB Debugger のgdbとの比較がわかりやすいです。
watchが効かないという話も見かけましたが、少なくとも私の環境では問題ありませんでした。

感想

少ししか使っていないので、どこまでちゃんと動くのか、GDBとの差がどれだけあるかわからないけど、LLDBでもデバッグはできそうです。

続くかもしれない。

水曜日, 5月 13, 2015

Goで書かれたHTTP2対応サーバーCADDYを試す

CADDYというHTTP2対応サーバーを見つけたので簡単に使ってみる。

https://caddyserver.com/

Goで書かれていて、インストールは各プラットホーム毎のバイナリを適当に配置するだけなので、インストールが楽。

起動する

とりあえずPATHが通っているところに置いて叩いてみる。

$ caddy
0.0.0.0:2015

デフォルトポートが2015なのだけど、来年になったら2016になるのだろうか。

curlで叩いてヘッダを見てみる。

$ curl -I localhost:2015
HTTP/1.1 404 Not Found
Server: Caddy
Date: Tue, 12 May 2015 13:52:04 GMT
Content-Length: 13
Content-Type: text/plain; charset=utf-8

404だけど、とりあえず動いている。HTTP2はHTTPSじゃないと動かないので、HTTP/1.1なのが残念。
(CADDYのサイトはCADDYでホストされていて、HTTP2が使われていることをブラウザで確認しています)

設定を変える

CADDYの設定ファイルは-confオプションで任意のファイルを指定出来るけど、デフォルトではコマンドを叩いたディレクトリにあるCaddyfileを見るらしい。
1行目には必ずlistenするアドレスを書けということなので、ポートをデフォルトから変更したものを記述してみる。

$ echo "localhost:2016" > Caddyfile
$ caddy
localhost:2016

無事ポート2016で起動した。
ちなみに1行目と言うのは、設定値としての1行目で、コメントは普通にかける。コメントはapacheと同様、#から始まる文字列。

静的ファイル配信

このままでは404を返すだけのサーバーなので、とりあえず静的ファイルを返すようにしてみる。
まずはドキュメントルートとなるディレクトリを作り、適当なHTMLファイルを置く。

$ mkdir public_html
$ echo "Hello, world" > public_html/index.html

次にCaddyfileをこんな感じに編集する。
rootはドキュメントルートを指定するディレクティブ。カッコでくくらずに、そのまま次の行に書いてもよいのだけど、書いた方が分かりやすいと思う。

# example
localhost:2016 {
    root ./public_html/
}

再起動して、curlしてみる。

$ curl localhost:2016
Hello, world

無事、静的ファイルを配信出来た。

markdown配信

リダイレクトやプロキシといった機能の他に、CADDY特有の機能がいくつかある。その一つのmarkdown配信機能を使ってみる。

設定はmarkdownディレクティブの後ろに、適用するPATHを記述する。このPATHはリクエストのパスであると同時にmarkdownファイルを置くディレクトリの事も示すらしい。
つまりmarkdownファイルは、public_html/blogではなくblogというディレクトリに置く必要がある。

# example
localhost:2016 {
    root ./public_html/
    markdown /blog
}

次に記事を作る。

$ mkdir blog
$ echo "# Hello, markdown" > blog/hello.md 

curlで確認。

$ curl localhost:2016/blog/hello.md
<!DOCTYPE html>
<html>
    <head>
        <title># Hello, markdown</title>
        <meta charset="utf-8">


    </head>
    <body>
        <h1>Hello, markdown</h1>

    </body>
</html>

ソースを確認したところ、markdownの処理には https://github.com/russross/blackfriday を使っていた。

性能

性能について、FAQには正確に答えるのは難しいとしながら、概ね速いと書いてある。
Apacheよりは速くて、nginxよりちょっと遅いぐらいという事なので、実用上十分な速さと思っておけば良さそう。
CADDYのコンセプトは設定が簡単なWebサーバなので、それでよいのだと思う。

感想

全部の機能を試したわけではないけど、1バイナリで完結して、設定が簡単に出来て、それなりに速いという使い勝手の良いサーバーという印象。
ちょっとしたサーバーが欲しいのだけど、Apacheやnginxだと大げさ過ぎるとか、設定が面倒という時に重宝しそう。

あとはfcgiを使ったプロジェクトのルートディレクトリにCaddyfileを置いといて、git cloneしてcaddyと叩いたら開発環境として立ち上がるみたいな使い方とか。

小回りが効くので、応用範囲が広そう。

水曜日, 4月 29, 2015

Goのtesting/quickを簡単に触ってみる

Goのpackageを眺めていたら、いまさらtesting/quickというものを見つけたので試してみる。
quickの説明をみると、

help with black box testing

と書いてあるし、Checkという関数があったりするので、QuickCheckのためのパッケージのはず。

普通のtest

まずはquickを使わないテスト。
2つのintで割り算をする関数のテスト。

package main

import "testing"

func div(n, m int) int {
    return n / m
}

func TestDiv(t *testing.T) {
    if div(4, 2) != 2 {
        t.Error("error!")
    }
    if div(3, 2) != 1 {
        t.Error("error!")
    }
}

明らかにテストケースが不足してる。
とりあえず第二引数に0を入れたら、確実にエラーになるはずだけど、このケースでは考慮されていない。

quick導入

というわけでquickを導入。

package main

import (
    "fmt"
    "testing"
    "testing/quick"
)

// テスト回数計測のためのカウンター
var count int = 0

func div(n, m int) int {
    count++
    return n / m
}

func TestDiv(t *testing.T) {
    f := func(x, y int) bool {
        return div(x, y) == (x / y)
    }
    if err := quick.Check(f, nil); err != nil {
        t.Error(err.Error())
    }
    fmt.Printf("テスト回数:%d\n", count)
}

これでgo testしてみる。

$ go test
テスト回数:100
PASS
ok      quick_example   0.005s

通ってしまった。
quickというかQuickTestは要はとにかくいろいろ試してみるテストなので、100回程度では通ってしまうのも仕方ない。
今回の場合、第二引数に0が入るのは 「1/intの取りうる値」 なので、試行回数が全然足りない。

試行回数を変える

ということで試行回数を増やしてみる。
試行回数の設定をするには、quick.Configのインスタンスを作って、
MaxCountに試行回数を設定する。

package main

import (
    "fmt"
    "testing"
    "testing/quick"
)

var count int = 0

func div(n, m int) int {
    count++
    return n / m
}

func TestDiv(t *testing.T) {
    f := func(x, y int) bool {
        return div(x, y) == (x / y)
    }

    config := &quick.Config{
        MaxCount: 10000,
    }

    if err := quick.Check(f, config); err != nil {
        t.Error(err.Error())
    }
    fmt.Printf("テスト回数:%d\n", count)
}

そしてgo testする。

$ go test
テスト回数:10000
PASS
ok      quick_example   0.015s

通ってしまった。
math.MaxInt64を突っ込めば確実に失敗するのだろうけど、
それは最早ブラックボックステストではない。
(ちなみにMaxInt64でやってみたら、手元のマシンでは3分経っても終わらなかった)

テスト値を制御する

とはいえ、0ぐらい入れて欲しい。
ブラックボックステストと言っても、本当に無作為に値を突っ込むのではなく、とりあえず0から100と、0から-100で動いてもらえば最低限の仕様は満たすという事はままある。
そのように、自分でランダム性を制御するにはconfig.Valuesに値を生成する関数を設定する。

値を生成する関数はfunc(args []reflect.Value, rand *rand.Rand)というシグネチャを持つ。
argsには、quick.Check関数の第一引数に渡す関数の引数の数のサイズを持つスライスが渡されてくる。

今回のケースで、0から100の範囲の値を無作為に選ばせるのであれば、こんな感じの関数をconfig.Valuesに渡せば良い。

func(args []reflect.Value, rand *rand.Rand) {
    args[0] = reflect.ValueOf(rand.Intn(100))
    args[1] = reflect.ValueOf(rand.Intn(100))
}

これで10000回くらい回せば、流石にargs[1]に0が入るはず。

第二引数のrandは見ての通りRandインスタンスが渡ってくる。このRandインスタンスはconfig.Randで設定しないとデフォルトのものが使われるのだけど、go 1.4.2ではデフォルトのrandはseedが0固定されているので、変えないと毎回同じ結果になる(デフォルトのseed=0は意図したものなのか?)。

上記を踏まえるとこんな感じのソースになる。

package main

import (
    "math/rand"
    "reflect"
    "testing"
    "testing/quick"
    "time"
)

var count int = 0

func div(n, m int) int {
    count++
    return n / m
}

func TestDiv(t *testing.T) {
    f := func(x, y int) bool {
        return div(x, y) == (x / y)
    }

    config := &quick.Config{
        MaxCount: 10000,
        Rand:     rand.New(rand.NewSource(time.Now().UTC().UnixNano())),
        Values: func(args []reflect.Value, rand *rand.Rand) {
            args[0] = reflect.ValueOf(rand.Intn(100))
            args[1] = reflect.ValueOf(rand.Intn(100))
        },
    }

    if err := quick.Check(f, config); err != nil {
        t.Error(err.Error())
    }
    fmt.Printf("テスト回数:%d\n", count)
}

これでgo testすると、まず確実にこんなかんじで0除算エラーが発生する。めでたしめでたし。

— FAIL: TestDiv (0.00s)
panic: runtime error: integer divide by zero [recovered]
panic: runtime error: integer divide by zero

まとめ

今回はquickを使うというのが目的なので、こんな感じのテストコードになったけど、実際には普通のテスト(=ホワイトボックステスト)と組み合わせて使うはず。
ブラックボックステストはホワイトボックスを置き換えるものではないし。
といっても、特に難しく考えず、とりあえず値を大量に作ってくれる装置として使うだけでも恩恵があるかと。今回のように完全な無作為ではなく、そこそこの無作為にしてグレーボックスとして使ったりとか。

quick固有の問題として、デフォルトのRandがseed=0で初期化されているという点だけは要注意ですね。

水曜日, 12月 24, 2014

bingから画像をダウンロードしてMacの壁紙に設定するツールをGoで書いた

bingから画像をダウンロードしてMacの壁紙に設定するbinggoというツールをGoで書きました。

https://github.com/kmtr/binggo

go get github.com/kmtr/binggoでインストール出来ます。

なぜ書いたのか

私はbingのトップページの画像が大好きで、Windowsの壁紙はBingデスクトップを使って、毎日bingの画像が設定されるようにしています。 ですが、残念ながらBingデスクトップのMac版は存在していません。 無いのであれば作るしかないということで、書いてみました。 ちなみに同じことを考える人はそれなりにいるようで、Pythonやbash、Automatorによる同様のツールがすでに存在しています。 ただインストールが面倒であったり、私の環境での動作が不安定だったりと不満があったので、自分で書いてみました。

なぜGoなのか

Goが好きだからというのが最大の理由です。

前々からGoには少し触っていたのですが、Go Conference 2014 autumnに参加して自分の中のGo熱が高まり、何か書きたいけど丁度良いネタが無いかと考えていたところ、bingに「今日の画像をダウンロード」という機能が追加され、Goで書いてみようと思いつきました。 ただ機能が単純なのでchannelどころかgoroutineすら使わなかった(使うまでもなかった)のが少し残念でした。

おまけ

bingの過去画像が欲しい場合は、Bing Homepage Galleryが便利です。

水曜日, 9月 14, 2011

devquiz 2011 Go!の解答

devquizのGo!の問題で提出した解答。
問題文は以下の通り。
Go 言語で、PNG 画像を入力として受け取り、その画像が何色使っているかを返す関数

func CountColor(png io.Reader) int
を実装してください。PNG 画像は io.Reader 型で与えられます。
なお、入力の画像は R G B の各色の値が 0 から 255 までの 256 段階のいずれかであり、不透明(アルファチャンネルの値が常に 255)であることが保証されています。

答えとして送ったもの(ただし変数名は多少整理した)。素直にスキャンしているだけです。
package main

import (
 "fmt"
 "io"
 "strings"
 "image"
 "image/png"
)

func CountColor(r io.Reader) int {
 colormap := map[uint32]int{}
 image, _ := png.Decode(r)
 bounds := image.Bounds()
 w, h := bounds.Max.X, bounds.Max.Y
 var colorNum uint32 = 0
 for i := 0; i < w; i++ {
  for j := 0; j < h; j++ {
   colorNum = colorCheck(image.At(i, j))
   colormap[colorNum] = colormap[colorNum] + 1
  }
 }
 return len(colormap)
}

func colorCheck(color image.Color) uint32 {
 r, g, b, _ := color.RGBA()
 var num uint32
 num = uint32(uint8(r))
 num = num << 8
 num = num + uint32(uint8(g))
 num = num << 8
 num = num + uint32(uint8(b))
 return num
}

/* これらの関数は提出時に自動挿入されます。 */
func main() {
 png := GetPngBinary()
 cnt := CountColor(png)
 fmt.Println(cnt)
}

func GetPngBinary() io.Reader {
 // img_strの中身は提出するたびに変化します。
 // 省略
}

以下は、後から考えたgoroutine使用版。

package main

import (
 "fmt"
 "io"
 "strings"
 "image"
 "image/png"
)

func CountColor(r io.Reader) int {
 colormap := map[uint32]int{}
 img, _ := png.Decode(r)
 bounds := img.Bounds()
 w, h := bounds.Max.X, bounds.Max.Y
 result := make(chan uint32)
 go colorCheck(img, w, h, result)
 for i := 0; i < w*h; i++ {
  colormap[<-result]++
 }
 return len(colormap)
}

func colorCheck(img image.Image, w, h int, result chan uint32) {
 var num uint32
 for x := 0; x < w; x++ {
  for y := 0; y < h; y++ {
   r, g, b, _ := img.At(x, y).RGBA()
   num = uint32(uint8(r))
   num = num << 8
   num = num + uint32(uint8(g))
   num = num << 8
   num = num + uint32(uint8(b))
   result <- num
  }
 }
}

/* これらの関数は提出時に自動挿入されます。 */
func main() {
 png := GetPngBinary()
 cnt := CountColor(png)
 fmt.Println(cnt)
}

func GetPngBinary() io.Reader {
 // img_strの中身は提出するたびに変化します。
 // 省略
}
goっぽさという点ではgoroutine版のが良いのだろうけど、いきなりこれを書くのは今の私には無理でした。

改めて見ると、後者ではImageをスキャンしている部分(colorCheck)と、色数を数える部分(CountColor)が分離出来ているので、すっきりしているかも。

土曜日, 11月 20, 2010

gofmtをvimで簡単に使うためのスクリプト

Go言語は、推奨コーディングスタイルがあります。
とはいえ、実際に書くときにはあまり細かく気にする必要はなく、gofmtというGo言語をインストールするとついてくるフォーマッタを通してやれば、推奨スタイル通りに整形してくれます。
これを随時実行したいので、vimスクリプトを書いてみました。
都合上、フォーマットをかける前に、ファイルを保存します。

function! Gofmt()
:w
python << EOF
import vim
import subprocess

cb = vim.current.buffer
cursor = vim.current.window.cursor
p=subprocess.Popen(
['gofmt', cb.name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True)
returncode=p.poll()
if not returncode:
err = p.stderr.read()
if err != None and err == '':
del cb[:]
cb.append(p.stdout.readlines(), 0)
else:
print(err)
vim.current.window.cursor = cursor
EOF
endfunction

水曜日, 11月 10, 2010

gocode入れてみた

VimでGoがautocomplete出来るgocodeがいい感じ」を見て、gocodeを入れてみた。

ビルドやインストールについては、gocodeのサイトに書いてあって、特に引っかかるところはない。
ただ、その説明だけでは、gvimでは動作しなかったので、gvimrcに以下の行を追加した。

let $GOBIN="$HOME/bin/go/"
let $GOROOT="$HOME/src/go/"
let $PATH="$PATH:$HOME/bin/go"

火曜日, 11月 09, 2010

Gordonさまがマウスカーソルにくっつく

多少書き直してみた。ついでに分身の術ではなく、マウスカーソルにくっつくように動くようにした。
コールバックな部分は若干やっつけだけど、基本的にはこんな感じだと思う。
event.goは綺麗というかすっきりして、ここまで来たらファイルを分離する意味が無い。
その分、main.goのhandleMouseEventがやたら汚い。
interface{}を具体的な型に落とす方法はswitchしかないのかな。

(一度投降後、何度かコードを綺麗にしてます)

main.go

package main

import (
"fmt"
"exp/draw"
"exp/draw/x11"
"image"

"./event"
"./gordon"
)

func handleMouseEvent(event interface{}, args ...interface{}) {
switch e := event.(type) {
case draw.MouseEvent:
{
me := draw.MouseEvent(e)
switch win := args[0].(type) {
case draw.Window:
{
win = draw.Window(win)
switch char := args[1].(type) {
case gordon.Character:
{
char = gordon.Character(char)
fmt.Printf(
"MouseEvent: Button= %d, Loc= %v\n",
me.Buttons, me.Loc)

draw.Draw(win.Screen(), char.Canon(), char.Mask(), image.ZP)
char.Position(me.Loc.X, me.Loc.Y)
draw.Draw(win.Screen(), char.Canon(), char, image.ZP)
win.FlushImage()
}
}
}
}
}
}
}

func main() {
gordon1, e := gordon.NewGordon(image.Point{X: 0, Y: 0})
if e != nil {
fmt.Printf("Error %v\n", e)
return
}

win, e := x11.NewWindow()
if e != nil {
fmt.Printf("Error %v\n", e)
return
}

eventChan := win.EventChan()
mouseCallBack := event.CallBack(handleMouseEvent)
sync := make(<-chan bool)
go event.HandleCallBack(eventChan, mouseCallBack, win, gordon1)
<-sync
}

event.go

package event

type CallBack func(event interface{}, args ...interface{})

func HandleCallBack(eventChan <-chan interface{}, fun CallBack, args ...interface{}) {
for {
event := <-eventChan
go fun(event, args...)
}
}

gordon.go

package gordon

import (
"bufio"
"image"
"image/png"
"os"
)
os.DevNull

type Character interface {
image.Image
Canon() image.Rectangle
Mask() image.Image
Position(x, y int)
}

type Gordon struct {
image.Image
Rect image.Rectangle
maskImage image.Image
}

func (g *Gordon) Position(x, y int) {
r := g.Bounds()
g.Rect = image.Rect(
x, y,
x+r.Max.X, y+r.Max.Y)
}

func (g *Gordon) Canon() image.Rectangle {
return g.Rect.Canon()
}

func (g *Gordon) Mask() image.Image {
return g.maskImage
}

func NewGordon(point image.Point) (gordon *Gordon, e os.Error) {
gordon = new(Gordon)
img, e := loadGordon()
if e != nil {
return nil, e
}
gordon.Image = img
gordon.Position(point.X, point.Y)
gordon.maskImage = image.NewGray(img.Bounds().Max.X, img.Bounds().Max.Y)
return gordon, e
}

func loadGordon() (image.Image, os.Error) {
file, e := os.Open("./Gordon.png", os.O_RDONLY, 0666)
if e != nil {
return nil, e
}

read := bufio.NewReader(file)
icon, e := png.Decode(read)
if e != nil {
return nil, e
}
return icon, nil
}

月曜日, 11月 08, 2010

Gordonさまと遊んでみて

とりあえず、自分で見ても滅茶苦茶なソースだと思う。
Interfaceなんて使ってみたかっただけで、無理やり過ぎる。
そもそも書いてある場所がどう考えてもおかしい。
コールバック的なものは関数型を渡してやればいいんだろうけど、何故かうまくいかなかった。
基本的な文法で間違っている可能性大。

そんな適当ながらも、書いてみてのGoの感想。

1. interfaceにFieldを宣言出来ないのは若干不便
Javaで言えばpublicなFieldの値を取りたいだけなのに、関数が必要になるのは不便。
要するにsetter/getterもインターフェースに書く必要があるわけで。
とはいえinterfaceてのはそういうものなのかも

2. ifの括弧が無いのは楽
慣れると他の言語で括弧を書くのを忘れる。

3. 型の後置は慣れると気にならない。
たまに間違うけど

4. 出来上がるバイナリがでかい
パソコンで動かしていると大して気にならないけど。もっと貧弱な環境だと大変かも。

CにもPythonにも似ていないけど、慣れれば便利な予感。

日曜日, 11月 07, 2010

GoでGordonさまと遊ぶ

Go言語のマスコットのGordonさまは、みんなから気持ち悪いと言われていますが、じっと見ていると、かわいいところが無く無くない気がしてきました。

なので、ちょっとコードが整理されていませんが、GoでGordonさまをいっぱい並べられるものを作ってみました。
コールバックな関数はどう書けばいいんだろう。

main.go

package main

import (
"fmt"
"exp/draw/x11"
"image"

"./event"
"./gordon"
)


func main() {
gordon1, e := gordon.NewGordon(image.Point{X: 0, Y: 0})
if e != nil {
fmt.Printf("Error %v\n", e)
return
}

win, e := x11.NewWindow()
if e != nil {
fmt.Printf("Error %v\n", e)
return
}

eventChan := win.EventChan()
sync := make(<-chan bool)
go event.Handle(eventChan, win, gordon1)
<-sync

}


gordon.go

package gordon

import (
"bufio"
"image"
"image/png"
"os"
)

type Gordon struct {
image.Image
Rect image.Rectangle
}

func (g *Gordon) Position(x, y int) {
r := g.Bounds()
g.Rect = image.Rect(
x, y,
x+r.Max.X, y+r.Max.Y)
}

func (g *Gordon) Canon() image.Rectangle {
return g.Rect.Canon()
}

func NewGordon(point image.Point) (gordon *Gordon, e os.Error) {
gordon = new(Gordon)
img, e := loadGordon()
if e != nil {
return nil, e
}
gordon.Image = img
gordon.Position(point.X, point.Y)
return gordon, e
}

func loadGordon() (image.Image, os.Error) {
file, e := os.Open("./Gordon.png", os.O_RDONLY, 0666)
if e != nil {
return nil, e
}

read := bufio.NewReader(file)
icon, e := png.Decode(read)
if e != nil {
return nil, e
}
return icon, nil
}


event.go

package event

import (
"exp/draw"
"fmt"
"image"
)

type Character interface {
image.Image
Canon() image.Rectangle
Position(x, y int)
}

func Handle(eventChan <-chan interface{}, win draw.Window, char Character) {
for {
event := <-eventChan
switch e := event.(type) {
case draw.MouseEvent:
{
me := MouseEvent(e)
go me.handleMouseEvent(win.Screen(), char)
}
case draw.KeyEvent:
{
ke := KeyEvent(e)
go ke.handleKeyEvent(win.Screen(), char)
}
}
win.FlushImage()
}
}


type MouseEvent draw.MouseEvent
type KeyEvent draw.KeyEvent

func (me *MouseEvent) handleMouseEvent(screen draw.Image, char Character) {
fmt.Printf("MouseEvent: Button= %d, Loc= %v : %v\n", me.Buttons, me.Loc, char)
char.Position(me.Loc.X, me.Loc.Y)
draw.Draw(screen, char.Canon(), char, image.ZP)
}

func (ke *KeyEvent) handleKeyEvent(screen draw.Image, char Character) {
fmt.Printf("KeyEvent: Key= %d\n", ke.Key)
}


実行結果