2011年1月9日日曜日

mikutterをRuby1.9.1で動作させる

mikutterは、いままでRuby 1.8.7専用で開発してきた。
理由は、GUIツールキットとして採用しているRuby-Gnome2が、Ubuntuでは1.8用にビルドされたものしか用意されていないからだ。もし1.9を採用するなら、mikutterを使うためにRubyGtkをわざわざソースコードからビルドしなければならない。一方、1.8を採用すれば、apt-getで全て手に入る。

Ruby1.8と1.9では大きな変更があったが、両方で動くコードは充分に実現可能だ。そこで、今回はmikutterを両方で動くようにしてみた。

なぜ1.9を使うのか

では、1.9の追加機能を使わずにサポートするメリットとはなんなのか。主な理由は
  1. 速くなると思われる
  2. 1.8は古いので、いずれ1.9にシフトしていくだろうから
  3. Twitterでmikutterの開発協力者にそそのかされた
だ。とくに最後の要素が非常に大きい。
1.9系列の最新版は今のところ1.9.2だが、今回はubuntu 10.04で手に入る最新の1.9.1を使用する。

やりかた

1.9で動作させるには、trunkを使う必要がある。
参考サイトには10.10用と書いてあったが、10.04でも同じ手順で問題なかった。ただし、現在Ruby-Gtkはより新しいバージョンがリリースされている。
また、Ruby-GtkのフルセットとGtkのみが入ったパッケージがあるが、mikutterを動かすならGtkだけで良い。
加えて、READMEに書いてあるライブラリも1.9用のものを入れる必要がある。また、libhmac-sha1は1.9用のものが用意されていないので、gemでインストールする必要があった。
# apt-get install rubygems1.9.1
# gem1.9.1 install ruby-hmac
mikutterはrubyコマンドで起動するようになっているが、この方法ではruby1.9.1というコマンド名でインストールされる。そのため、
$ ruby1.9.1 mikutter.rb
のように起動しなければいけない。通常通り起動すれば、1.9.1をインストールした後でも1.8で動作させることができる。

どう変わったか

私の開発環境では、起動時間に圧倒的な差があった。1.8が23秒であったのに対し、1.9は7秒で起動できた。実に三倍である。
mikutterはリプライ元など取得に時間がかかる可能性のあるものは遅延して表示するが、起動時間にはこれは含まれていない。あくまでつぶやきが表示されるまでの時間だ。
しかし、その遅延して表示されるウィジェットもいずれも高速に表示され(見た感じ倍速程度)、非常に快適になった。
高速化のための遅延レンダリングが露骨に見えることも多かったが、1.9を使えばそれも殆ど見えなくなった。

今後

mikutterは非常に高度で複雑なウィジェットの配置をしている。UIを妥協なしに実装したいがためであったが、マシンパワーを異常に浪費してしまっていた。そこでCairoによるタイムラインのレンダリングを検討していたが、1.9を採用すれば充分に高速化されることが期待できるので、このまま様子を見ることにした。
更に改善を続ければ、1.9でも問題なく使えるようになるだろう。

2010年12月26日日曜日

mikutterで振り返る一年

CHI

http://hakurei-shain.blogspot.com/2009/12/computer-humanoid-interface.html

2009年12月26日に、computer humanoid interfaceとかいうタイトルの記事をポストしていた。9月くらいから書き始めた監視スクリプトだが、25日にリリースし、翌日にこの記事を書いたもの。どういうものかというと、

  • これをインストールしたサーバ(マシン)のあらゆる状態を監視し
  • 異変があればTwitterにポストする
  • 欲しい機能はプラグインで拡張できる
  • 会話支援機能などが付いていて、Twitter上でリプライを飛ばしあうようなプラグインが簡単に作れる
というもの。現在もメンテナンスはしていて、わがサーバを監視しているlime_toshiaや、監視プラグインを全て外して独自のプラグインを入れて稼働しているkirimaru_botがある。
これをリリースする少し前から、GUIプラグインを入れたらTwitterクライアントになるんじゃないか、と考え始めていた。それが現在のmikutterだ。

