ずっと君のターン

2011-10-04 アキバれ

Go Google+ API

| 22:19 | Go Google+ API - ずっと君のターン を含むブックマーク

http://developer.mixi.co.jp/connect/mixi_graph_api/mixi_io_spec_top/examples/

mixi Developer Centerでmixi Graph APIを実行するいろんな言語のサンプルが公開されています。されていますっていうか、しましたっていうか。とにかくすばらしいですね。

で、これはmixi Graph APIのサンプルなんですが、ぶっちゃけOAuthで認可するRestfulなAPIはmixiに限らずどれもだいたい同じようなものです。

ということでmixi Graph APIサンプルのGo言語版を元にGoogle+のAPIを実行できるようにしてみました。

変更点はだいたい以下の四点。

  • エンドポイントの変更
  • 出力フォーマットが違うのでとりあえず生JSON表示
  • grant_type=refresh_tokenで新しいrefresh_tokenをもらえないみたいなので、一度取得したらそれをずっと使う
  • Google+のエンドポイントはHTTPSなのでnet.Dialの代わりにtls.Dialを使用

あんまり変更してないですね。それでは実行してみます。

$ 6g plus.go
$ 6l plus.6
$ ./6.out
Please open http://localhost:8008 on a web browser.

結果。

f:id:technohippy:20111004133215p:image

ということで無事に実行できました。ありがとうmixi。ありがとう自分。

// plus.go
package main

import (
  "crypto/tls"
  "fmt"
  "http"
  "io/ioutil"
  "json"
  "log"
  "os"
  "strings"
  "template"
)

type tokens struct {
  AccessToken string
  RefreshToken string
}

const (
  AUTHORIZE_URL_BASE = "https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https%%3A%%2F%%2Fwww.googleapis.com%%2Fauth%%2Fplus.me&client_id=%v&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A8008%%2Foauth2callback"
  TOKENS_ENDPOINT = "https://accounts.google.com/o/oauth2/token"
  PEOPLE_ENDPOINT= "https://www.googleapis.com/plus/v1/people/112986010883461255400"
  CONFIG_FILENAME = "config.json"
  TOKENS_FILENAME = "tokens.txt"
  TEMPLATE = ` 
<html>
  <head><title>People API</title></head>
  <body>
    <pre>{Response}</pre>
  </body>
</html>
`
)

var config map[string]string
var currentTokens *tokens

func NewTokens(accessToken string, refreshToken string) (*tokens) {
  return &tokens{accessToken, refreshToken}
}

func RestoreTokens() (*tokens) {
  if bytes, err := ioutil.ReadFile(TOKENS_FILENAME); err == nil {
    pair := strings.Split(string(bytes), "\n", -1)
    return &tokens{pair[0], pair[1]}
  }
  return nil
}

func (t *tokens)store() {
  ioutil.WriteFile(TOKENS_FILENAME, []byte(t.AccessToken +
    "\n" + t.RefreshToken), 0666)
}

func authorizeCode(authCode string) (err os.Error) {
  return updateTokens(map[string][]string{
    "grant_type":{"authorization_code"},
    "client_id":{config["client_id"]},
    "client_secret":{config["client_secret"]},
    "code":{authCode},
    "redirect_uri":{"http://localhost:" + config["redirect_port"] + "/oauth2callback"},
  })
}

func refreshToken() (err os.Error) {
  return updateTokens(map[string][]string{
    "grant_type":{"refresh_token"},
    "client_id":{config["client_id"]},
    "client_secret":{config["client_secret"]},
    "refresh_token":{currentTokens.RefreshToken},
  })
}

