Twitterのライブラリを書きたい

まだPerl6実装のTwitterクライアントが存在しないと聞いて、ゴリゴリ実装中です。完成までにまだまだ時間は掛りそうですが、完成したらGitHubで公開しようと思ってます。

書いてる上で思った事、詰まった事を何点か。

モジュール無い

はい、致命的ですね。OAuthの署名に使うHMAC-SHA1というダイジェストの方式があるのですが、まずSHA1を計算するモジュールがありません。もちろんHMAC署名を生成するモジュールもありません。これが無いとOAuthはどうにもならないので、いまはココを書いています。

SHA1

Pure Perl6でSHA1ダイジェスト関数を実装すると死ぬほど遅いのは目に見えてるので、/tmp以下にメッセージをダンプして、sha1sum(MacOSXだとshasum)に投げています。OOっぽいインターフェースと手続きっぽいインターフェースを両方準備したせいで、かなり行儀悪いコードになっている気が…。add(),addfile(),hexdigest()などPerl5のDigest::SHA1と同じインターフェースを用意しています。

HMAC-SHA1

これもPerl5のDigest::HMAC_SHA1を意識した設計にしているのですが…。

class Digest::SHA1 {
      method add (Str $s) { ... }
      method addfile (Str $file) { ... }
      method clear () { ... }
      multi method hexdigest () { ... }
      multi method hexdigest (Str $s) { ... }
}

role Digest::HMAC {
     method new (Str $key) { ... }
     multi  method hexdigest () { self.SUPER::hexdigest()を使って計算 }
     multi  method hexdigest (Str $s) { self.SUPER::hexdigest()を使って計算 }
}

理想としてはこんな感じ。コレが上手く書けるなら、任意のDigest::Hに関して以下のようにHMAC版クラスを作る事が出来ます。

class Digest::HMAC::H is Digest::H does Digest::HMAC { }
my $hmac-h = Digest::HMAC::H.new("key");

# もしかしたら以下のように書けるかも
# 少なくともRakudo Star 2011.04では動かない

my $hmac-h = (class Digest::HMAC::H is Digest::H does Digest::HMAC { }).new("key");

ただ、「理想」と言ったのは、現行のStar 2011.04ではnextsameやSUPER疑似パッケージが使えないため、継承元のオーバーライドされたメソッドを明示的に呼び出せないためです。なので、role Digest::HMACはhexdigest()という名前でなく、hmac-hexdigest()という名前でダイジェストを計算する仕様になっています。処理系が改善されれば、順次改良して行きたいですね。

ビット演算とか

Perl6にはビット列のようなクラスは標準ライブラリでは用意されていません。基本的にはInt型で問題は無いはずでしたが…。

HMAC-SHA1の署名を生成する際に、どうも排他的論理和が上手く計算できていないようなのです。いろいろ覗いてみた結果、Int型が512bitのデータを保持できずに桁溢れしている事が判明しました。仕様ではInt型は多倍長の整数を保持できるようになっているようですが、現行の実装では符号付き64bit整数までしか格納できません。これはParrotのデータ型に依存してるんじゃないかなーという所です。@uasiさんに教えて頂いた所によると「GMPのParrotバインディングが開発されているので完成すれば進展あるかも」とのことです。

infix:<~^>を使う事で文字列同士の排他的論理和を取る事が出来るのですが、生バイナリをそのままStrに突っ込むと"Invalid operation on binary string"と怒られてしまったり、良く無い事が色々起きてしまっていて悲しいので、BitStreamクラスを作るかなーというのを考えています。単純にIntリストのラッパークラスを作るだけ…だと良いのですけれども。

オマケ

ビット演算周りで得たノウハウを幾つか

# ASCII文字列を0/1のビット列へ
$str.comb>>.ord>>.fmt("%08b").join;

#逆
$bin.comb(/.**8/).map({("0b" ~ $_).chr}).join;

# 16進数を10進数に
("0x" ~ $hex).Int;

#逆
$dec.fmt("%x");

# 16進数列を文字列に
$hex.comb(/../).map({("0x" ~ $_).chr}).join;

# 16進数列をビット列へ
$hex.comb>>.map({("0x" ~ $_).fmt("%04b")}).join;