一年で起こったこと

理想(最初のUI設計)

現実

これが最古の写真(理想はイラスト)。今には程遠いように見えるけれど、ほとんどこの時に今の原型ができていた。
今にも受け継がれている特徴は

  • できるだけコンパクト
  • リプライ入力欄がつぶやきのすぐ下に現れる
  • どのつぶやきへのリプライかが簡単に分かる
リプライ入力欄をすぐ下に出現させるのは実装し始めてすぐに思いついたアイデアだった。これはたくさんのスレッドがひとつのタイムラインに絡まり合って存在しがちなタイムラインにはなかなか良いUIだと思っている。同じようなTwitterクライアントはいくつかあるが、このような芸当はウィンドウのパーツの制約で難しいことが多いので、ほとんどのクライアントはひとつのつぶやき入力ウィンドウを使っているのが現状だ(尤も、好みの問題もあるのでその限りではない)。
ツールチップで何に対してのリプライかを表示するのは、RubyGnome2のバグにハマって途中でやめてしまった。それがなければずるずるツールチップに表示していたかもしれない。(そんなことはないか)

逆に考えをあらためたこともあった。
  • DM
  • Twitterでできることを全てできるように!という方針
mikutterを開発している間に、りむったーというサービスが終了した。これは、リムーブされたらDMで通知してくれるというサービスだった。これが終了すると、めっきりDMが来なくなった。普段使わない機能を実装する気にはなかなかなれない。
Twitterでできることを全てできるようにしようと思ったが、時間的な制約もあるので、実装を遅らせるのもよしとすることにした。TwitterのAPIに対応するより、それ以上の機能を実装したほうが良い場合もあると考えているからだ。

他のTwitterクライアントにも様々な変化があった(以下、覚えている範囲で書いているのでいろいろ間違っているかも知れない)
  • Twitter Webの刷新。重くなったとも言われているが、画像のプレビューやyoutubeなどに対応、外部サービスをTwitter Web上から使えるようになるというのは前代未聞だった。
  • 多くのTwitter クライアントが返信元やスレッド機能を実装してしまった。mikutterは会話を追うことを得意としているので、差別化は困難になった。
  • 開発をはじめてしばらくしてTwitterクライアントが大量にリリースされた。しかしながら、LinuxでのクライアントはUIや機能がパッとするものが少なく、今のところmikutterが少しリードしている、と思う。
  • TwitterがGoogleなどにつぶやきの情報を提供するようになった。これによってGoogleでリアルタイム検索(つぶやき検索)ができるようになった。favstarの収集がやたらと速いが、たぶんこれだと思う(よく知らない)。
開発は常に絶え間なく行なっていたが、途中様々なTwitterの仕様変更があり、時間を取られたりもした
  • 公式リツイート。これにより、従来のリツイートが「非公式RT」と呼ばれるようになり、恰好の論争のネタになっている。
  • OAuth。8月31日を以てBasic認証でTwitterAPIを叩けなくなった。mikutterは6月末くらいに対応して、この時から投稿クライアントに「from mikutter」と入るようになった。この日を境に多くのメンテナンスされていないTwitterクライアント、BOTが使えなくなった。
  • ジオタグ。つぶやきをどこでしたか、位置情報を含められるようになった。多くのPC用クライアントでは非対応(そりゃそうだ)
  • Streaming API。mikutterは製作開始時点からこれを視野に入れていたが、今ではUser Streamは一般的になっている。クライアントによってはオプションになっているが、mikutterではUser Streamは標準。Twitterの使い方が大きく変わった。
  • ユーザリスト。1ユーザにつき20個のリストを作ることができ、そのリストのTLを見たり、人の公開リストをフォローしたりできる。対応しているクライアントは今でも少ないが、mikutterは不完全ながら最低限の対応はしている。