func updateTokens(params map[string][]string) (err os.Error) {
  println("Call: " + TOKENS_ENDPOINT)
  response, _ := http.PostForm(TOKENS_ENDPOINT, params)
  b, _ := ioutil.ReadAll(response.Body)
  println(string(b))

  var responseJson map[string]interface{}
  json.Unmarshal(b, &responseJson)
  if errorMessage, ok := responseJson["error"]; ok {
    return os.ErrorString(errorMessage.(string))
  } else {
    if responseJson["refresh_token"] == nil {
      currentTokens = &tokens{responseJson["access_token"].(string), currentTokens.RefreshToken}
    } else {
      currentTokens = &tokens{responseJson["access_token"].(string), responseJson["refresh_token"].(string)}
    }
    currentTokens.store()
    return nil
  }
  return os.ErrorString("access_token is null")
}

func oauthGet(accessToken string, urlString string) (*http.Response, os.Error) {
  url, _ := http.ParseURL(urlString)
  conn, _ := tls.Dial("tcp", url.Host + ":443", nil)

  clientConn := http.NewClientConn(conn, nil)
  header := map[string][]string {"Authorization":{"OAuth " + accessToken}}
  request := http.Request{Method:"GET", URL:url, Header:header}
  clientConn.Write(&request)
  return clientConn.Read(&request)
}

func getPeople() (result string, err os.Error) {
  println("Call: " + PEOPLE_ENDPOINT)
  response, _ := oauthGet(currentTokens.AccessToken, PEOPLE_ENDPOINT)
  if response.StatusCode == 401 {
    if err = refreshToken(); err == nil {
      return getPeople()
    }
    return "", err
  }
  b, _ := ioutil.ReadAll(response.Body)
  result = string(b)
  println(result)
  return result, nil
}

func redirect(writer http.ResponseWriter, request *http.Request, redirectUrl string) {
  println("Redirect to: " + redirectUrl)
  http.Redirect(writer, request, redirectUrl, http.StatusFound)
}

func handleFriendList(writer http.ResponseWriter, request *http.Request) {
  if request.RawURL == "/favicon.ico" { return }

  var (
    people string
    err os.Error
  )

  authorizeUrl := fmt.Sprintf(AUTHORIZE_URL_BASE, config["client_id"])
  parts := strings.Split(request.RawURL, "?code=", -1)
  if 2 <= len(parts) {
    if err = authorizeCode(parts[1]); err != nil {
      redirect(writer, request, authorizeUrl)
    } else {
      redirect(writer, request, "/")
    }
    return
  } else if currentTokens == nil {
    redirect(writer, request, authorizeUrl)
    return
  }

  if people, err = getPeople(); err != nil {
    redirect(writer, request, authorizeUrl)
  } else {
    params := new(struct{Response string})
    params.Response = people
    tmpl, _ := template.Parse(TEMPLATE, nil)
    tmpl.Execute(writer, params)
  }
}

func main() {
  var (
    bytes []byte
    err os.Error
  )

  if bytes, err = ioutil.ReadFile(CONFIG_FILENAME); err != nil {
    log.Fatal("ioutil.ReadFile:", err)
  }
  if err = json.Unmarshal(bytes, &config); err != nil {
    log.Fatal("json.Unmarshal:", err)
  }
  currentTokens = RestoreTokens()

  http.Handle("/", http.HandlerFunc(handleFriendList))

  addr := "localhost:" + config["redirect_port"]
  println("Please open http://" + addr + " on a web browser.")
  if err = http.ListenAndServe(addr, nil); err != nil {
    log.Fatal("http.ListenAndServe:", err)
  }
}
/* config.json */
{
    "client_id":"YOUR CLIENT ID",
    "client_secret":"YOUR CLIENT SECRET",
    "redirect_port":"8008"
}

なお、mixi Graph APIサンプルコード集には他にもVBAのサンプルとかあったりするので、その気になればExcelからもGoogle+ APIが実行できるはず。変態ですね。

2010-11-12 夜はわりとさむい

Go言語公開一周年

| 00:42 | Go言語公開一周年 - ずっと君のターン を含むブックマーク

Go言語が一歳になったらしいので記念にちょっと訳してみる。わりとヨサゲな言語になってると思うですよ。


http://blog.golang.org/2010/11/go-one-year-ago-today.html

Go: 一年前の今日

