2011年4月16日土曜日

Go fix that **** code...

Goのユーザーとしてちょっと辟易していたこととして、開発チームがGoそのものの文法や標準ライブラリのAPIをガツガツ変更することが挙げられる。いやホントに良く変わる。発表されてからもう1年経ったけど、まだ当分安定するようには見えない。

で変更されて何が困るかというと、古いコードのコンパイルが通らなくなること。自分が書いたコードならいざしらず、他人が書いたコードのコンパイルが通らなくなってしまうと結構厄介。メンテナー次第では下手すると当分コードがアップデートされずにそのままなんてこともありうる。じゃあ自分で直そうにも、もしかしたらエンバグするかもしれないし、そもそもが面倒だからあんまりヤル気にならない。そうすると自分が使ってたパッケージが使えなくなって、でもかといって古いバージョンのGoを使う気にもなれなくて、開発に対するモチベーションが下がって…という酷い悪循環におちいる。自分は。

Gofix

そういう状況を開発チームも理解していたのか、ちょっと前にGofixなんていうツールが発表された。詳しくはRussの書いたブログを読んでもらうとして、Gofixがやってくれるのは簡単に言うと「コードの自動修復」だ。どうやら古いAPIやら文法が使われているコードを自動的に修復して、ナウいバージョンに仕立てなおしてくれるらしい。

Go fix Thrift

ということで、実益と実験も兼ねてThriftのGoコードをGofixにかける実験をやってみた。なお使用したバージョンはGoがr8131、Thriftがr1093950。

Thrift修正前

Thriftの修正前ライブラリを普通にコンパイルしようとしたら、以下のようになる。net.DialあたりのAPIが変わったせいでコンパイルが通らない。

$ make
cd thrift/ && gomake install
make[1]: Entering directory `/home/masato/lib/thrift/lib/go/thrift'
8g -o _go_.8 tapplication_exception.go tbase.go tbinary_protocol.go tcompact_protocol.go tcompare.go tcontainer.go texception.go tfield.go tframed_transport.go thttp_client.go tiostream_transport.go tlist.go tjson_protocol.go tmap.go tmemory_buffer.go tmessage.go tmessagetype.go tnonblocking_server.go tnonblocking_server_socket.go tnonblocking_socket.go tnonblocking_transport.go tnumeric.go tprocessor.go tprocessor_factory.go tprotocol.go tprotocol_exception.go tprotocol_factory.go tserver.go tserver_socket.go tserver_transport.go tset.go tsimple_server.go tsimple_json_protocol.go tsocket.go tstruct.go ttransport.go ttransport_exception.go ttransport_factory.go ttype.go
tnonblocking_socket.go:111: too many arguments in call to net.Dial
tsocket.go:137: too many arguments in call to net.Dial
make[1]: *** [_go_.8] Error 1
make[1]: Leaving directory `/home/masato/lib/thrift/lib/go/thrift'
make: *** [thrift/.install] Error 2

Thrift修正後

この状況から、まずGofixを走らせた上でmakeすると…

$ gofix .
thrift/tnonblocking_socket.go: fixed netdial
thrift/tsocket.go: fixed netdial
$ make
cd thrift/ && gomake install
make[1]: Entering directory `/home/masato/lib/thrift/lib/go/thrift'
8g -o _go_.8 tapplication_exception.go tbase.go tbinary_protocol.go tcompact_protocol.go tcompare.go tcontainer.go texception.go tfield.go tframed_transport.go thttp_client.go tiostream_transport.go tlist.go tjson_protocol.go tmap.go tmemory_buffer.go tmessage.go tmessagetype.go tnonblocking_server.go tnonblocking_server_socket.go tnonblocking_socket.go tnonblocking_transport.go tnumeric.go tprocessor.go tprocessor_factory.go tprotocol.go tprotocol_exception.go tprotocol_factory.go tserver.go tserver_socket.go tserver_transport.go tset.go tsimple_server.go tsimple_json_protocol.go tsocket.go tstruct.go ttransport.go ttransport_exception.go ttransport_factory.go ttype.go
rm -f _obj/thrift.a
gopack grc _obj/thrift.a _go_.8
cp _obj/thrift.a "/home/masato/lib/google-go/pkg/linux_386/thrift.a"
make[1]: Leaving directory `/home/masato/lib/thrift/lib/go/thrift'

