Hatena::ブログ(Diary)

北海道苫小牧市出身のPGが書くブログ RSSフィード Twitter

Fork me on GitHub

2012-02-10(金)

退職します

今日が今勤めている会社の最終出社日となりました。


思えば6年前、次期システムをPHPで開発するつもりだと話していた当時のスタッフにPerlを使うよう誑かしたアドバイスしたのが始まりで、まさかここまで付き合いが長くなるとは思ってもいませんでした。


在職中は0からシステム構築をする機会を与えられた上、BTSやCIを始めとする開発ツール・フローの導入、書籍購入費の無料化、20%ルールの実践、社内勉強会の開催など、エンジニアがやりたいと思ったことをどんどん実践できる権限を与えられ、大変刺激的に過ごすことができました。


お世話になった職場の皆様に、この場を借りて感謝致します。どうもありがとうございました。

2012-02-02(木)

Scalaの空括弧とUnit

Unit周りでハマったのでメモ。

まず、() => Any という型はあるが、()という型はない。

scala> def id(x: () => Int): () => Int = x
id: (x: () => Int)() => Int

scala> def id(x: ()): () = x
<console>:1: error: '=>' expected but ')' found.
       def id(x: ()): () = x
                   ^

Unit型の唯一の要素が()である。

scala> ().isInstanceOf[Unit]
res67: Boolean = true

() => Any は Unit => Any とは違う。

// 型が違って代入できない
scala> val emptyParentheses: () => Any = { () => 10 }
emptyParentheses: () => Any = <function0>

scala> val unit: Unit => Any = emptyParentheses
<console>:17: error: type mismatch;
 found   : () => Any
 required: Unit => Any
       val unit: Unit => Any = emptyParentheses
                               ^
// 型が違って代入できない
scala> val unit: Unit => Any = {(u: Unit) => 20}
unit: Unit => Any = <function1>

scala> val emptyParentheses: () => Any = unit
<console>:17: error: type mismatch;
 found   : Unit => Any
 required: () => Any
       val emptyParentheses: () => Any = unit
                                         ^

が、Unit => Any は空括弧で呼び出すことができる。

scala> def one(u: Unit): Int = 1
one: (u: Unit)Int

scala> one(())
res68: Int = 1

// これ、なんでエラーにならないのかわからず (Value Discardingから??)
scala> one()
res69: Int = 1

scala> one
<console>:18: error: missing arguments for method one in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
              one
              ^

// これは、要請がUnit型なら{ e; () }に変換するルール(Value Discarding)より
scala> one("DUMMYYY")
res71: Int = 1

// これもValue Discarding から??
scala> one(1, "XXX", Nil)
res72: Int = 1

Scalaのvalとvarとdef

valは定数、varは変数、defはコード、というのが大まかな理解になる。これらは名前空間を共有しているので、定数、変数、コードに同じ名前をつけることはできない。ただし、JSやPythonのように単純にフィールドにメソッドオブジェクトが入っていると思うとハマる部分があるので、その辺を含めて調べたことをメモ。(なお、このエントリは 2.9.1.final での挙動について書いている。)


クラス定義のvar、def、val はメソッド

valとvarはfinal付き、final無しの変数として解釈されると思われるが、クラスのフィールドとして定義した場合にはJVM上ではメソッド経由でアクセスすることになる。ので、abstract defをvarやvalでオーバーライド*1したりできる。

class ValVarDef {
  val x = 1
  var y = 2
  def z = 3
}
% javap -private ValVarDef
public class ValVarDef extends java.lang.Object implements scala.ScalaObject{
    private final int x;
    private int y;
    public int x();
    public int y();
    public void y_$eq(int);
    public int z();
    public ValVarDef();
}

メソッドと関数型のフィールドは違う

JSなどと違い、def hoge(x: String): Int (メソッド)とdef hoge: String => Int (関数オブジェクトのフィールド)はまったく違うもの。

// 名前付き引数で呼べる
scala> def hoge(x: String): Int = x.length
hoge: (x: String)Int

scala> hoge("abcde")  // ←一見同じ振る舞いをするように見える
res140: Int = 5

scala> hoge(x = "abcde")
res141: Int = 5

// 名前付き引数で呼べない
scala> def hoge: String => Int = x => x.length
hoge: String => Int

scala> hoge("abcde")  // ←一見同じ振る舞いをするように見える
res142: Int = 5

scala> hoge(x = "abcde")
<console>:11: error: reassignment to val
              hoge(x = "abcde")
                     ^