こう見ると、この一年はTwitterにとっても大きく変わった一年だったことがわかる。とくにReTweet、UserStreamは私たちのTwitterの使い方を大きく変えた。また日本ではメディアに取り上げられたりドラマが放映されたりして大きく認知度が上がり、ユーザ層が大幅に変わって、それに併せて周辺サービスの性格も変わってきた。
Twitter自体の安定性が上がったことは誰もが感じているはず。一週間何もトラブルがなければ不安になっていたものだが、今やTwitterAPIに不具合があるとTwitterクライアントの作者に苦情がいくこともあるそうだ(mikutterはユーザ層的にないけれど・ありがたい)。

それ以外にmikutterを取り巻く環境にも様々な変化があった。
  • RubyGnome2が安定してきた。最初はかなり不安定で採用したことを正直後悔していたが、最近は不具合も減り、mikutterは確実にそのパフォーマンスを上げている。
  • 協力者が出てきた。パッチを送ってもらったことにより様々な機能がついた。またブログ等で紹介してくれる人も何人か出てきて、様々なOSでの動作報告、パッケージ化がなされた。特設サイトにはドメインも提供してもらった。
  • 利用者が思ったより増えた。Linuxでも動く優秀なTwitterクライアントが出現する見立てだったが、今のところ満足できるものはあまりない。AirやJavaを使ったものはLinuxでも動くが、どうしても対応が疎かになっているようだ。その中でmikutterはUbuntu Linuxに主軸を置いており、それを買ってか利用してくれるユーザが確実に増えている。
個人的にも、これほど長期にわたって開発を続けることができたプロジェクトも初めてだし、これだけ多くの人を巻き込んで開発するという貴重な体験をさせてもらえることにとても感謝している。いろんな事を勉強できたので、来年はそれを還元していける一年にしたいとも思っている。ほかに、自分自身にはこんなメリット・デメリットがあった。
  • Rubyの理解が深まった。去年に比べて大幅に理解が進んだ。
  • ソフトウェア開発に対する理解も深まった。
  • Twitterがより楽しくなった。
  • 廃人度が上がった。知らないうちにつぶやいていたり、ふぁぼっていたりする。どうしたものか。
制作開始当初は、Twitte.rbを目標にしていたが、機能的には(一部実装していないものがあるが)超えたと思う(目指している方向が違う気もするが)。
これからは安定化を図りつつ、プラグインとして追加で提供する機能でTweenやTweetDeckに追いつくことを視野に入れて開発していきたいと思っているので、今後とも宜しくお願いします。


2010年8月27日金曜日

Rubyでメソッド定義のときに引数の型をチェック

例えば、以下は二つの文字列引数xをとり、x self xを連結するメソッドsandのRubyによる定義。

class String
  def sand(x)
    x + self + x end end

これは一見うまく動くようだが、

"hello".sand("'") # => 'hello'

実は文字列を指定しなければエラーとなる。

'hello'.sand(1) # =>
# ~> -:2:in `+': String can't be coerced into Fixnum (TypeError)
# ~>    from -:2:in `sand'
# ~>    from -:5

原因は、xに渡された1(Integer)の+メソッドは、String型を渡すとTypeErrorを投げるから。文字と数字が足せないのは当たり前なので、妥当だといえる。
このようなエラーを実行前にトラップするために、静的型付けが有用だとされてきたが、事実Rubyではこの初歩的なバグは実行時ならないとわからない。
もちろん言語仕様上コンパイル時にエラーを上げることなど不可能なので、エラー処理をわざわざ入れるというのはよくやることだ。ここでは、sandを「エラー時にはnilを返す」ようにする(ここは、例外を投げるようにしてもいいかもしれないが、とりあえず)。

class String
  def sand(x)
    if(x.is_a?(String))
      x + self + x end end end

これで、Integerを引数に指定するとnilを返すようになる。しかし、このような煩雑な処理を毎回書いていると冗長になるし、オブジェクト志向言語では、型ではなくて「特定のメソッドが定義されていれば」など込み入った定義もしたくなる。そこで、これらをできるだけ簡単に判断できるようなユーティリティを書いてみた。

