( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

設計を型にエンコードするということ

動的型付け vs 静的型漬けのアレでもんにょりしてたのをついったーに放出して会話してたらなんとなく自分なりの考えがまとまったので貼っておく。



まとめると

  • コンパイラがテストしてくれるように型を書きたい
  • 型に対して、データ型のラベル以上の意味(設計の意図とか)を持たせたい
  • そうすると、自分以外の人が自分が書いたコードを使うときに、正しい使い方を自分やドキュメントに代わって、コンパイラが教えてくれるようになる
  • そういうことが簡単にできるように言語とか進化するといいよね


現実的になんでもかんでもコンパイラにやらせようとすると難しいところもあると思う。
「アカデミックだ」「現場では使えない」とか単に否定するのは簡単だ。
静的な型漬けの言語でコード書いたからって、上記のようにすべてのロジックを型レベルにエンコードすることなど不可能だし、制約が強すぎると感じるかも知れないし、この制約が逆に足かせになる性質のプロダクトがあることもわかっている。


でも、型推論とかGenericsとかは、アカデミックな研究と実用のニーズが相まって、
現実のプログラミング言語に取り入れられつつあるし、
静的な型漬けを好む人達が見据えているプログラミング言語の進化の方向性として、こういう考え方がありますよ、って知って欲しい。

git symbolic-ref を利用して、ブランチに対して別名をつけることができて墓ドル

何が便利かというと、今git-flowで運用しているのだけど、リリースブランチを常に同じブランチ名で参照できるようになってうれしい。


例えば、次回にリリースブランチが'release/1.17.0'だとすると、'git symbolic-ref rc release/1.17.0'とすることで、'rc'という名前で参照できる。
エイリアス先の'release/1.17.0'のHEADが先に進んでも、rcも常に同じHEADを参照するので、一貫して'rc'という名前で扱える。

  $ git symbolic-ref rc refs/heads/release/1.17.0


これだけだとありがたみがない。が、無事に1.17.0がリリースされて、その次のリリースブランチがrelease/1.18.0になったときでも、'git symbolic-ref rc release/1.18.0'でエイリアスを張り直せばいい。リリースブランチは、常に一貫して'rc'という名前でアクセスできる。


この機能、git-flowに入れたい。

svn - Is it possible to alias a branch in Git? - Stack Overflow

httpstatus コマンドで、HTTP のステータスコードをすばやくしらべる!

一般的な Web Programmer ならば、HTTP Status code はすべて暗記していると聞きました。

しかし、僕は初心者なので、なかなか覚えきれていないので、HTTPのステータスコードをさがすのに便利なツールを用意しました。
httpstatus.hs です。インストール方法は 適当にコンパイルしてください。


yuroyoro / httpstatus.hs

Scala2.10.0のDependent method typesと型クラスを組み合わせた『The Magnet Pattern』がヤバい件

これが……型の力かッ……!!

f:id:yuroyoro:20130123192116j:plain

spray | Blog » The Magnet Patternという記事で、「The Magnet Pattern」というデザインパターンが紹介されている。


これは、メソッドオーバーロードで解決していた問題を、型クラスとDependent method typesを組み合わせて置き換えることで、オーバーロードの際の様々な制約(Type Erasureなど)を突破し、より柔軟な拡張性を得ることができるというもの。このパターンでは、引数の型に応じて異なる結果型を返すようにできる。


この記事で、今まで何のために使われるのかわからんかったDependent method typesの有効性が理解でき、あらためて型の力を思い知った。
以前に"Generalized type constraints"(Scalaで<:<とか=:=を使ったgeneralized type constraintsがスゴすぎて感動した話 - ( ꒪⌓꒪) ゆるよろ日記) を知った時以来の感動だったので、勢いで書いてみた。

Dependent method types ってなんぞ?

簡単に言うと、引数の値に応じて結果型が変わるメソッドを定義できるということ。

Scala 2.10 に dependent method types というのが入るらしいよ - scalaとか・・・


以下の例では、fメソッドは、引数のFooトレイトのResult型に応じて結果型が変わる

trait Foo{
  type Result
  def bar:Result
}

// 引数のFooトレイトのResult型に応じて結果型が変わる
def f(obj:Foo):obj.Result = obj.bar


ResultをStringで定義してるstringFooオブジェクトと、Intで定義しているintFooオブジェクトを用意し、

val stringFoo = new Foo {
  type Result = String
  def bar:Result = "foo"
}

val intFoo = new Foo {
  type Result = Int
  def bar:Result = 99
}


fメソッドにそれぞれ渡すと、引数の値に応じて結果型が変わっていることがわかる。もちろん型安全なので、全てコンパイル時に型チェックが行われる。

scala> f(stringFoo)
res0: stringFoo.Result = foo

scala> f(intFoo)
res1: intFoo.Result = 99

scala> f(stringFoo).getClass
res2: Class[_ <: stringFoo.Result] = class java.lang.String

scala> f(intFoo).getClass
res3: Class[intFoo.Result] = int

The Magnet Patternが解決する問題

引数の型に応じてメソッドの振る舞いを変えたい場合、一つの手段としてメソッドをオーバーロードする、という手がある。 しかし 元記事では、オーバーロードでは以下のような問題が発生する、と述べている。

  • type erasureにより引数の型が衝突する場合がある
  • メソッドからFunctionオブジェクトに"lift"できない(println _のような)
  • package objectでは使えない(2.9以前)
  • 似たようなコードが多発
  • デフォルト引数の利用に制限がある
  • 引数の型推論に制限がある


The Magnet Patternは、この問題のいくつかを解決し、さらなるメリットをもたらす

  • type erasureによる型の衝突は解決
  • Functionオブジェクトへのliftは、全ての結果型が同一であれば可能
  • package objectでも定義できる
  • 実装をDRYにできる
  • 危険なimplicit conversionの定義を避けることが出来る
  • 引数の型に応じて異なる結果型を返すことが可能


このパターンは、拡張性に柔軟をもたらす一方で、DrakSideもあると。

  • オーバーロードに比べて実装が細かく見通しが悪くなる
  • 名前付きパラメータは利用できない
  • by-name(名前渡し)パラメータとimplict conversionの組み合わせで重複してby-nameパラメータが評価されることがある
  • Magnet Parttenで定義するメソッドは引数宣言が必須
  • デフォルト引数は定義できない
  • 引数に対しての型推論はできない(引数に無名関数を渡す場合などで)

The Magnet Patternの例

元記事では、例としてSprayにおけるURLルーティングを定義するDSLの実装をあげている。 非常にわかりやすいのでそっちを見てもらうのがいいのだけど、それだとあんまりなので例を書いてみる。


ここでの例は、3個のIntまたはStringから日付変換を行う関数toDateを考える。

通常のオーバーロードでの実装

object M {
  def toDate(year:Int, month:Int, date:Int): java.util.Date = {
    val c = java.util.Calendar.getInstance
    c.set(year, month - 1, date, 0, 0, 0)
    c.getTime
  }

  def toDate(s:String): java.util.Date =
    (new java.text.SimpleDateFormat("yyyy/MM/dd")).parse(s)
}


まぁ見たとおりです。

scala> M.toDate(2013,1,21)
res40: java.util.Date = Mon Jan 21 00:00:00 JST 2013

scala> M.toDate("2013/01/21")
res41: java.util.Date = Mon Jan 21 00:00:00 JST 2013

The Magnet Patternで書き換えてみる

The Magnet Patternでは、オーバーロードを行う代わりに、メソッドの引数にある型(仮にmagnet型と呼ぶ)を一つだけとるようにする。 そして、そのmagnet型へのimplicit convesionを定義することで、オーバーロードと同様に引数の型に応じた振る舞いを定義できる。


さらに、The Magnet Patternでは引数の型に応じて結果型を変えることが可能である。 "magnet型"に抽象型で結果型を持たせ、Depenent method typesを利用して、引数がIntの場合はDateではなくCalndarを返すようにしている。

scala> :paste
// Entering paste mode (ctrl-D to finish)

// toDateは引数に"magnet型"を取るようにする。変換の実装はconvertメソッドに任せる
// 結果型は、"magnet型"が持つ抽象型Resultに依存させている
def toDate(magnet:DateMagnet):magnet.Result = magnet.convert

// toDate関数で使われる"magnet型"のtrait
trait DateMagnet{
  type Result           // 変換結果の結果型は抽象型で持つ
  def convert():Result  // 変換を行うメソッド
}

// "magnet型"のコンパニオンオブジェクトに、
// それぞれの引数の型に合わせた"magnet型インスタンス"を返すimplicit defを
// 定義しておく
object DateMagnet {

  // 3つのIntからDateMagnetのインスタンスへ
  implicit def fromInt(tuple:(Int, Int, Int)) = new DateMagnet {
    type Result = java.util.Calendar
    def convert():Result  = {
      val (year, month, date) = tuple
      val c = java.util.Calendar.getInstance
      c.set(year, month - 1, date, 0, 0, 0)
      c
    }
  }

  // StringからDateMagnetのインスタンスへ
  implicit def fromString(s:String) = new DateMagnet {
    type Result = java.util.Date
    def convert():Result  =
      (new java.text.SimpleDateFormat("yyyy/MM/dd")).parse(s)
  }
}

// Exiting paste mode, now interpreting.

warning: there were 2 feature warnings; re-run with -feature for details
toDate: (magnet: DateMagnet)magnet.Result
defined trait DateMagnet
defined module DateMagnet

scala> toDate(2013, 1, 21)
res0: java.util.Calendar = java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2013,MONTH=0,WEEK_OF_YEAR=4,WEEK_OF_MONTH=4,DAY_OF_MONTH=21,DAY_OF_YEAR=25,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=10,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=88,ZONE_OFFSET=32400000,DST_OFFSET=0]