// 引数には自動的に名前がつく (コメント欄参照)
scala> hoge(v1 = "abcde")
res3: Int = 5

JVMのレベルで見ると、前者はメソッドに直接処理を記述したもので、後者は処理を記述されたFunctionNオブジェクト(ここでは MyTest$$anonfun$hoge2$1)を返すメソッドになる。

class MyTest {
  def hoge1(x: String): Int = x.length
  def hoge2: String => Int = x => x.length
}
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public int hoge1(java.lang.String);
    public scala.Function1 hoge2();
    public MyTest();
}

public final class MyTest$$anonfun$hoge2$1 extends scala.runtime.AbstractFunction1 implements scala.Serializable{
    public static final long serialVersionUID;
    public static {};
    public final int apply(java.lang.String);
    public final java.lang.Object apply(java.lang.Object);
    public MyTest$$anonfun$hoge2$1(MyTest);
}

defの定義で、引数なしと空括弧は微妙に違う

def hoge = 1 と def hoge() = 1 は振る舞いが微妙に違う。特に関数オブジェクトが入っている場合。

scala> def func1 = (_: Int) + 1
func1: Int => Int

scala> def func2() = (_: Int) + 1
func2: ()Int => Int

// 引数無しで定義すると"func1()"では呼び出せない
scala> func1
res136: Int => Int = <function1>

scala> func1()
<console>:9: error: not enough arguments for method apply: (v1: Int)Int in trait Function1.
Unspecified value parameter v1.
              func1()
                   ^

// 空括弧で定義すると"func2()"でも"func2"でも呼び出せる
scala> func2
res138: Int => Int = <function1>

scala> func2()
res139: Int => Int = <function1>

// func1は括弧付きで呼び出せないので、意図したように解釈される
scala> func1(5)
res131: Int = 6

// func2を5を引数として呼び出したと解釈されるため、エラーとなる
scala> func2(5)
<console>:9: error: too many arguments for method func2: ()Int => Int
              func2(5)
                   ^

scala> func2()(5)
res135: Int = 6

=> はJVM上では関数オブジェクト

まあそうだよね。

class MyTest {
  def hoge1(x: Int) = x + 1
  def hoge2(x: => Int) = x + 1
  def hoge3(x: () => Int) = x() + 1
}
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public int hoge1(int);
    public int hoge2(scala.Function0);
    public int hoge3(scala.Function0);
    public MyTest();
}

lazy val はJVM上ではメソッドとして定義される

まあそうだよね。

class MyClass {
  def hoge1: Int = {
    val x = 1
    lazy val y = 1
    x + y
  }

  def hoge2: Int = {
    val x = 1
    lazy val y = 1
    x + y
  }
}
public class MyClass extends java.lang.Object implements scala.ScalaObject{
    public int hoge1();
      Code:
       ....
       24:  invokespecial   #21; //Method y$1:(Lscala/runtime/IntRef;Lscala/runtime/VolatileIntRef;)I
       ....
       27:  iadd
       28:  ireturn

    public int hoge2();
    private final int y$1(scala.runtime.IntRef, scala.runtime.VolatileIntRef);
    private final int y$2(scala.runtime.IntRef, scala.runtime.VolatileIntRef);
    public MyClass();
}

objectはstatic

objectはシングルトンオブジェクトを定義するものだが、JVM上ではstaticとして解釈される互換性のためstaticメソッドも用意される

class  MyTest { val inClass  = 1 }
object MyTest { val inObject = 2 }
% javap MyTest MyTest$
public class MyTest extends java.lang.Object implements scala.ScalaObject{
    public static final int inObject();
    public int inClass();
    public MyTest();
}

public final class MyTest$ extends java.lang.Object implements scala.ScalaObject{
    public static final MyTest$ MODULE$;
    public static {};
    public int inObject();
}

*1:コメント欄も参照

2012-01-05(木)

Test::LeakTrace についてメモ書き

ハマったので未来の自分のためにメモ書きを残しつつ、今年もよろしくお願い致しますm(_ _)m。


Test::LeakTraceの特性

Test::LeakTrace で、以下のテストは通る。

my %x;
no_leaks_ok { $x{x} = undef };

しかし以下のテストは通らない。

my @x;
no_leaks_ok { push @x, undef };