def type_check(args, &proc)
  check_function = lambda{ |val, check|
    if check.nil?
      nil
    elsif check.respond_to?(:call)
      check.call(val)
    elsif check.is_a? Array
      val.__send__(*check)
    elsif check.is_a? Module
      val.is_a?(check) end }
  error = args.find{ |a| not(check_function.call(*a)) }
  if(error)
    warn "argument error: #{error[0].inspect} is not passed #{error[1].inspect}"
    nil
  else
    proc.call if proc end end

関数type_checkは、すべてのチェックにパスしたら渡されたブロックを実行してその結果を返す関数。いずれかのチェックに失敗したら、エラーを出力してnilを返す。
チェックは、値 => 判定式 のような連想配列もしくは[[値,判定式],...]のような配列。
使える判定式は、

  • nil ... 常にパス
  • [symbol, *args] ... 値.send(symbol, *args)がtrueを返せばパス
  • lambda{|x|} ... 値を引数に取る無名関数(厳密にはcallメソッドが定義されているもの)。trueを返せばパス。
  • Module ... その型とis_a?関係ならパス

例えば以下のように使う。

type_check([[1 , Integer], [2 ,[:is_a?, Integer]]]){ :passed } # => :passed

これを利用すれば、sandは以下のように定義できる。

class String
  def sand(x)
    type_check(x => String) do
      x + self + x end end end

利点は、 x => Stringは、「xはString」のように自然言語に読み替えやすいことか。このままでは微妙なので、型チェックを伴う無名関数を定義するメソッドを定義してみる。

def tclambda(*args, &proc)
  lambda{ |*a|
    if proc.arity >= 0
      if proc.arity != a.size
        raise ArgumentError.new("wrong number of arguments (#{a.size} for #{proc.arity})") end
    else
      if -(proc.arity+1) > a.size
        raise ArgumentError.new("wrong number of arguments (#{a.size} for #{proc.arity})") end end
    type_check(a.slice(0, args.size).zip(args)){
      proc.call(*a) } } end

type_checked_lambdaは、lambdaとほぼ同じように使うが、ブロック以外に引数を取る。それは、各引数のチェック式になる。
例えば、配列aryの中の数を2倍にして返したい場合、以下のようにできる。

ary.map(&tclambda(Integer){ |x| x * 2 })

こうすれば、aryに数値以外がはいっていれば、そこはnilとなる。かなりすっきり書けた。
これがあれば、型をチェックするdefを定義することもでき、それを使えばsandの定義はかなりすっきりする。

class Object
  def self.defun(method_name, *args, &proc)
    define_method(method_name, &tclambda(*args, &proc)) end end

class String
  defun(:sand, Integer) do |x|
    x + self + x end end

このように、やや強引ではあるが、型を定義したメソッドを定義できた。できるだけこれでメソッドを定義すれば、エラー箇所がより明確になるかもしれない。

動機

関数、とくに自分の書いたものは信用してしまいがちで、この類のエラーは後を立たない。今回は、静的型付け言語の引数チェックを取り入れられないかという実験の一環でコードを書いてみた。
こういったメタなコードを書くたび、Lispへの憧れが深まっていく(笑)。

2010年7月24日土曜日

Rubyで遅延評価

いろんなところで既にやられているけど、自分でやってみたくなったので。
遅延評価とは、計算結果が実際に必要になるまで処理を遅延すること。

a = huge_proc()
b = large_proc()
print a

この仮想コードのhuge_procの呼び出しは、3行目のprintで出力されるまで遅延される。また、bは使われていないのでlarge_procは呼び出されない。
こういった仕組みがあれば、処理に時間がかかるが戻り値を使うかどうか分からない関数をムダに実行することがなくなるので、ある種の処理は非常に早くなる。これをRubyでやりたい。

実装

今回作ったユーティリティは、lazy{ 遅延させたい処理 }のように使い、戻り値として遅延オブジェクトを返す。
使い方は例えば、a = lazy{ x }と書けば、lazy自体はすぐに遅延オブジェクトを返し、aのメソッドが呼ばれたときに初めてxが実行される。