scala> toDate("2013/01/21")
res1: java.util.Date = Mon Jan 21 00:00:00 JST 2013


実装のポイントは、

  • 引数は"magnet型"を一つとるようにする
  • implicit conversionで引数の型に応じた"magnet型"のインスタンスへ変換する
  • 型に応じた振る舞いは、"magnet型"のメソッドに実装する
  • Dependent method typesを利用することで、"magnet型"の抽象型を差し替えて結果型を変えることができる

上記4点である。ひとことでいうと、オーバーロードで実装されている型毎の処理を型クラスに移した、といえる。


まとめ

メソッドのオーバーロードでは、Type Erasureによる制約などがあるが、 The Magnet Patternではimplicit convesionを定義することであとで対応する型を増やしたり、 結果型を変えたりと、様々な柔軟性を得ることができる。 一方で、実装が複雑になる、コードの見通しが悪くなる、などのデメリットもある。


興味深いのは、このThe Magnet Patternは型クラスやDepenent method typesを利用した、従来のOOPでは実現できない新しいプログラミングパラダイムにおけるデザインパターンであるという事実だ。
デザインパターンGoFからまだまだ進化していると言える。


これが……型の力だッッッ!!!!

Rails3でMultiJsonのBackendをyajlに変更してJSONのエンコード/デコードのパフォーマンスを改善する