こんな感じで上手く行く。スバラシイ。

Thrift修正差分

ついでにどういう差分が適用されたかを見てみる。下はdiffをとった結果の一部だけど、net.Dialのところに注目するとちゃんと引数が減らされてる。

-  var err os.Error
-  if p.conn, err = net.Dial(p.addr.Network(), "", p.addr.String()); err != nil {
-    LOGGER.Print("Could not open socket", err.String())
-    return NewTTransportException(NOT_OPEN, err.String())
-  }
-  if p.conn != nil {
-    p.conn.SetTimeout(p.nsecTimeout)
-  }
-  return nil
+       var err os.Error
+       if p.conn, err = net.Dial(p.addr.Network(), p.addr.String()); err != nil {
+               LOGGER.Print("Could not open socket", err.String())
+               return NewTTransportException(NOT_OPEN, err.String())
+       }
+       if p.conn != nil {
+               p.conn.SetTimeout(p.nsecTimeout)
+       }
+       return nil

まとめ

ということで、Thriftという実例をつかってGofixの便利さを体感してみた。こういう便利ツールが公式に提供されて、かつほとんどが自動的に修正されるのであればAPIやら文法の変更もバンバンやってくれて構わないかも。

ちなみに、Thriftが生成したCassandraへのインタフェースライブラリをGofixしてもコンパイルが通らなかったことに関しては、また別の話。これThriftのバグなのか…? いまいち追う気になれない。

2011年4月9日土曜日

OCaml Pattern Matching

引きつづきOCaml。今度はパターンマッチング。パターンマッチング自体はその名の通り、与えられた引数の値に応じた値を返す関数を定義する仕組み。

組込み型でのパターンマッチング

(* normal pattern matching *)
let color_to_num = function
    "RED" -> 0
  | "BLUE" -> 1
  | "GREEN" -> 2
  | _ -> -1;;

let num = color_to_num "RED" in
Printf.printf "%d\n" num;;

let num = color_to_num "aardvark" in
Printf.printf "%d\n" num;;

出力

$ ./a.out
0
-1

独自型でのパターンマッチング

(* matching via Sum type*)
type color_t = Red | Blue | Green ;;
let color_t_to_num = function
    Red -> 0
  | Blue -> 1
  | Green -> 2;;

let num = color_t_to_num Blue in
Printf.printf "%d\n" num;;

(* Compile error
let num = color_t_to_num Black in
Printf.printf "%d\n" num;;

let num = color_t_to_num "aardvark" in
Printf.printf "%d\n" num;;
*)

出力

$ ./a.out
1

やっぱ型チェックが厳しいと安心してコードがかけるのがいい。正直Pythonとかはもうあまり書きたくないかも。

パターンマッチングの文法に関しては、let xxxx = function ... という書きかたと let xxxx = match x with ... という書きかたの使いわけが良くわからない。ただ、Oreillyの本で読むかぎりだとfunctionを使う方は単なるシンタックスシュガーっぽい。

2011年4月3日日曜日

Object Oriented OCaml

引きつづきOCaml。OCamlのObject Orientedな部分の仕様が思ってたより膨大で、Thriftが生成したコードが理解できないのでOreillyの本を読みつつサンプルを書いてみる。

oo.ml

class car (name, color)=
  object
      val mutable m_name = name
      val mutable m_color = color
      method get_name = m_name
      method get_color = m_color
      method to_string = m_color ^ " " ^ m_name
  end;;

class flying_car(name, color, flying) =
  object
      inherit car(name, color)
      val mutable m_flying = flying
      method land_car = m_flying <- false
      method fly_car = m_flying <- true
      method to_string =
        let state = if m_flying then "flying" else "not flying" in
        state ^ " " ^ m_color ^ " " ^ m_name
  end;;

let c = new flying_car("Keitora", "black", true) in
let () = c#land_car in
let msg = "I bought a new " ^ c#to_string in
print_endline msg

出力

$ ocamlc oo.ml
$ ./a.out
I bought a new not flying black Keitora

しかしOOな仕様が入ると途端に予約語が増える。ちなみに、最初は関数land_carをlandって名前にしてて謎のコンパイルエラーが発生してた。結局原因はlandという名前の組込み演算子が存在するから。syntax errorってだけじゃなかなか気付かないッスよコンパイラさん。