class Lazy
  def initialize
    @proc = Proc.new
    @obj = nil end

  def method_missing(method, *args, &block)
    if @proc
      @obj = @proc.call
      @proc = nil end
    @obj.__send__(method, *args, &block) end end

def lazy(&proc)
  Lazy.new(&proc) end

method_missingは、メソッドが無かった時に呼び出されるメソッドで、このメソッドはブロックの戻り値のオブジェクトのメソッドを呼び出す。これによって、遅延オブジェクトから実行結果のオブジェクトのメソッドを透過的に呼べる。

竹内関数によるベンチマーク

試しに、竹内関数(たらい回し関数)の遅延版と通常版と最適化版を作って、どちらも同じ引数(12,6,0)を指定して、速度の差を計測してみた。

def tak(x, y, z)
  if x <= y.to_i
    y
  else
    tak(tak(x-1, y, z), tak(y-1,z,x), tak(z-1,x,y)) end end

def ltak(x, y, z)
  lazy{
    if x <= y.to_i
      y
    else
      ltak(ltak(x-1, y, z), ltak(y-1,z,x), ltak(z-1,x,y)) end } end

def btak(x, y, z)
  if x <= y.to_i
    y
  else
    btak(btak(x-1, y, z), btak(y-1,z,x), lazy{ btak(z-1,x,y) }) end end

a = Benchmark.realtime{ tak(12,6,0).to_i } # => 9.08182191848755
b = Benchmark.realtime{ ltak(12,6,0).to_i } # => 0.00162792205810547
c = Benchmark.realtime{ btak(12,6,0).to_i } # => 0.000227928161621094
a / b # => 5578.78178090217
a / c # => 39845.1066945607
b / c # => 7.14225941422594

関数ltakは、takの内容をlazyで囲っただけで、takは素直な竹内関数の実装。btakは、takの第一引数と第二引数は必ず必要になる=遅延する必要がないので第三引数だけを遅延したバージョンだ。

通常9秒かかるこの計算が、ltakだと10ミリ秒に、btakでは2ミリ秒になり、btakはtakに比べて39845倍の速度になった。

まとめ
当然だが、遅延オブジェクトの生成にもオーバヘッドがあるので、必要以上に使うことは却って速度の低下につながるので避けたい。
また、この類のコードは汎用性が高いので、もうすこし実装を詰めてもいいかもしれない。
例えば、is_a?等、Objectクラスのメソッドが適切に動くようにすれば、関数の内部の処理を考えずに遅延オブジェクトを渡したり返したり出来るだろう。

2010年7月18日日曜日

Macのターミナルの配色が絶望的なのでなんとかした

Macで標準のターミナルを使っていると、カスタマイズができなくて困ることがある。
特に、デフォルトの色はあまりにも残念で、ディスプレイと環境光によっては青が全然見えなかったりするが、これは通常編集できない。

先日、初めてフルHDのディスプレイを購入したが、昨日から梅雨が明けて太陽が出るやいなや、ruby-modeの関数名が全く見えなくなってしまった。
そこで、TerminalColoreopardをインストールして、色を変更することに。


詳しいインストール方法や使い方はリンク先に任せるが、あとは環境に合わせて見やすい色にすればよい。

そういえば、このディスプレイを買ってから、晴れた日に部屋でコードを書いていなかった。

2010年7月7日水曜日

Twitterの規制について気づいたこと

たまにはこんな話でも。クライアントやBOTを作っていて気づいたことを幾つか。

Twitterをクライアントソフトウェアを通して使っている人は多いと思うが、一時間ごとに問い合わせられる回数が決まっており(規制)、その回数制限に悩まされている人が多い。その規制の種類や、回避策、今後の変化を総合的に考察してみたい。

ここでいう規制とは、データの取得にかかる規制のことで、投稿とは関係ない。投稿は、TL取得規制がかかってもすることができるので、実は規制されてもクライアントソフトウェアでつぶやくことはできる。

まず、規制には以下の種類がある(以下に書いた名前は正式名称ではない/括弧内は1時間あたりの回数制限)
  1. OAuth規制(175)
  2. Basic認証規制(75)
  3. IPアドレス規制(150)
  4. Streaming API(∞)