yajl(Yet Another JSON Library)っていう高速なJSONライブラリがあって、

yajl


こいつをrubyから使えるようにするyajl-rubyってgemがあって、これをMultiJsonのBackendに変更することで、RailsにおけるJSON処理の高速化が期待できるデス。

brianmario/yajl-ruby · GitHub


素のjson.gemと、yajl-rubyとで適当なActiveRecordオブジェクトからJSONへのエンコードと、その逆のデコードで簡単にベンチってみると、約2倍の差があることが分かる。

--------------------------------------------------------------------------------
Benchmark of json encoding/decoding
  json_gem vs yajl
--------------------------------------------------------------------------------

                                            | json_gem |    yajl | json_gem/yajl |
--Single ActiveRecord Object -----------------------------------------------------
encode                               x10000 |   12.130 |   6.167 |         1.97x |
decode                               x10000 |    1.085 |   0.437 |         2.48x |
--Array  ActiveRecord Objects-----------------------------------------------------
encode                               x10000 |  508.319 | 225.235 |         2.26x |
decode                               x10000 |   39.069 |  19.869 |         1.97x |

MultiJsonのBackendをyajlに変えたら素のjson.gemの2倍のパフォーマンスになった件


MultiJsonは、yajl-rubyがあると自動的にそっちを見るようになってるので、gem 'yajl-ruby'するだけでjsonの処理が高速化する、とおもいきや……


ActiveSupport::JSONのコードを見てみると、JSONのデコード時にはMultiJsonを利用するようになっているが、エンコードする際にはActiveSupport独自の実装でエンコードするようになっている。この理由としては、ActiveSupportAPIと他のライブラリの実装で互換性がないかもしれない、という話みたいだ。

Endoding with yajl-ruby for rails 3 · Issue #40 · brianmario/yajl-ruby · GitHub


とはいえ、オブジェクトをActiveSupportのas_jsonでHashにしてしまって、それをyajlでJSONにエンコードすれば問題ないはず。ということで、このようなパッチを書いた。