2009年11月10日、私たちはGoプロジェクト(単純さと効率に焦点を当てたオープンソースのプログラミング言語)を開始しました。この一年間でGoプロジェクト自身とそのコミュニティの両方に多くの発展が見られました。

私たちは当初システムプログラミング、典型的にはCやC++で書かれるような類のプログラム、のための言語を作成することを目指していましたが、驚いたことにGoは汎用的な目的に使用できる言語になりました。CやC++、Javaなどのプログラマの興味は得られるだろうとは思っていましたが、PythonやJavaScriptのような動的型言語のユーザーからこれほど支持を得られるとは思っていませんでした。ネイティブコンパイル、静的型付、メモリ管理、軽量な構文などが組み合わされたGo言語は幅広いプログラミングコミュニティの交わりの心を打ったようです。

その交わりは熱心なGoコーダーの献身的コミュニティに育ちました。私たちのメーリングリストには3,800人以上のメンバーが参加し、毎月1,500を超える投稿があります。プロジェクトは130を超えるコントリビューター(コードやドキュメントを投稿した人)を持ち、公開してからなされた2,800以上のコミットのほぼ三分の一はコアチーム以外のプログラマによるものです。それら全てのコードを形にするためには、14,000近くのEメールが開発者用メーリングリスト上でやりとりされました。

これらの数値はプロジェクトのコードベースに成果として現れています。コンパイラは大幅に改善され、より早く、より効率的なコードを生成するようになり、報告されたバグが100以上修正され、幅広いOSやアーキテクチャがサポートされるようになりました。Windowsへのポーティングも献身的なコントリビューターのグループ(彼らの中の一人はGoogle以外での最初のコミッターです)のお陰で完成に近づいています。ARMへのポートも大きな進展を見せ、最近すべてのテストが通るマイルストーンに到達しつつあります。

Goツールセットも拡張と改良が進んでいます。Goドキュメントツール、godoc、は他のソースツリーのドキュメントもサポートするようになり(自分自身のコードをブラウズしたり検索できます)、"code walk"インターフェースを使用してチュートリアルの資料を表示できます(他にも多くの改良があります)。Goinstall(新しいパッケージマネージメントツール)を使うとユーザーは外部パッケージを一つのコマンドでインストールしたりアップデートできます。Gofmt(Go整形ツール)は今や可能であれば構文の簡略化まで行うようになりました。Goplay(ウェブベースの「文字を打つするごとにコンパイルする」ツール)はGo Playgroundにアクセスできないときに、Goでなにかを試してみるにはいい道具です。

標準ライブラリも成長を続け、42,000行を越えるコードと、20個の新しいパッケージを含みます。追加されたものにはjpegjsonrpcmimenetchansmtpパッケージに加え、たくさんの新しい暗号パッケージもあります。より全体的な話をすると、標準ライブラリは私たちのGoのイディオムの理解が深まるのに合わせて継続的に改良され改訂が進んでいます。

デバッグに関する状況も良くなりました。gcコンパイラのDWARF出力に関する最近の改良によってGNUデバッガ、GDB、をGoバイナリに対して利用でき、さらにデバッグ情報を完全にするために積極的に作業を進めています。(詳細については最近のブログポストを参照してください。)

Go以外の言語で書かれた既存のライブラリとリンクするのも、これまでと比べて簡単になっています。SWIGの最新リリース、バージョン2.0.1、にはGoのサポートが含まれ、CやC++とのリンクが簡単になりました。また、我々のcgoツールも修正と改良が重ねられています。

Gccgo(GNU CコンパイラのためのGoフロントエンド)はもうひとつのGo実装としてgcコンパイラに組み込まれています。今やガーベジコレクションも動作し、GCCコアにも受け入れられました。私たちはgofrontendをGCCと完全に切り離されたBSDライセンスのGoコンパイラフロントエンド利用できるように作業を進めています。