OAuth認証規制
OAuthは新しい認証方式で、コンシューマ(ふぁぼったーやTweenのようないわば2次サービス)がそのユーザのパスワードを知ること無くアクセス出来る認証方式。これを使うと、規制回数がBasic認証の10倍にあたる1500回になる。・・・のだが、最近では350に引き下げられてしまった・・・と思ったら、従量制APIが導入され、Twitterの負荷によってAPIの規制回数が変動するようになった。今は175に固定されているらしい。誰もが予想したとおりの展開だった。

Basic認証規制
HTTPのBasic認証を使った認証方式。単純で実装が用意だったが、パスワードをコンシューマに教える必要があり、危険だったのでOAuthに取って変わられる。150回の制限だったが、今は75回になったらしい。もともと、6月いっぱいで終了する予定だったので、今となっては殆ど誰も使っていないのではないだろうか。

IPアドレス規制
意外と知らない人が多い制限その1。
実は、Twitterには、つぶやき検索や公開リスト、つぶやきIDを指定してつぶやきを取得など、ログインせずに見ることが出来る情報が結構ある。ログインせずに見ることが出来るのだから、OAuthやBasicの回数制限とは関係ない。
しかし、コンピュータに割り当てられる※本当は違うけど勘弁IPアドレス毎に、毎時間150回までと、ここでも規制がかかっている。OAuthやBasicとは当然別にカウントされているので、クライアントを作るときは可能ならこちらを使うようにしたほうがユーザには親切ということだ。

Streaming API
知られていないその2。
Streaming APIは、定期的にツイッターに問い合わせるのではなく、ずっとつなぎっぱなしにしておき、新しいつぶやきがあれば順次向こうから送ってくるというもの。その性質上、回数制限はない。Basic認証で認証されるため、パスワードが必要。このAPIに対応しているクライアントはあまりないし、時々取りこぼしがあるようだが、一切規制の心配がないし、つぶやきが投稿されたら数秒でそれが取得できる。

で、
Streaming APIは置いておくとして(クライアントの対応次第なので)、実はIP回数制限を、通常のタイムライン取得に使うことが出来る。
公開リストは、取得するためにログインする必要がない。つまりIPアドレス規制の範囲で取得することができる。
ということは、フォローしている人全員が入ったリストを作っておけば、OAuthやBasic認証が規制されてもクライアントソフトウェアから見ることが出来る(IPアドレス問い合わせに対応していない、リストに対応していない場合は不可能だが)。
また、似たような方法でリプライも取得することが出来る。検索もIP問い合わせで取得できるので、「@ユーザ名」で検索をかければ、自分のリプライがすべて見られる(なお、この方法を使えば、誰に対するリプライでも見ることが出来る。自分宛以外でも)。
問い合わせ方法を上手に使い分ければ、規制に悩まされることも減るのではないだろうか。

クライアントソフトウェアの対応
現在はまだテスト中らしいが、chirp user streamを使えば、規制の影響を受けずに、わずか数秒のタイムラグでつぶやきを受け取ることが出来る。しかも、フォローされたとき、誰かが自分のつぶやきをお気に入りにいれたとき(ふぁぼられた時)にも、数秒でわかる。これは今後どうなるかわからないが、Twitter使い方が大きく変わるだろうし、規制を過去の遺物にならしめることだろう。
しかし、これが一般に利用されるようになったとしたら、Twitterのサーバは大丈夫なのだろうか。少し心配だ。

2010年6月17日木曜日

Subversionには実行権限を記録させておけるらしい

% svn propset svn:executable 1 mikutter.rb
property 'svn:executable' set on 'mikutter.rb'

なんでも、実行権限があればいい具合に扱ってくれるので、今まで知らなかった(忘れていた?)らしい。ぐぐっても、誤って付与されてしまった実行権限を取る方法のほうがよく出てくる。

1ってなんなんだろう。最近gitが流行りなので、Subversionのバッドノウハウにはすっかり興味がなくなってしまった。