これは、@x にエントリが増えた分がリークとしてカウントされるから。じゃあ前者はなぜ大丈夫なのかというと、no_leaks_ok はブロックを2回実行して2回目でリークをカウントするせい。1回目の実行で%xのバケットが1つ増えるが、2回目以降は再利用するのでリークとしてカウントされない。

あと、次のコードは "not ok 1 - leaks 1 <= 0" と言ってる癖にダンプを表示しない。

my %x;
my $k = 0;
no_leaks_ok {
    $k = ($k + 1) % 2;
    $x{$k} = undef;
};

これはダンプを作る際にさらに改めて2回ブロックを実行する仕組みになっているため。この現象は2回目の呼び出しまではメモリが増加するけど3回目以降は増加しないような場合に起こる。

Test::LeakTrace はこのように複数回ブロックを呼び出してリークを判定・調査する特性があるので、メモリの使い方が安定した状態で呼んだ方が懸命である。


Test::LeakTrace と AnyEvent

AnyEvent のコードはコールバックを中心に書くため、リークしやすい。なので、 Test::LeakTrace でリークしてないことを確かめるのはよいプラクティスだと思う。

しかし AnyEvent は実際の初期化処理を任意のメソッドの使用時にまで遅らせている。そのため、任意のタイミングで初期化処理が走る可能性があり、それが Test::LeakTrace の結果に影響を及ぼすかもしれない。よって、 AnyEvent::detect を先に呼んで初期化を終わらせておいた方が変にハマらずに済む(気がする)。

2011-12-28(水)

2011年のまとめ

2009年のまとめはあるのに2010年のまとめがないことに気がつきましたが気にしない。

  • CLTT読書会に割と出た
  • #yhiosに割と出た
  • 各種.pmに割と出た
  • スタートHaskellに少し出た
  • Awodey読書会に少し出た
  • Java EE勉強会に少し出てClojureした
  • GAEにパッチ送った
  • (お仕事で)レガシーなのをPSGI化した
  • Git勉強会で話した
  • 揺れた
  • 計画停電時間検索ツールをDIS手伝った
  • (お仕事で)iPhoneアプリ書いた
  • Coqに触れた
  • 論理と計算のしくみ読んだ
  • 書くことなくなってソース読むシリーズでお茶を濁した
  • JSゲーム製作勉強会に出た
  • 函数プログラミングの集いに出た
  • RIP Steve Jobs
  • YAPCのスピーカーデビュー
  • YAPC::ASIA 2011 をレポートした
  • Hokkaido.pm のおかげで結婚した
  • Allowsの論文読んだ
  • #fcsap にお邪魔した
  • Hacker Track に書いた
  • node.js に触れた
  • Scala に触れた

今年は計算論方面にどっぷり染まった1年でしたが、今後はどうするか迷うところです*1。また、来年は大きな変化も訪れる予定です。そんな変化の年を迎えるにあたり、ブログ名もシンプルにしてみました。


それでは皆様、よいお年をお迎え下さい。

*1機械学習に戻りたいんだけど、勿体ないし面白いしという泥沼orz

2011-12-25(日)

Connectのソースを読む(2)

前回の続きで、Connect 1.8.4のソースを読む。今日はミドルウェアから適当に抜粋。残ってるのはミドルウェアだけなので、今日で終わりのつもり(気が向いたら残りも書くかも?)。


middleware/logger.js

アクセスログを書き込むだけだが、connectのミドルウェアの仕組みではリクエスト完了後に割り込むことができない*1のでhackが必要となる。

具体的に言うと、res.endをラップして終了処理に割り込む。そして、何もせずnextを呼べば、リクエストの完了時に割り込んで処理をすることができる。ただし、immediate オプションを渡した場合はこのhackを行わず、ミドルウェアに処理が移った段階でログを吐く*2

ログに使える:date、:method などは全てloggerオブジェクトのプロパティにメソッドとして定義されており、req, res, field の3引数を渡すことで値を取り出せる。ログのフォーマットとして文字列を渡すと、これらのメソッドを適宜呼び出すような関数へコンパイルされる。


middleware/errorHandler.js

4引数を持つ唯一のミドルウェア。4引数のミドルウェアはエラーハンドリングのために呼び出される。

~accept.indexOf の形式が多用されているが、ビット反転すると-1が0になることを使っていて、文字列が見つからなかった場合にのみfalseとなる。

内部はAcceptヘッダを見てレスポンスをhtml、json、text に分けてスタックを出力するだけの簡単な処理。


utils.pause, resume