Goプロジェクト自身だけでなく、Goは実際のソフトウェア構築にも使用され始めています。私たちのプロジェクトダッシュボードGoogle CodeGithubにリストされているGoプログラムやライブラリの数は200を超えます。メーリングリストやIRCを見れば、Goを自身のプログラミングプロジェクトに利用しているコーダーが世界中にいることが分かるでしょう。(先月から続く実世界での例に関するゲストによるブログポストも参照してください。)Google内部でもいくつかのチームが製品となるソフトウェアを構築するのにGoを利用することを選択しましたし、Goを用いて大規模なシステムを開発している企業からのレポートも受け取っています。さらに私たちはGoを使ってプログラミング言語を教えている何人かの教育者とも連絡をとりあっています。

言語自身も成長を続け成熟してきています。昨年、私たちは数多くの機能リクエストを受け取りました。しかし、Goは小さな言語です。私たちは全ての新機能が単純さと便利さのほどよい妥協点になり得るように気を配っています。ローンチ後にもたくさん言語を変化させましたし、そのうちの多くがコミュニティからのフィードバックによるものです。

  • セミコロンは今やほぼ全てのインスタンスでオプショナルです。(仕様
  • 新しい組み込み関数copyとappendによってスライスをさらに効率的、直接的に管理できるようになります。(仕様
  • サブスライスを作るときに上限と下限は省略できます。つまりs[:]はs[0:len(s)]の短縮形です。(仕様
  • 新しい組み込み関数recoverはエラー処理機構としてpanicとdeferを補完します。(ブログ仕様
  • 新しい複素数型(complex、complex64、complex128)はある種の数値演算を単純化します。(仕様仕様
  • 複合リテラル記法によって(例えば二次元配列を指定するときに)冗長な型情報を省略できます。(2010-10-27リリース仕様
  • 関数の可変引数のための一般的な記法(...T)とその伝播(v...)が仕様に追加されました。(仕様仕様2010-09-29リリース

Goがすでに製品に使用可能であるということははっきりしていますが、改善の余地はまだあります。私たちが集中している直近のフィーチャーは、ハイパフォーマンスが要求されるシステムで利用できるようにGoプログラムをより早く、より効率的にすることです。要するに、ガーベジコレクターを改良し、生成されるコードを最適化し、コアライブラリを改良しているということです。また、私たちはさらなる追加機能として総称プログラミングをより簡単にするための型システムも調査中です。多くのことをがこの一年間に起こり、それはスリルに富んだものであると同時に満足の行くものでした。私たちは次の一年がこの一年よりもさらに実り多いものであることを願っています。

もしあなたがGo言語を[再び]試してみようと思っているなら、今こそやるべきです!情報を得るためにドキュメントGetting Startedページをチェックするか、そうでなければ単にGo Playgroundで遊びまくってもいいでしょう。

2010-11-04 寒かったり暖かかったり

Go言語で囲碁ゲームできたよー\(^o^)/

| 04:52 | Go言語で囲碁ゲームできたよー\(^o^)/ - ずっと君のターン を含むブックマーク

https://github.com/technohippy/Go-Go

f:id:technohippy:20101105035339p:image

GoGo って言いたかっただけDeath!

全世界に一万人くらい同じこと思いついた人いると思うけど、検索してみたら意外と見つからなかったからつい・・・。

コマンドのオプションはこんな感じ。

Usage of ./go:
  -server=false: ブラウザをUIに使いたいときはtrueに設定する。デフォルトのURLは http://localhost:55555/
  -port=55555: ポート番号。server=trueじゃなければ無視される
  -ai=false: コンピュータ対戦。今はひたすらランダムな場所に打ってくる
  -size=19: 碁盤のサイズ
  -load="": ファイルに保存した盤面をロードする。主にデバッグ用

現時点のAIと対戦するのは苦行でしかないので、ブラウザ使って人間同士で対戦してがんばって楽しんで欲しい。整地はできないから競ってると地を数えるのが大変そうだけど・・・。

もう少し時間ができたらまともなAI組み込んで囲碁の自習に使えるようにしたいと妄想中*1。ゴルーチンってゲーム木の探索とかに便利そうな気がするけどどんなもんなんだろう。

・・・

そういえばGo言語のドキュメントは http://golang.jp/ にたくさん翻訳があって非常にありがたい。ただ、言語自体の開発が活発なこともあってときどきはオリジナルをチェックしないとハマる。具体的にはHTTPサーバーの書き方が日本語ドキュメントに従うとこんな感じだったりするけど

http.HandleFunc("/", http.HandlerFunc(func(conn *http.Conn, req *http.Request) {
	conn.SetHeader("Content-Type", "text/plain")
	conn.Write([]byte("This is an example server.\n"))
}))

いつのまにやら http.Conn がなくなったみたいで今は http.ResponseWriter を使ってこんな感じにしないと動かなかった。

https://github.com/technohippy/Go-Go/blob/master/src/go/server.go

http.HandleFunc("/get", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
    // 略
    fmt.Fprint(rw, m.Json())
}))

まぁそれはそれとして、Go言語は標準添付ライブラリが意外と充実してるし、無駄なお作法が少なくて簡潔にかけるし、慣れるとなかなかいい感じ。登場から1年が経って結構こなれて来てると思うので、そろそろ何か新しい言語を覚えたいけどあんまり素っ頓狂な文法は避けたいという人は試してみてもいいんじゃないかなと。

*1買った本に載ってる処理は最低限組み込みたい

2010-10-21 降ったり止んだり

Go言語でマンデルブロ

| 01:42 | Go言語でマンデルブロ - ずっと君のターン を含むブックマーク

f:id:technohippy:20101022013828p:image

マンデルブロ氏追悼兼ゴルーチンの練習として、Go言語でマンデルブロ集合を書いてみた。初めてマンデルブロ書いたけど、なんや深いところを理解しようとせずに実装するだけなら意外と簡単なんすね。あと、Go言語のライブラリは思いのほか充実してる。複素数普通に使えるし、ImageMagicとか使わなくても単体でPNGを書き出せるのもすばらしい。

package main

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

type Settings struct {
  z0 complex128
  n complex128
  repeat int 

  viewCenter complex128
  viewWidth float64
  viewHeight float64

  imageWidth int 
  imageHeight int 
}

var settings = &Settings{
  z0 : complex128(cmplx(0, 0)),
  n : complex128(cmplx(2.0, 0)),
  repeat : 30, 
  viewCenter : complex128(cmplx(0, 0)),
  viewWidth : 3.5,
  viewHeight : 3.5,
  imageWidth : 300,
  imageHeight : 300,
}

func cmplx128(r float64, i float64) complex128 {
  return complex128(cmplx(r, i)) 
}

func checkMandelbrot(c complex128, res chan int) {
  z := settings.z0
  for i := 0; i < settings.repeat; i++ {
    z = cmath.Pow(z, settings.n) + c
    if 2.0 < cmath.Abs(cmath.Pow(z, 2)) {
      res <- i + 1
      return
    }
  }
  res <- -1
}

func calculate(results [][]chan int) {
  var comp complex128
  cr := real(settings.viewCenter) - settings.viewWidth / 2
  ci := imag(settings.viewCenter) - settings.viewHeight / 2
  dr := settings.viewWidth / float64(settings.imageWidth)
  di := settings.viewHeight / float64(settings.imageHeight)

  for y := 0; y < settings.imageHeight; y++ {
    results[y] = make([]chan int, settings.imageWidth)
    for x := 0; x < settings.imageWidth; x++ {
      results[y][x] = make(chan int)
      comp = cmplx128(cr + dr * float64(x), ci + di * float64(y))
      go checkMandelbrot(comp, results[y][x])
    }
  }
}

func generateImage(results [][]chan int) *image.RGBA {
  rgba := image.NewRGBA(settings.imageWidth, settings.imageHeight)
  for y := 0; y < settings.imageHeight; y++ {
    for x := 0; x < settings.imageWidth; x++ {
      res := <-results[y][x]
      if res < 0 {
        rgba.Set(x, y, image.RGBAColor{0, 0, 0, 255})
      } else {
        depth := uint8(25 * res)
        rgba.Set(x, y, image.RGBAColor{0, depth, depth, 255})
      }
    }
  }
  return rgba
}

func main() {
  var results [][]chan int
  results = make([][]chan int, settings.imageHeight)
  calculate(results)
  image := generateImage(results)
  png.Encode(os.Stdout, image)
}

設定をファイルかコマンドラインから読み取るようにしようと思ってたけど、面倒くさくなったので放置。

もっちもっち 2010/10/22 10:18 Windows(MinGW)でビルドしたGoでも、本記事のソースをビルド出来、
問題なくマンデルブローの画像を出力できました。
感動しました。

technohippytechnohippy 2010/10/22 13:00 WindowsでもGoは動くんですねー

2010-10-07 過ごしやすい季節になりました

Go言語版Whitespace作ってみました

| 03:34 | Go言語版Whitespace作ってみました - ずっと君のターン を含むブックマーク

http://github.com/technohippy/go-whitespace/blob/master/src/whitespace.go

前回、Go言語でBrainf*ckを作ってみたので、今度はやっぱWhitespaceかなと思って軽い気持ちで始めたんだけど、正直Whitespaceをなめてました。もっとこう、一日くらいでさくっとできるかと思ってた。すいません。Brainf*ckは完全にネタだけど、Whitespaceは結構ガチな言語なんすね。るびまの他言語探訪でとりあげられるだけのことはある・・・。手作業でちまちまパースしたけど素直にyaccとか使った方が楽だったかも。

しかも結局公式のExamplesの半分くらいしか動かなくて、後の半分は結果がなんかおかしい。一つ仕様がわからなくて(Haskell読めないし・・・)実装してないインストラクションがあるからそれが原因なのかな。ただ、いかんせんソースは目に見えないし、やってることもいちいちローレベルで確認が面倒だしで、この辺で妥協してもう止めます。いずれ気が向いたときに直すということで。

・・・

直しました。ラベルを数値扱いしてたせいで異なるラベル([Tab][Space][LF]と[Tab][LF]とか)が同一視されてたのがおかしな挙動の原因だった。(2010/10/11)

・・・

とりあえずこれ作っててGo言語について分かったこと。

  • インポートしたパッケージを使ってないとコンパイルエラー
  • 宣言した変数を使ってないとコンパイルエラー

親切といえば親切だけど、こういうのはWarningくらいでいいんじゃないかとも・・・。

  • elseの前に改行が入れられない
if cond {
  hoge
}
else {
  fuga
}

これだめ。

if cond {
  hoge
} else {
  fuga
}

こうする。

  • 条件文使った時のreturnの判定が・・・
func foo() bar {
  if cond {
    return hoge
  } else {
    return fuga
  }
}

だとreturnがないって叱られるので

func foo() bar {
  if cond {
    return hoge
  }
  return fuga
}

こんな感じにしたりした。

  • 多重代入使ったswapはいい感じ
package main 
import ("fmt")

func main() {
  var arr = []int{1,2,3,4}
  arr[1], arr[2] = arr[2], arr[1]
  fmt.Printf("%v\n", arr)
} 
$ 6.out
[1 3 2 4]

配列要素もswapできた。よしよし。

・・・

まとめると、RubyとかScalaなんかの近代の言語は割とコンパイラやらインタプリタやらが頑張っていい感じに空気を読んでくれるけど、Go言語はむしろ人間の側が気を使ってコンパイラが解釈しやすいようにコード書いてやる必要がある。

まぁ結果的にその方が表記のブレは減るので、そういう判断もありなのかなという感じ。慣れれば意識しなくてもそう書けるようになるだろうし。しかし、PythonといいGo言語といい、Googleは誰が書いても同じような見た目になる言語が好きなんだろうか。

次はゴルーチン使ってなんか作ってみたいすね。