MultiJson.engine = :yajl unless MultiJson.engine == MultiJson::Adapters::Yajl

module ActiveSupport
  module JSON
    def self.encode(value, options = nil)
      hash = ActiveSupport::JSON::Encoding::Encoder.new(options).as_json(value)
      MultiJson.encode(hash)
    end
  end
end

JSONを大量にやりとりする系のアプリケーションには多少のパフォーマンス改善が期待できる、はず。

Jenkinsで外部パラメータで与えたブランチを対象にビルドできるようにしておくと凄惨性あがって墓ドル

テストが終わるまでの時間で書いてみる。

Jenkinsでジョブを実行させるときに、外部パラメータで任意のブランチを対象にビルドできると墓ドル。

例えば、自分のローカルブランチをマージするまえに、テストが通るか確認したい場合とか。

そんなのローカルでテストすりゃーいいじゃんって言われるかもしれないが、 テスト全部通すのに時間が掛かるようになってると、とりあえずCIに実行を投げておいてあとで確認するほうがずっと効率がいい。


F.Y.I: Building github branches with Jenkins

ジョブの設定

「ビルドのパラメータ化」にチェックをつけて、以下のようにbranchって名前のパラメータを設定しておく。
f:id:yuroyoro:20121220174907p:plain


ソースコード管理システム」で「Branches to build」のところに、設定したパラメータである"$branch"を入れておく。
f:id:yuroyoro:20121220174921p:plain


ジョブの設定は以上。上記の方法はGitの場合だけど、他のVCSでも似たようなことできると思う多分。

ジョブの実行

ビルドを実行したいブランチをpushした上で、「ビルド実行」をクリックするとブランチを入力するように言われるのでよろしくやる。
f:id:yuroyoro:20121220174929p:plain


自分は、ブラウザから実行させるのがタルいので、以下の用にcurlでjenkinsにリクエストを送る事でジョブをkickしている。墓・ドル。

$ curl -v http://<your jenkins host>/job/<job name>/buildWithParameters\?branch\=<branch name>

* About to connect() <your jenkins host> port 80 (#0)
*   Trying <your jenkins ip>... connected
* Connected to <your jenkins host> (192.168.1.171) port 80 (#0)
> GET /job/yuroyoro-build/buildWithParameters?branch=origin/features/ci_test  HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.12.9.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
> Host: <your jenkins host>
> Accept: */*
> 
< HTTP/1.1 302 Found
< Date: Thu, 20 Dec 2012 08:25:55 GMT
< Server: Winstone Servlet Engine v0.9.10
< Location: http://<your jenkins host>/job/yuroyoro-build/
< Content-Length: 0
< X-Powered-By: Servlet/2.5 (Winstone/0.9.10)
< Via: 1.1 <your jenkins host>
< Content-Type: text/plain
< 
* Connection #0 to host <your jenkins host> left intact
* Closing connection #0

注意点として、[branch name]は'origin/features/ci_test'のように指定しないといけない。

実行結果の確認

ジョブの結果は、Growl pluginを利用して通知を受け取るようにしている。墓$。

Jenkinsの通知をGrowlで受け取る - Basic

実行ログは、jenkins.gemを使ってCLIから確認している。'jenkins [job name]'で最新の実行ログを取ってきてくれる。墓。


jenkins | RubyGems.org | your community gem host
cowboyd/jenkins.rb-history · GitHub


こんな感じで、ビルド実行ブランチをパラメータ化しておくことで、いつでも好きなときに、メインラインにマージ前のブランチのテストをCI鯖に任せることが可能になった。
curlとGrowl pluginとjenkins.gemのおかげで、「ジョブ投入 → 終了通知 → 結果確認」をすべてCLI内で完結させることができて、凄惨性あがって墓ドル。

.gitconfigでFizzBuzz

.gitconfigのtipsを公開するのが流行ってるみたいなので。

git config alias.fizzbuzz "!f() { seq "$@" | awk '$0=NR%15?NR%5?NR%3?$0:\"Fizz\":\"Buzz\":\"FizzBuzz\"' ;}; f"

( ꒪⌓꒪) git fizzbuzz <num> · 7a4ddcd · yuroyoro/dotfiles · GitHub


git fizzbuzz で実行

ozaki@mbp-4 $ git fizzbuzz 30 
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz


Fizzbuzzも書けないプログラマは(炎上ワードにつき削除)

実体はawkです。
AWK Users JP :: awk で FizzBuzz