ミドルウェアから使われているutils.js 内の関数。任意のObjectについてpauseすると、以後のdataイベントとonイベントがキャッシュされる。キャッシュされたイベントはresumeで再び元のObjectへ渡される。

ミドルウェアから非同期で後続のミドルウェアへ処理を渡そうとする場合は、このメソッドを使う必要がある。そうしないと、後続のミドルウェアがreqからデータを読むためにdataイベントやendイベントを拾おうとしている場合に、それらのミドルウェアがリスナーをしかける前に発生したdataやendのイベントが全て捨てられてしまう。


middleware/basicAuth.js

BASIC認証の実装。Authorization ヘッダがあって認証が成功していればreq.remoteUserへユーザ名をセット、そうでなければ401ステータスを返して認証を促す、というのを愚直に実装している。

basicAuth()の引数の取り方は3パターンで、「user, pass, realm」「cb(user, pass), realm」「cb(user, pass, cb(err, user)), realm」の3通り。最後のパターンは非同期で認証を行うパターンで、認証が完了したらcbへエラー、またはユーザ名を返せばよい。


middleware/router.js

Sinatraライクのルータの実現。ミドルウェアの中でソースは一番行数が多い。

router関数の中で、まずDSLを実現するためにgetやらpostやらのメソッドを持つmethodsというオブジェクトを作っている。利用者はこいつのgetやらpostやらへルーチング情報を渡せばいいというスンポー。getやpostには、Sinatraなどと同じようにPATHとハンドラを渡せるが、ハンドラに加えてミドルウェアも任意個指定ができる。

後、ドキュメント化されてないapp.paramって関数が生えていて、こいつを呼ぶとparamsという変数へコールバックを貯められるが、この辺の実装はかなり怪しい。要はルーチングでマッチしたパラメータに前処理するためのコールバックなのだけど、普通の関数の他にcb(req, res, next, val) というミドルウェアっぽい(けど違う)引数をとるコールバックも指定できて、整理されてない感がある(非同期対応が必要なのはDBから値を読むことを想定してるんだろうけど)。

ルーチングの情報はroutesオブジェクトへメソッドごとに分けて保存される。getやらpostを呼んだときの処理を作っているのがgenerateMethodFunction で、ここでは基本的にroutesの中へルーチング情報を詰め込むだけ。PATHの正規表現へのコンパイルはnormalizePath関数が行う。

本丸は内部のrouter(req, res, next)関数でこいつが実際にルーチングをする。match関数によってマッチするルートがあるか調べられ、マッチした場合はキャプチャーした値がfn.params へ保存される*3

さらに内部にあるparamという関数は、前述のparamを前処理するコールバックを反復的に適用するために再帰呼び出しされる関数。これはcb(req, res, next, val)のシグネチャを持つ前処理関数が非同期呼び出しに対応しているために必要となる。なお、この前処理の最中でnextへエラーとして'route'という文字列を渡すと、このハンドラでの処理を諦めて大域脱出し、後続のルーターのマッチ処理へ戻る。

paramの再帰の最後で、ハンドラと共に指定したミドルウェアを適用するため、さらに内部にあるnextMiddleware という関数による再帰が起こる。ここでもミドルウェアがnextへエラーとして'route'を渡すと大域脱出できる。ここのミドルウェアの適用は自前で処理しているが、app(req, res, nextMiddleware) の形式のものしかサポートしていないため、エラー処理をするfn(err, req, res, nextMiddleware)の形式のミドルウェアを渡しても機能しない。

ここまでを経て、ようやく最終的にユーザがgetやpostへ指定したハンドラがキックされる。ハンドラ内からnextを呼ぶと、次のルーチングの検索へ移る。その必要がない場合はres.endでレスポンスを完了すればよい。

最終的にマッチするルートがなかった場合は、OPTIONSメソッドの場合はAllow:ヘッダ入りのレスポンスをするか、それにも該当しなければ後続のミドルウェアへ処理を渡す。

作成されたrouterミドルウェアには、.remove, .lookup, .match のメソッドが生えており、作成後にルーティングを削除したり、どんなリクエストがマッチするのかテストしたりできる。

*1:リクエストを完了すると、後続のミドルウェアには処理を移さないため

*2:immediateを指定した場合は、当然ステータスコードなどを記録することはできない

*3:fn はgetやpostに渡したハンドラで、こいつを処理結果の共有の(グローバルの)一時保存場所にしててすごくよくない。paramsという名前もコールバックを保存する変数名と被ってて、この辺の実装はすごくよくない。