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のバッドノウハウにはすっかり興味がなくなってしまった。

2010年6月13日日曜日

あの言語は今

あの言語は識者の助言からMIKUという名前になり、今もちょっとづつ開発が進んでいる。
とにかく、今は無名関数が使えるようになったのでいわゆるアキュムレータを返す関数が定義出来る。
すなわち、関数genacmは、「数値をとり、過去にとった数値と合計した値を返す関数」を返す関数だ。


(setf 'genacm (lambda () (set 'now 0) (lambda (inc) (set 'now (+ inc now)))))
(setf 'a1 (genacm)) ; => #
(setf 'a2 (genacm)) ; => #
(a1 1) ; => 1
(a2 2) ; => 2
(a1 3) ; => 4
(a2 4) ; => 6


パーサが前に書いたようなルールで読み取ってくれるならこう書けるはずだ。


setf[genacm {()
now = 0
{(inc) now = inc + now}]
setf[a1 genacm[]] ; => #
setf[a2 genacm[]] ; => #
a1[1] ; => 1
a2[2] ; => 2
a1[3] ; => 4
a2[4] ; => 6


例示しておいてなんだけどシンタックスは特に触れない。
注目すべきは、a1とa2は、何れもgenacmが作った関数であり、それは毎回nowに引数を加算するだけだが、nowはgenacmが呼び出される度に新たに作られる(a1とa2のnowは別物)。レキシカルスコープを持ったクロージャだ。
MIKUで定義した無名関数は、Rubyにおける無名関数と等価だ。つまり、Rubyのメソッドにそのまま渡すことが出来る。
問題は、Rubyでは無名関数を渡すときに「ブロック引数」として渡す。この特別扱いによってさも構文を拡張したかのように振る舞えるが、MIKUから渡すには新たにシンタックスを加えることになる。


ary.each{ |x| p x } # eachメソッドにブロック引数を渡している


Rubyに似せるならこんな方法があるだろう。


each[ary |x| p x]


|が出てきたら、そのリストの終わりまでを全て無名関数に変換して、ブロック引数として渡す。
一見いいような気がするが、これができるようになるということは、MIKUで定義した無名関数もブロック引数をとれるということになってしまう。そして、所詮RubyのProcオブジェクトなので、実は手を加えなくてもnew[Proc]とするだけでブロック引数を取得することが出来る。
ブロック引数は、Rubyには良いアイデアだったが、曲がりなりにもLispな言語に組み込んでも、紛らわしいだけな気がする。

とにかく、無くても死なないので、当分このまま放置。しかしこれが使えないとなると、Rubyのメソッドが叩けるとはいえないのも事実。

2010年5月28日金曜日

key chord

Emacsにはキーバインドが不足している。私は矢印キーなどを使う軟弱者なので、幸いC-[bfpn]は開いているが、でも足りない。例えば、replace-stringやreplace-regexpは割と使用頻度が高いが、さてこれをどこに割り当てたものか。あと、1ストロークで実行したいものもたくさんある。
そんな時はkey-chordというelispを使えばいいらしい。(http://www.emacswiki.org/cgi-bin/wiki/download/key-chord.el

;; key-chord
(require 'key-chord)
(key-chord-mode 1)
(setq key-chord-two-keys-delay 0.05)

;; key setting
(key-chord-define-global "gl" 'goto-line)
(key-chord-define-global "as" 'auto-save-buffers-toggle)
(key-chord-define-global "re" 'replace-string)
(key-chord-define-global "zx" 'undo)

key-chord-define-global は、第一引数で指定された文字全てが同時に(key-chord-two-keys-delay秒以内の誤差で)押されたら、関数がコールされるというもの。

例えば、指定行に行きたいときにはgoto-line(M-g g)を使うが、これがgとl同時押しでよいのだ。同じ要領で、reでreplace-stringも使えるようにした。これこそ、私にしてみればワンストロークでやりたかったことだった。
今回は頭文字をとってキーバインドを適当に決めたが、実際にはおしやすさが重要。中途半端に離れているととても打ちづらい。なので、実は現実的な割当数は思ったよりは少ないが、それでも大変な数のキーバインドを手にいれることが出来る。

key-chord.elにはcopyright 2003とか書いてあった。こういうelispを知る度に、一週間くらいずっと拡張ばっかりする時間が欲しいと思うのだが、結局そんな時間は取れた試しが無い。まだ数日しか使っていないが、手放せないelispになりそうだ。

なお、ターミナルではキルリングとクリップボードの共有(とりあえずMac)をやっておかないと、クリップボードから貼りつけると、いろんなキーがほぼ同時に押されたことになって大変なことになるので注意。

キルリングとクリップボードの共有(とりあえずMac)

emacsは独自のキルリングを使っているので、emacsで C-w などでコピーしたところで、例えばFirefoxにそれを貼り付けることはできない。
回避策として、ターミナルなら、それのコピー機能を使用することで、外部アプリケーションに張り付けられる。逆に、クリップボードの文字列も同様の方法でemacsに貼り付けることが出来る。
しかしこれには致命的な欠点があり:
  • 見えたままクリップボードに入るので、ウィンドウ分割していると上手くコピーできない
  • クリップボードからペーストすると、一文字ずつ打ったことになるので、重いしインデントが勝手につく
と、スマートとはいえない状態だ。GUIから起動すると、メニューからコピーなどができるようになっているが、Screenバンザイなのでそちらに行こうとは思えなかった。

そこで、以下のようなelispを、.emacsに書き加えた。(ソース:http://blog.lathi.net/articles/2007/11/07/sharing-the-mac-clipboard-with-emacs
; http://blog.lathi.net/articles/2007/11/07/sharing-the-mac-clipboard-with-emacs
(defun copy-from-osx ()
(shell-command-to-string "pbpaste"))

(defun paste-to-osx (text &optional push)
(let ((process-connection-type nil))
(let ((proc (start-process "pbcopy" "*Messages*" "pbcopy")))
(process-send-string proc text)
(process-send-eof proc))))

(setq interprogram-cut-function 'paste-to-osx)
(setq interprogram-paste-function 'copy-from-osx)
これでシェアされる。ただしMac限定。
仕組みは簡単で、macのコマンドpbcopyとpbpasteを利用しているだけだ。コマンドの名前があまりにも自明なのであえて説明はしない。
要するに、linux, windowsでも同様のコマンドがあれば、コマンド名の書換だけで使用できるようになる。現在度重なる不幸によって(笑)自分用のubuntu開発機を持っていないので、Linuxでの対応はまたその時にしたいと思うが、少し調べたら同様のコマンドはあるみたいだし、GTKを使えば数行のコードで実現出来るはずだ。

2010年5月18日火曜日

まだまだやりたい事は出来ていない・・・

先日、mikutterの着想から半年が経っていたことに気がついた。途中なんども開発を停止しているし、別のこともいろいろとやっていたので本当に半年やっていたわけではないが、結構経ってしまった。
実際には、CHIを書き始めたのは10月であり、今まで何をしていたんだ、と落胆しそうになったが、とりあえずこれだけの機能がつけられた。
  • ホームタイムライン
  • リプライタイムライン
  • 検索
  • saved search
  • 通知
  • URL短縮
  • リプライ元つぶやきの表示
  • リプライ
  • リツイート
  • 公式リツイート
  • お気に入り
と、非常に少ない。亀進行である。未だにプロトタイプのレベルを出ていない。
主観だが、以下の機能が「普通のクライアント」を名乗るためには足りないのではないだろうか。
  • ダイレクトメッセージ
  • お気に入りに入れたつぶやきのタイムライン
  • スマートリスト(フィルタ)
逆に、現時点では以下の機能が普通のクライアントを上回っていると考えている
  • twitter.comからつぶやきを検索出来る
  • ハッシュタグの内容をmikutterから閲覧出来る
  • Webで保存した検索をタブに表示できる
  • あるつぶやきのリプライ元をノークリックで見ることが出来る
特に検索関係は強い。とりあえずだが自動更新も有り、少し改良すればtsudaる用途にもいい線を行くのではないだろうか。
つぶやきのリプライ元表示は良いアイデアで、構想段階からすでにあった。単純に、フォントを変えて下にリプライ元を表示しているだけだが、これだけでタイムラインの見た目はぐっと変わり、一番の長所といっていい。が、こんなものは簡単にパクられる。
# つぶやきの表示にリストビューを使っちゃった人たちには簡単にはパクれないかもしれない

では、構想段階ではどのようなことを考えていたのか、再び挙げてみたい。これは、12月の時点で既に欲しいと思っていた機能だ(既に実現したものは外している)

  • ダイレクトメッセージ
  • お気に入り
  • ふぁぼられ
  • 特定のつぶやきだけをまとめたリスト(Togetterのようなイメージ)
  • 公式リストへの完全対応
  • フォローしている人一覧
  • フォロワー一覧
  • 特定の人のプロフィール確認
  • 自分のプロフィールの編集
  • フォロー・リムーブ・ブロック・ミュート・マーク
  • スマートリスト
  • つぶやきの永久保存及び検索
  • 画像アップロード
  • 画像インライン表示(少なくともブラウザを介さない)
  • Twitlongerを利用したつぶやきの短縮
  • スレッド
Togetterのようなものだが、これは今はどうかな、と思っている。使いたいケースがあまり思いつかないからだ。何故欲しいと思ったんだろう(とはいえ、この程度のことはプラグインで出来るので、後で作ってもいいかも)。
ミュート、マークはオリジナルの概念だ。ミュートは、フォローしているにも関わらず特定の人のつぶやきを一切ホームタイムラインに表示させない。逆にマークは、フォローしていないにもかかわらず特定の人のつぶやきをホームタイムラインに表示させる。相手に知られること無く、フォロー/リムーブができる。幾つかのクライアントソフトには、既に実装されている。
ミュート、マークはスマートリストで実現可能かもしれない。スマートリストは、簡単なフィルタ言語等でつぶやきを絞り込んで表示するタブで、フィルタ言語にはHatsuneLispを検討している。スマートリストは、ホームタイムラインやリプライタイムラインを実現可能なので、これ一つで大抵のことをしてしまう時がくるかもしれない。
つぶやきの永久保存及び検索は、MySQLとSQLiteを用いて実装したが、万人受けするものではない割にライブラリ等の依存関係がややこしいので配布には至っていない。
最後のスレッドとは、つぶやきの親子関係を再帰的に辿り、一連の会話のツリーを構成するるぶやきをタイムラインに並べる機能。これがあれば、つぶやきを一つ拾えば会話全体が見えるようになり、革新的な機能となるだろう。

かなり盛りだくさんに見えるかもしれないが、これくらいないと使い物にならないと考えている。mikutterは、現在のTwitterクライアントに対する問題提起なのだ。作り始めたきっかけが「良いクライアントがない」という現状をなんとかするため、だったからだ。

こうしてみると、やりたい事の半分以下も出来ていないように見える。しかし、拡張可能な基板は整いつつあり、現在はつぶやきの保存方法すらプラグインで拡張出来るようになっている。つぶやきを投稿したら1秒くらいでフォロワー全員のところに届くのが当たり前の時代もついにすぐそこまで来ているし、半年後は今では想像もできない使い方をしているだろう。
# ハッシュタグとか、去年は公式サポートなかったんじゃなかったっけ・・・あと公式リツイート?検索っていつついたっけ?
とにかく・・・Twitterが本気を出す頃には、Twitterをちゃんと使えるクライアントにしておきたい。

2010年4月2日金曜日

はつねなLispのようなものを考え中

今、片手間にLispのようなプログラミング言語を作っている。プログラミング言語というよりは、処理をデータで表現したかっただけなのだが。

何故作ったし
みくった〜♪を柔軟に拡張できるようにするため

文法
S式とM式を合わせたような文法を採用。略してSMs(ry
だけど見た目はあんまりLispっぽくない。これは、できればプログラマでなくても多少使えるように、などいろいろ考えがある。以下に、みくった〜♪のフレンドタイムラインプラグインのコードを示す。

tl = gen-timeline[200]
regist-window[tl]
onupdate << { (watch msg)
add[tl msg]
}

Lispなんて言わなければ良かった(笑)S式だけで書くと以下のようになるだろう。

(setq tl (gen_timeline 200))
(regist-window tl)
(add onupdate (lambda (watch msg) (add tl msg)))

まず、特記事項はM式だ。これは、S[...]と表記し、(S ...)というリストに展開される。一般的な言語の関数呼び出しf(x)に近い書き方ができるし、S式に慣れていなくても比較的コードが見やすくなる。
次に、演算子。通常Lispでは計算式を次のように書く。

(+ 1 2 3)

いわゆる前置記法でとっつきにくい。演算子がリストの最初以外に現れた場合、その前後の値を括弧で括り、第一要素と演算子を入れ替える。文章では説明しづらいが、要するにパース時に要素の入れ替えが発生するというものだ。
例えば、

1 + 2 + 3
A B + C D
1 + 2 * 3 - 4

は、

(+(+ 1 2) 3)
(A (+ B C) D)
(+ 1 (- (*2 3) 4))

となる。
最後に中括弧だが、これはlambdaの省略記法。

(lambda (a b) (+ (* a a) (sqrt b)))

は、

{(a b) a * a + sqrt[b] }

と書ける。また、シンボル_は、その関数が呼ばれた時の引数リスト、_nのように数字をつければ、n個目の引数に束縛されている。普通の用途ならこれは不要かと思ったが、フィルタを書くために多用することになるので、あえて用意した。

フィルタ関数

では、本題のフィルタにうつる。たとえば、幾つかの優秀なツイッタークライアントでは、「ユーザaか、aへのリプライか、googleが含まれている」つぶやきだけを抽出したタブを作成できるが、これはこう書ける。

{ ()
or{
(assoc[_0 'user] == "a")
include?[assoc[_o 'message] "@a"]
include?[assoc[_o 'message] "google"]
}
}

(※注:orは引数をtが帰ってくるまで順次実行するマクロだが、はつねLispでは表記上の理由からlambdaも渡せる)
ちょっと複雑だ。Rubyだとこうなるだろう。

lambda{ |m|
m[:user] == 'a' or
m[:message].include?("@a") or
m[:message].include?("google")
}

あれ、かわんない。無理もない。はつねLispの関数は、以下の値のいずれかになる。
  1. =演算子でオーバライドされた関数
  2. はつねLispで定義されたプリミティブ関数
  3. 第一引数のクラスのメソッド
  4. 同名のRuby関数
例えばchomp[string]は、Rubyでstring.chompと書くのと同じ意味だ。しかしながら定数にはアクセス出来ないので、例えばnew[Time]のようなことはできない。これはあえて入れた制約で、これによって例えばはつねLispをJavaScriptなどで再実装する場合に、String, Numeric, Listくらいを再実装するだけではつねLispがすべて動かせるようになる。
また、はつねLispはほぼRubyに直訳出来る。プリミティブ関数も短いRubyコードだし、何よりほかのメソッド呼び出しは全てRubyのそれだからだ。つまり、高速化のためにRubyのコードに翻訳することが出来るのだ。

いわば、マクロを使えるようにしたRuby、という感じなのかもしれない。当然、Rubyとちがって全てS式にできるというのが最大のメリットだが。

2010年2月20日土曜日

みくった〜♪3 次までにやること

以前から開発を続けているみくった〜♪だが、テストしてくれている人が増えてきたので、既知のバグがなんなのか、最初のバージョンまでにどんな機能を実現したいのかを明確にしておきたい。

最初のバージョンの目標

目標としてどんな機能を付けるかを列挙しても良いのだが、ここでは「twitte.rbと同じ機能を実装する」とする。
理由は、具体的なクライアント名を上げた方が明確になることと、twitte.rbは基本的な機能をもっているクライアントの中では、機能が少ない方であること。このクライアントには、必要と思われる機能も一部含まれていないが、今回はあえてtwitte.rbに存在しない機能はこれ以上実装しない(削除機能、ふぁぼったつぶやき一覧など)。
なお、twitte.rbにある「アイコンアルバム」は今回実装しない。というのも、なくてもさしあたって困らないからだ(そのうち実装するつもり)。

人柱版までにやること

MMI作成時にフォーカスを移す
ボタンクリックで無数につぶやき入力欄を増やせるMMI(Multi Mumble Input)だが、これを作成した時に入力欄にフォーカスがいかないと、入力欄一つのインターフェイスよりクリック数が増えてしまう。
メモリバカ食いをやめさせる
つぶやきが溜まると、それを破棄しない仕様になっているため、タイムラインの流れが早い場合にメモリを数百MB消費してしまう場合がある。ページングすると操作性が落ちてしまうが、表示されていない部分についてはメモリを開放したりして、メモリ消費を抑える工夫をしなければならない。
ふぁぼる/アンふぁぼ
他人のつぶやきをお気に入れる/外す機能。
URL開く
URLをつぶやく人は多い。今はコピーできるようになっているが、クリックだけで開けるようにするべき。
ショートカットキーによる投稿(Ctrl+Enterとか)
現状、ボタン押下による投稿のみしか対応していない。
設定変更プラグイン
要するに、設定変更画面。内容は詳しく詰めていないが、以下のような物が最低限必要だと思う
  • 通知機能(サウンドを鳴らす、inotify、コマンド実行等)
  • 更新間隔(現在は一分固定、リプライは20分)
  • アカウント情報の変更
初回のアカウント入力/アカウント変更
現在、initialize.rbを実行することで初回の設定をするが、mikutter.rbを起動した時点でこれをするべき。
ポストする時にハッシュタグが2つつく
#mikutterなどとハッシュタグをつけると、#mikutter #mikutterと投稿される。
リプライ送るときに、宛先がデフォルトで入ってない
送信時に補完していたが、残り字数がわからなくなる。
<>問題
<>などがエンティティエンコードされた状態で表示される。
140文字に切り詰める
クライアントがこれをしないと、Twitterは140字以上の投稿をはじいてしまう。

是非ともやっておきたいこと

IMEのデフォルトONオプション
毎回つぶやき入力欄が消えるため、設定によっては毎回半角入力モードになってしまっている。
ダイレクトメッセージの実装
ただし、これはユーザのプロフィール画面と密接に関わる。そのため、今回はごく簡単なものになると思われる。

余裕があればやること
これは絶対に将来的にやりたい事だが、今回やろうとして却下したもの。理由は上記の通り、twitte.rbに実装されていないからだ。
  • 短縮URL
  • 画像表示
  • 自分のつぶやきを削除
  • スキン機能、というか見た目変更機能(フォントや文字色、背景など)
  • スクロールロック
もしかしたら、見落としているだけで実装されているかもしれないが、今回はこれらを見送る。なお、付けたい機能はこの他もいくらでもある。

まとめ

今回は、直近のやることをまとめた、いわばメモのようなものをかいたが、リリース前なので実装したい機能というよりは、バグ修正と最適化が主になっている。
次回くらいには、ここや私のWebページで「みくった〜♪」を配布したいと思っている。

2010年2月16日火曜日

みくった〜♪2 ちょっと作ってみた

みくった〜関連の記事しか書き込んでいないけど、それしかやっていないので。
ある程度インターフェイスがしっかりしてきたので、まだ殆どみっくみくにされていないけどスクリーンショットを公開する。なお、Ubuntu 9.10で撮影した。



これがフレンドタイムライン。いわゆるTL。モザイクの行は「toshi_a 伊吹萃香」のように表示されている。ツールバーの「つぶやく」ボタンや、各つぶやきにある「@」「RT」ボタンを押したらつぶやきを入力するスペースが現れる。普段表示しないことにより、つぶやきを少しでも多く表示出来るようになっているし、猫につぶやかれる心配も減るかもしれない。

目玉機能のマルチつぶやき機能。だけど名前を付けておかないとパクられたときに残念な気持ちになるので「Multi Mumble Input(MMI)」という名前を付けておく※1。つぶやきボタン等は、押せばいくらでも量産できるようになっているので、つぶやきを書いている途中で別のつぶやきにリプライを送りたくなったりしても、その内容をわざわざ覚えておいたり、書きかけのつぶやきを消す必要はない。それを覚えておくのはみくった〜♪の仕事なのだ。
※1 というか、パクられるまでになれたら嬉しいんだけど


リプライ画面。先ほどと基本的には変わらない。リプライ画面に限ったことではないが、カーソルを合わせたつぶやきが別のつぶやきへの返事だった場合、親つぶやきがツールチップで表示される。
表示方法はともかくとして、あるリプライが何に対してなのかが分からなくなることというのは、複数人と会話していたらよくある話だ。これが確認出来るクライアントが当然多く使われているはずだが、その数はそんなに多くないし、しかも大抵のクライアントでは相当確認が面倒だ。
・・・ということを考えると、現時点ではついったーでよく会話をする人に適したものになっていると考えられる。もちろん、通知機能、URLを開く機能など基本的な機能が揃っていないのでまだ使ってもらえる段階にも無いが、twitte.rbを超えるという最初の目標はすぐそこだ。

まとめ
今回は、現在の開発の進捗を報告した。今後の課題は、見た目がみっくみくにされていない点である。

2010年2月10日水曜日

みくった~1 バージョン1の全貌

ついったーでわいわい言っていたが、ちゃんとまとめてなかったのでまとめる。
前回は動機を語ったので、今回は短期目標を具体的に語る。

とりあえず短期目標として、最低限、他のクライアントにある機能はつけたい。もっとも機能が多いと思われる夜フクロウに勝てばいいだろうと思ってる。
しかし短期目標には目標が高すぎる感があるので、まず、みくった~にコードの一部をいただいたtwitte.rbにある機能を網羅し、それを以ってバージョン1としたい。
だけど、あのクライアントにある機能で実装しないものも多いので、ここに最初の搭載予定機能を書いておく。

まずタブには、フレンドタイムライン、リプライ、DM、設定が並ぶ。

DMのタイムラインは大体どのクライアントにもあるし、実装が容易なのでとりあえず実装するが、どちらかというとフォロワーのプロフィールからその人だけを絞ったりできるようにしたい。

つぶやき入力欄が特徴的で、デフォルトでは表示されていない。ボタンを押すことで次々と入力欄を作れる。

各つぶやきにはリプライボタンが付いていて、クリックするとその下につぶやきが入力できる。これも、複数の同時入力に対応している。

またあるつぶやきをダブルクリックすると、それがなにかに対する返信だった場合に、それを再帰的に辿ってタイムラインを作るので、何に対する返事なのかが一目瞭然になる。

設定がタブに埋め込まれているのは、ボタンを置くスペースが無かったから。右クリックメニューからしか開けない、ということにしてもいいかもしれないが、いちいちポップアップにする必要もなさそうだ。
設定項目は次のものを予定する。タイムライン、リプライ、DMの通知方法(inotify、サウンド、任意のコマンド)。アカウント情報。更新頻度。そしてフッダ。各プラグインに関して設定が増えると思うがいまはこんなものだろう。

とりあえず、機能としては何もないに等しいがこれくらいでリリースする(すごいのに期待してた人ごめんなさい。でもこれからだ)。近いうちに、将来つけたい機能もまとめる。

次回は、プラグインによる機能拡張について書く。と思う。


その他
今回はiPhoneから書いてみた。やっぱりキーボードがいい!

2010年2月4日木曜日

ついったーブラウザ「みくった〜」0

タイトルのとおり。今回は半分メモ書きみたいなもの。
今回は、Twitter(以下ついったー)のクライアントサイドソフトを作るために、いろいろ考察したことを、なかなか寝付けないのでまとめてみた。

何が問題なのか

既存のクライアントの問題、特徴の雑感。

1、必要な機能が削られている場合が多い
例えば、大体のついったークライアントはフォロー/フォロワー数一覧すら取得することが出来ない。これらはWebページ上で済む話だから実装されていないらしい。場合によってはふぁぼりも実装されていない。Linuxにはお世辞にも使えるとは言い難い代物が多く、DMすら実装されていないものもある。普段使わない機能は実装しないというコンセプトかもしれないけれど、それが行き過ぎて必要な機能まで実装されていないことがある。

2、安定していない
Linuxで顕著。文字通り。例えば、HTTPリクエストをメインスレッドで送ってしまっているのか、更新中に動作しなくなるクライアントが以外と多かったり、そもそも突然落ちたり。ここに関してはとくに言うことはない。安定したものを作らなければならないと思う。

3、情報を表示しない
これは普通にしていては気づかないことかもしれない。Computer Humanoid Interfaceを作ったことによって気がついた。例えばついったーではゆるいチャットになることがしばしばあるが、いくつもの話題が平行して進行するので非常に分かりづらい。こればっかりはどうしようもないと思っていたら、なんとAPIの戻り値には、どのツイートへの返事なのかがちゃんと書いてあるのだ。このように、開発者しか知らないことが結構あったりする(※当然、返信を再帰的にたどれるクライアントも存在する)。

4、Webのほうが総じて使いやすい
このブログが人気だったらすごく炎上しそうな見出し。上述のとおり、機能が少ないためだ。ただ、頻繁に行うことがやりやすい、通知機能が使えるなどの理由で、クライアントを使う価値はある。

今使っているクライアントの特徴

というわけで、見聞したクライアントをいくつか試した。とりあえず今使っているついったークライアントとその感想(括弧内は使っているOS。必ずしもそのOSでしか動かないわけではない)。

twitte.rb (Ubuntu)
CHIの参考にしたクライアント。Rubyで書かれてる。つぶやき、RT、メンション、DM、ふぁぼができる。新規タブを作って、簡単なフィルタでつぶやきを分類できる。
HTTPリクエスト中に(ry)というのはこれのこと。最近Ubuntuを使う時はあまりついったーをしていないのでそんなに使ってない。Linux用ももっと良い物があると思う。

YoruFukurou (Mac)
Mac専用であまり知名度は高くないみたいだけどMacでは一番使われている(気がする)。
twitte.rbの機能に加えて、ふぁぼったものの確認、公式RT、twitter検索タブの作成、TLからつぶやきのインクリメンタルサーチ、特定アカウントのプロフィールの確認、画像のクイックプレビュー、特定のツイートの親ツイートの探索、などができる。高機能な割には直感的に使える。PC用クライアントはつめが甘いものが多いが、このクライアントは極めてマシ。レビューしてるわけじゃないけどおすすめ。

twitbird Premium (iPhone)
有料ソフト(無料の時に落としたけど)。お金取っている分なかなか出来は良い。
YoruFukurouにある「インクリメンタルサーチ」「公式RT」は無いが、フォロー・リムーブ、公式リスト、ユーザ検索など、ブラウザとの切り替えが煩わしいせいか、多くのPCクライアントにない機能が実装されている※。アカウントを2つまで登録可能。Proは6つくらいいけるらしい。
※iPhone用クライアントでは別に珍しいことではないんだけれど

これだけ機能があればそれほど問題が無いようにも思えるが、実際に使うと様々不満は出る。まず、機能が豊富そうな夜フクロウだが、アカウント管理のあたりが非常に弱い。フォローするだけでブラウザのお世話になるハメになる(そのため、twitter.comをブラウザで開くボタンが画面の一番左上にある)。はっきり言って煩わしい。一方twitter birdはいちいちブラウザを立ち上げる必要もなく、相手の情報をほぼ全て見ることが出来る。

どのクライアントも込み入ったことは全てWebに任せているし、何より「ついったーをチャットとして使うため」の機能がいまひとつ整っていない。そんな用途で使わないという人も多いだろうけど、単純なサービスなのでいろんな使い方があるから、それに耐えうる物を作りたい。今の状態は「ついったー中毒になったと思っていたら、このクライアント中毒になっていた」ように見受けられる。

具体的にどうしたいの?

1、初心者でも使える
はっきりいって、操作が煩わしいものが多い。いつもデザインする時には心がけているが、見ただけで不安になるようなUIは、初心者はおろかあるクライアントに慣れた上級者もアレルギー反応を起こす。

2、情報がすべてユーザに見える
せっかくついったーが提供してくれている情報を使わない手はない。全て表示するのはやりすぎだが、ユーザが望むならアクセスできたほうがいいし、できないものがあれば後発のクライアントに機能面で容易に追い越される。

3、情報を活用できる
この間、ある特定のことをつぶやいたのが何時だったか思い出せなかったのだが、それを検索する手段が無かった。このように、せっかく膨大なデータを持っているにも関わらず、そのデータを活用できないことが多い。今、多くのついったーの2次Webサービスができてきているが、クライアントソフトならではの機能というものもあるはずだ。

4、クライアントにならない
本来の意味ではないが、ついったーに於けるクライアントは「頻繁に行うことを楽にする」ものになっている感がある(それを想定しているのかもしれない)。一方で私は全機能をカバーしたいと思っているので、クライアントではなくブラウザを作ることにしたい(例えば2chブラウザは全ての2chの機能を利用できるようだが、そういう感じ)。

今回はこのあたりで止めておく。まだ見せられる段階に来ていないのでスクリーンショットなどは見せられない。UIなども今考え中なので、できてきたら随時ブログに書いていく。
とはいっても、また書くのをサボるかもしれない。コードを書けばブログを書いてくれるBOTがあればいいのに(笑)

まとめ
・ついったークライアントだらしないね。
・歪みない“ブラウザ”をつくろう。
・不便を感じたら口より先に手が動くのは仕方ないね。