delegation って何?

DelegateClass の謎 - ¬¬日常日記

さて、このように Ruby の DelegateClass は良く分からない事でいっぱいです。しかし そもそも「delegation って何?」ということを理解していないと、MLで聞くことも出来ませんので delegation について調べてみることにしました。色々と調べていると、次のような記事をみつけることが出来ました。

http://roots.iai.uni-bonn.de/research/darwin/delegation

これはまさに「delegationって何なの?何じゃないの?」ということなので、私の目的にはぴったりです(ただしこれは lava っていう言語の解説ページの一部だから、lava における delegation とは、という可能性も高いのでこの内容がどれだけ一般化できるのかは分かりません)。ただし delegation という言葉は現実的には多義的に用いられてしまっているそうですので、これに説明されているのは色々な delegation の一つである、という事に注意しておく必要があるようです。概要をまとめておきます。

  • Henry Lieberman さんが最初に delegation の用語を導入した
  • 最初に delegation が実装されたのは Self などのプロトタイプベースの言語
  • delegation という用語は不幸にも様々な使われ方をされてしまっている
  • delegation と consultation の概念を区別しよう
    • delegation はメッセージに対する振舞いを他のオブジェクトに委ね、self は自分
    • consultation とメッセージ自体を他のオブジェクトに丸投げ、self は丸投げ先
  • Invocation や Object extension とは一緒くたにしないでね

delegation と consultation の区別がとても興味深いですね。Ruby で書くと次のような違いということになるでしょうか。

class Parent
  def call; puts "self: #{self.inspect}"; end
end

# UnboundMethod の制限のため
# Child は Parent を継承しておきますが他意はありません
class Child < Parent
  def initialize
    @parent = Parent.new
  end
end

# delegation
class ChildA < Child
  def call
    # @parent のメソッド call を取り出して self に束縛
    # つまり振舞だけを @parent に譲る
    @parent.method(:call).unbind.bind(self).call
  end
end

# consultation
class ChildB < Child
  def call
    # @parent にメッセージ call を譲る
    @parent.call
  end
end

ChildA.new.call
# self: #<ChildA:0xb7c712b4 @parent=#<Parent:0xb7c71250>>

ChildB.new.call
# self: #<Parent:0xb7c7119c>

即ち、この定義に従えば DelegateClass で行なわれているのは delegation ではなくて consultation ということになります。

require "delegate"

class Parent
  def call; puts "self: #{self.inspect}"; end
end

class Child < DelegateClass(Parent)
  def initialize
    super(Parent.new)
  end
end

Child.new.call
# self: #<Parent:0xb7cc1cb4>

つまり標準の delegate.rb は実は全然 delegation してなくって、実際のところ consultation を行なうライブラリであることになります。

ただし、この定義における delegation とは、ほとんど Ruby の Mix-in と変わりません。

module Parent
  def call; puts "self: #{self.inspect}"; end
end

class Child
  include Parent
end

Child.new.call
# self: #<Child:0xb7c509b0>

もしくは継承もまた delegation と変わらないように見えます。

class Parent
  def call; puts "self: #{self.inspect}"; end
end

class Child < Parent; end

Child.new.call
# self: #<Child:0xb7cf6a04>

そうするとここで言う delegation は Mix-in や継承で実現出来てしまっているので、DelegateClass で delegation を行なう必要は全然ないことになり、従って consultation こそ行なわれるべきであるのでしょう。

以上の話をまとめると、delegation と consultation は self が何であるのかによって区別されますが、この場合の delegation は Ruby は Mix-in もしくは継承によって実現できているために、敢えてライブラリとする必要はありません。よって delegate.rb では consultation の実現が目的とされているのでしょうし、DelegateClass がどうあるべきなのかを考える際にもこのことに注意しておくべきだろうと思います。

さて、ここまで来るとなぜ delegation のことを知りたいだけなのにプロトタイプベース言語の Self に行き着いたのかが、なんとなく分かってきました。delegation って早い話がプロトタイプベース言語における継承の代替物(と言っていいのかな?)として使われているわけですね。Self を試すのは大変なので(だってまだ文法もなにも分かってないんだもん!)Javascript で大体のイメージをつかんでみます。

var parent = {
    is: "parent",
    call: function () { print(this.is) }
}

function Child() {
    this.is = "child";
}

Child.prototype = parent;

new Child().call();
// child

delegation がプロトタイプベース言語の正体!と考えると、Javascript のもやもやっとしたところが一気によく分かるようになりました。おお、なんてお得な感じなのでしょう。

DelegateClass の謎

DelegateClass に起因するスペックテスト問題 - ¬¬日常日記

というわけで、さて本論の DelegateClass の謎について書いてみたいと思います。まず前提として、私は delegation というものが何か今はまだよく分かっていません。だから、謎は謎のままです。今後緩やかに勉強していって、じゃあ DelegateClass はどうあるべきなのかという点を考えていくつもりです。つまり今回の範囲ではどうあるべきかといった結論は出さずに、Ruby の DelegateClass の振舞をただただ観察することが目的となります。

まず、DelegateClass の使い方は以下の記事がとても参考になります。

るびま

ここに挙げられている DelegateClass の使用例をそのまま引用します。

require 'delegate'
class ExtArray < DelegateClass(Array)     # Step 1
  def initialize()
    super([])                             # Step 2
  end
end
p ExtArray.ancestors #=> [ExtArray, #<Class:0x402a4f24>, Object, Kernel]
a = ExtArray.new
p a.type  # => ExtArray
a.push 25
p a       # => [25]

しかし、どうでしょうか。こうは書かれているものの、実際動かしてみないとどうなるのか分からないのが Ruby というものです。まず 1.8 trunk で動かしてみます。

[ExtArray, #<Class:0xb7cc24ac>, Object, Kernel]
ExtArray
[25]

というわけで大丈夫ですね(いや、Object#type の警告は出ますけれども)。では次に 1.9 trunk で動かしてみます(1.9 ではさすがに Object#type がないので、これは #class に書き換えて実行しています)。

[ExtArray, #<Class:0x82bdf10>, Delegator::MethodDelegation, Object, Kernel, BasicObject]
Array
[25]

ancestors の結果が色々と変わっていますし、また a.class の結果が Array です。前者の件については、1.9 的なクラス階層になっているだけでなく、Delegator::MethodDelegation というのが追加されています。つまり、1.9 では DelegateClass はなんか変わったよ、ということですね。でもこれはあまり影響がないので良いとします。しかし後者は激変と言って良いかと思われます。こうした振舞の差を次によって確かめてみましょう。

require "delegate"

class A; end
class B < DelegateClass(A)
  def initialize
    super(A.new)
  end
end

b = B.new

p b == b
p b.kind_of?(B)
p b.instance_of?(B)
b.instance_variable_set(:@b, :b)
p b.instance_eval{@b}
p b

まず 1.8 では結果は次のように表示されます。

false
true
true
:b
#<A:0xb7c13c2c>

なんとまぁ b == b が false になります。さすがにこれはどうか、と思うところですが、もっとマズいのは次の振舞いです。

b == b.__getobj__ #=> true
b.__getobj__ == b #=> false

よって #== に関する対称性が保たれていません。よって多分 1.8 系の DelegateClass の #== は壊れているのだろう、と判断します(いずれ ruby-dev か BTS で聞いてみるつもりです)。

あと細かい点ですが、p(b) の結果が # となっていますが、これは @b = :b が設定されていないことから分かるように、b#inspect が委譲されて __setobj__.inspect の結果が表示されているだけとなっています。

では次に 1.9 の結果を見てみます。

true
false
false
nil
#<A:0x82ce4b4 @b=:b>

おお、嬉しいことに b == b が真になっています(でも試してみたらやっぱり #== に関する対称性が保たれていません)。しかしやはり b.kind_of?(B) 及び b.instance_of?(B) は偽です。もちろん既に述べたように b#class は A になるわけで、これはある意味首尾一貫した変更です。つまり B のインスタンスである b はあくまでもクラス A のインスタンスのように振る舞うわけですから、#kind_of? も #instance_of? も #class も、まるで A であるかのように振る舞うわけなのでしょう。そのように DelegateClass の働きが変わってしまった、と理解することが出来ます。

個人的には b.kind_of?(B) が偽となるのはとても違和感を覚えるのですが、それは委譲について私が良く理解していないことを起因とする誤解かも知れませんので、ここではそうしたことは問題とするつもりはありません。しかしこうした 1.9 の DelegateClass のあり方について問題がないわけではありません。

まず #instance_variable_set/get も委譲されているのは問題であろうと思われます。この理由は、もしこれらのインスタンス操作系のメソッドを委譲するのであれば #extend や #instance_eval だって委譲しなければ首尾一貫しませんが、そうなっていません。しかしこれらを全て委譲するのは極めて不便であろうとは想像できます(ここでは具体例をすぐに示せませんがそのうちなにか考えます)。私はこうしたインスタンスの状態変化を起こすメソッドを委譲すべきではないと考えます。

次に、b.kind_of?(B) が偽であるのに、case 文における状況はそれと一致しません。

case B.new
when A; puts "A"
when B; puts "B"
end
#=> B

もしも B のインスタンスが DelegateClass の効果によりまるで A のインスタンスのように振舞い、それが #kind_of? や #class にまで影響するのであれば、case 文における比較についても同様であるべきでしょう。従って、ここでは"A"が印字されることを期待しますが、実際には"B"となります。つまり 1.9 での DelegateClass のあり方は #=== について問題を抱えています。しかも、これを解決するのは、なかなか大変ですよね。というのも、case 文における比較は実際には A === B.new のような形になって、これは A.===(B.new) であり、レシーバは A となります。さて困ったものだと思います。まさか A を直接いじるわけにはいかないですよね。でもだからと言って Module#=== をいじるのは、もっともっと悪いアイディアでしょうし。

そんなこんな感じです。結局のところ、1.8 系にせよ 1.9 系にせよ、DelegateClass の現状には問題があると言えます。以上の内容をまとめます。

  • 1.8 系では #== がちょっと耐え難い(修正は簡単かな?)
  • 1.9 系では #instance_variable_set/get や #=== が首尾一貫しない

個人的には 1.9 系の #kind_of?, #class の変更は無いことにしたいくらいですが、最初に述べたように、私は委譲についてきちんと理解していないので、実は 1.9 系のあり方が概念上優れていたり正しかったりする可能性があります。しかしその場合には #=== をどうにかしなければなりません。どうしようかな。

DelegateClass に起因するスペックテスト問題

私の大好きな Bacon さん(一応 RSpec クローンですけど、記法が色々違うので注意して下さい)で DelegateClass を使って定義したクラスのオブジェクトの振舞いを定めていて気付きました。RSpec についても同様の状況ですので、DelegateClass を使う場合には注意しましょう。次のような例をご覧下さい。

require "delegate"

class A; end
class B < DelegateClass(A)
  def initialize
    super(A.new)
  end
end

describe "class B" do
  it 'should be kind of B' do
    B.new.kind_of?(B).should.true
    B.new.should.kind_of B
  end
end

さて、このスペックテストの結果はどうなるでしょうか。B のインスタンスは当然 B の一種でしょ!? と考えると痛い目にあいます。これを 1.8 系で実行してみると次のようになります。

class B
- should be kind of B [FAILED]

Bacon::Error: #<A:0xb75809dc>.kind_of?(B) failed
	/home/keita/Build/test.rb:13: class B - should be kind of B
	/home/keita/Build/test.rb:11
	/home/keita/Build/test.rb:10

1 specifications (2 requirements), 1 failures, 0 errors

以上は、

  • B.new.kind_of?(B).should.true は成功
  • B.new.should.kind_of B は失敗

という結果となったことを示しています。あれ変だな、と思いませんでしょうか。B.new.kind_of?(B) は真であるというのに、B.new.should.kind_of(B) は失敗するのです。これはなぜでしょうか。

まず、直接の原因を探るために、bacon.rb で定義されている Object#should を見ておくことにします。

class Object
  def should(*args, &block)    Should.new(self).be(*args, &block)         end
end

つまり、B.new.should は B のインスタンスに #should を送るわけですが、これは DelegateClass の作用によって A のインスタンスにフックされてしまいます。従って実際には A.new.should となり、定義に従って Should.new(A.new).be 即ち Should インスタンスが返されます(be メソッドは基本的に何もせずに自分自身を返します)。これにより B.new.should.kind_of B は実際には Should.new(A.new).kind_of B と同等であることが分かります。よってこのスペックテストは失敗に終わるわけです。

だから、こうした DelegateClass にかかわるスペックを書く時には、次のように直接 Should クラスからオブジェクトを作成するというバッドノウハウを蛮行すれば良いだろうと思われます。

require "delegate"

class A; end
class B < DelegateClass(A)
  def initialize
    super(A.new)
  end
end

describe "class B" do
  it 'should be kind of B' do
    B.new.kind_of?(B).should.true
    Should.new(B.new).kind_of B
  end
end

このスペックの結果は 1.8 系では次のようになります。

class B
- should be kind of B

1 specifications (2 requirements), 0 failures, 0 errors

というわけで成功しました。ああ良かった素敵なバッドノウハウでいともたやすく解決だね!などと安堵していると血反吐を吐いて死にます。ではこれを 1.9 系で実行してみましょう。

class B
- should be kind of B [FAILED]

Bacon::Error: false.true?() failed
	/home/keita/Build/test.rb:12:in `block (2 levels) in <top (required)>': class B - should be kind of B
	/home/keita/Build/test.rb:11:in `block in <top (required)>'
	/home/keita/Build/test.rb:10:in `<top (required)>'

1 specifications (1 requirements), 1 failures, 0 errors

これは B.new.kind_of?(B).should.true の失敗を指し示しています。え?と思うかも知れませんが、実は DelegateClass の振舞は 1.8 系と 1.9 系でかなり違います。だから本当は 1.9 系では次のようなスペックを書くべきなのです。

require "delegate"

class A; end
class B < DelegateClass(A)
  def initialize
    super(A.new)
  end
end

describe "class B" do
  it 'should be kind of B' do
    B.new.kind_of?(B).should.false
    Should.new(B.new).kind_of A
  end
end

1.9系における結果は次のようになります。

class B
- should be kind of B

1 specifications (2 requirements), 0 failures, 0 errors

というわけで、 B.new.kind_of?(B) は偽だし、Should.new(B.new).kind_of A は正しいのです。このようにして最初に述べた「B のインスタンスは当然 B の一種でしょ!?」という直観はいともたやすく打ち砕かれてしまったわけです。

以上から分かる通り 1.9 系における DelegateClass はメソッドの委譲具合が半端じゃありません。#kind_of? だけではなく例えば #class なども委譲してしまいますから、これは注意が必要です。つまりどういうことかと言うと self.class.new とかするとハマるよ!ってことです。DelegateClass には魔物が潜んでいるので、これについてはまた別に書きます。

まぁこのように大袈裟に書きたてたところですけれども、実際のところ、スペックにはもっと振舞いらしいところを書くようにすればいいだけです。kind_of? とか書く必要は実際のところあまりないでしょう(でも私は書きます)。だからそういうわけでこの記事は DelegateClass の怪しさを喧伝するためだけに書いたのでした!

ていうかそもそも delegation ってなに?という文系らしい初歩的な疑問を追い掛けて、現在 Self にまでようやく漕ぎ着けたところなのですが、行き着く先が Self で本当に大丈夫なのでしょうか。ところで Self って名前しか知らなかったけど、やったねプロトタイプベースってヤツですね!まだ開発続いてるような雰囲気でなによりです。

Rubinius で循環継承

先日は Smalltalk で循環継承を起こして遊びました。

ProtoObject の謎と superclass - ¬¬日常日記

しかし CRuby では循環するような継承関係を作ることは出来ません(と思います、私がソースを読んだ限りでは無理だと結論しました)。CRuby においては継承関係は次の二通りの方法によって定められます。

  • クラスの作成時
  • モジュールの include 時

循環継承は結局のところ ClassA < ClassA のような形ですから、クラス作成時には親クラスを自分自身にすることが出来ないので無理です。またモジュールの include によっても仮の継承関係がつくられますが(と言っていいものなのかな?)、これも cyclic な関係にある場合に例外を出すようにチェックされているので無理です(class.c の rb_include_module をご覧下さい)。というわけで Ruby では squeak で出来たような循環継承を構成することが出来ません。もちろん循環継承は有害ですので出来なくて当たり前というか親切なんだと思います。

なぜ有害かと言えば、例えばメソッドの探索を行なう場合に循環継承を起こしていると探索が止まらない場合があります。いや、止めようと思えば止められると思いますが、わざわざそんなことにコストをかけても利点がちっともありません。というか、循環継承を起こすことに利点は全くないと思いますので(私の想像の範囲では思い付かないのですが、なんか使える場面とかあったりしますでしょうか)、色々面倒なことが起きないように禁止しておくのが一般的だろうと思います。循環しちゃったらタクソノミー的なクラス階層が作れないから脳味噌にもとっても厳しいと思います。

でもほら、禁止されれば禁止されるほど、やってみたいじゃないですか。そういう夢を Smalltalk というか squeak は叶えてくれたわけです。squeak で出来るのならば、じゃあそれに影響されている Rubinius でもきっと出来るわけでしょう。そう思ったのでやってみました。

class_a = Class.new
class_a.superclass = class_a

cls = class_a
while cls do
  p cls
  cls = cls.superclass
end

#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
#<Class:0x9>
# ...

以上のように Rubinius ではこんなに簡単に循環継承を起こせます。だから次のように知らないメソッドを呼ぶと探し続けて止まらなくなります。

class_a = Class.new
class_a.superclass = class_a

class_a.new.asdf # 止まらないよ!

Rubinius は楽しいですね!

これだけで終わるととっても中身がないのでもう少し Rubinius の振舞を眺めてみることにします。例えばちゃんとモジュールの include で循環が起きないようにチェックしているかを確かめてみます(以下の例は CRuby では例外が発生します)。

module MA; include MA; end
class CA; include MA; end
CA.ancestors #=> [CA, MA, Object, Kernel]

モジュールが循環を起こして止まらないことを期待していたのですが、なぜかプログラムが終了しました。ではさらに次のような状況がどうなっているのかを確認してみます。

module MA; end
module MB; end
MA.include MB # Rubinius では include が private じゃない!
MB.include MA
class CA; include MA; end
CA.ancestors #=> [CA, MA, MB, Object, Kernel]

おお、なんということでしょう、これもまたきちんと停止してしまいました。こうした振舞いを見ると、ホントにこれで大丈夫なの?と疑問を持たざる得ません。では今度は include がちゃんとしているのか確かめてみます。

module MA; end
module MB; end

class CA; include MA; end
CA.ancestors
#=> [CA, MA, Object, Kernel]

MA.module_eval { include MB }
class CA; include MA; end
CA.ancestors
# => Rubinius だと [CA, MA, Object, Kernel]
# => CRuby(1.8系) だと [CA, MA, MB, Object, Kernel]

ほら、やっぱり不安的中。では Rubinius のソースを読みましょう。

kernel//module.rb の Module#include と Module#append_features がどうなっているのかを確認します。kernel/core/module.rb の include_cv は次のようになっています。

  def include_cv(*modules)
    modules.reverse_each do |mod|
      if !mod.kind_of?(Module) or mod.kind_of?(Class)
        raise TypeError, "wrong argument type #{mod.class} (expected Module)"
      end

      next if ancestors.include? mod

      mod.send(:append_features, self)
      mod.send(:included, self)
    end
  end

そんなこんなんで、大体のところ CRuby と同じことをしていることが分かりますが(詳細は kernel/bootstrap/module.rb, class.rb もあわせて読んでみて下さい)、困ったことに next if ancestors.include? mod なんてしちゃってます。これは CRuby とは明らかに異なる挙動です。CRuby では ancestors に mod が含まれていても、mod がインクルードしているモジュールについても再確認を行なっています。

http://i.loveruby.net/ja/rhg/book/class.html

そういうわけですから Rubinius は現状ではきちんと include を実装していないことが分かりました。つまり「module MA; include MA; end」は、MA.ancestors が include が呼ばれた時点で [MA] となっているので、「if ancestors.include? mod」が真となるために結果として無視されるわけですね。これなら確かに循環継承も起こさないのでプログラムが停止するわけです。

以上、循環継承にチャレンジすることで CRuby と Rubinius の相違を発見できました。こういうちょっと馬鹿げた試みから豆知識を得ることが出来てとてもお得な感じですね!
Rubinius のこの相違を修正するパッチもいずれ作成してみたいと思います。

っていうか、include について言えば CRuby にしたって、次のようになっててくれた方が嬉しいんじゃないのかと思います。

module MA; end
module MB; end

class CA; include MA; end
CA.ancestors
#=> [CA, MA, Object, Kernel]

MA.module_eval { include MB }
CA.ancestors
# => [CA, MA, MB, Object, Kernel]

昔、こういうのを期待してて、「なんでうまく行かないの?」と悩んだことがあります。でもきっとこうなってないのには理由があるんですよね。なにかな?

ruby-pathtraq を作成しました

長い沈黙を破ってついに待望の Pathtraq API が登場したようです。やったね!

マウント アンマウント | パソコン豆知識

そこで RubyPathtraq API のラッパーライブラリであるところの ruby-pathtraq を作りました。API 自体はとっても簡単だからわざわざライブラリとして置いておく必要性もないとは思いますが(とか言ってるけど、後で書くように意外と本当はいざ作ってみるとなかなか面倒くさい事情でいっぱいでした)、書いたからには公開しておきます。

http://github.com/keita/ruby-pathtraq/tree/master

これから rubyforge に登録しますので、いずれ gem でインストールできるようになるものと思います。

使い方

ruby-pathtraq の使い方を紹介しておきます。

require "pathtraq"
include Pathtraq

# ニュースランキング取得API
NewsRanking.request(:m => :hot, :genre => :national)

# こういう感じに各ジャンル名でも呼べます
NewsRanking.sports
NewsRanking.culture

# カテゴリランキング取得API
CategoryRanking.request(:m => :popular, :category => :politics)

# 各カテゴリ名でも呼べます
CategoryRanking.anime
CategoryRanking.art

# キーワード・URL検索API
KeywordSearch.request(:url => "http://d.hatena.ne.jp/keita_yamaguchi/")

# URL正規化API はあんまり必要性を感じてないので未サポート

# ページカウンタAPI
PageCounter.request(:url => "http://d.hatena.ne.jp/keita_yamaguchi/")

# ページチャートAPI
PageChart.request(:url => "http://d.hatena.ne.jp/keita_yamaguchi/")

みたいな感じです。それっぽい感じなので、それっぽく使ってみて下さい。

Ruby 標準添付の RSS モジュールが面倒だという話

さて、こんな簡単な API のラッパーライブラリならあっという間に作れちゃうよね!と思ったのですが、実際には結構手間取りました。原因は以下の二点です。

  • RSS モジュールがいまいち
  • Pathtraq API の仕様がややこなれてない

最も時間をとったのが RSS との格闘です。最初は標準添付の RSS モジュールを使って書いていたのですが、これがとっても困ったさんでした。なんでこんなに使いづらいのでしょうか。

Pathtraq API はレスポンスを RSS で返してくれるのですが(JSONでも返してくれます)、フィードの中に pathtraq:hits という謎の要素が入り込むわけです。RSSモジュールはこうした場合の対処に非常に手間取ります。rss/dublincore.rb あたりを参考に地道にやっていけばなんとかなるだろう、というのは分かるのですが、これはとても面倒です。一度実際に DublinCore ばりに書いてみたのですが、こういう本質でもないなんでもない部分が API のラッパー部分よりもはるかに大きくなってしまうという、とてもろくでもない結果になったので、黙って消してしまいました。

これはやってられない!というわけで、世の中にはきっともっと便利なライブラリがあるに違いないと考え、gem 探索の旅に出ました。絶望的なrubyforge 検索(なんであんなにも検索しづらいのでしょう)の果てに見つけた良さそうなライブラリは次の二つです。

Syndication の方がなにかとしっかりしているように見えますが、今回は名前の通りとっても簡単な simple-rss を選択しました。simple-rss において pathtraq:hits 要素に対応するためには、以下のようにします。

require "simple-rss"
SimpleRSS.item_tags << :'pathtraq:hits'

たったこれだけ。こうでなくっちゃいけません。簡単でしょ?名前に偽りないでしょ?同じことを標準添付の RSS モジュールでやろうとするととっても大変ですから、素直にこうしたライブラリに頼りましょう(ただし simple-rss も syndication も読み込みのみで、書き出せませんから、完全な代替とはなりません)。

というわけで、ここに行き着くのに非常に時間がかかりました。Rubyで何かやろうとする時には、ライブラリ探索で時間をとられる場合が多いのではないでしょうか。rubyforge の検索がもう少し洗練されていると楽になるのになぁ、といつも思います。

さて、こうしてフィードの読み込みについては解決したものの、次に Pathtraq API そのものもちょっぴり困ったさんです。

  1. 不正なパラメタによるエラーが判定しづらい
  2. レスポンスを JSON にすると description が欠落する
  3. ページチャートAPIだけ JSON でしか返してくれない

1についてですが、不正なパラメタを渡した時にエラーになるのは当然として、問題はこのエラーの発生をどうやって補足すれば良いのかです。現状では、レスポンスの status コードが 200 OK なのですが、これは 400 の方が良くないでしょうか。しかもエラー時のレスポンスは content type が text/html になっているのに、実際のところ HTML ではなくただの plain text が返ってきます。というわけで、Pathtraq API はあまりエラー時の扱いがこなれていないと言えます。

2は、レスポンスをJSONにすると description が欠落しちゃうので、RSSがダメならJSONでいいかな、という選択が出来ません。

3は、なんか不統一なのが気になるだけです。でもこれだけのために JSON パーサをひっぱってくるのもおっくうです。ここもなんとか XML でよろしくお願いしたいところです。

以上、細かいところかも知れませんが、現在のところこういう点がしっかりしていません。でもいずれ良くなるものと信じております。

追記:ここに書いてあるの全部解決!すごいよPathtraq、ありがとうサイボウズ・ラボの皆様!

Kazuho@Cybozu Labs: Pathtraq の API を公開しました

まとめ

実のところ Pathtraq のアドオンは約一ヶ月前にアンインスールしちゃいましたよ。だってもう API とか出ないのかな、と思っていましたし。でもこうして出たので大変にめでたい事で、ありがたい話です。

追記

遅くなりましたが、gem をリリースしましたので、次の手順でインストールしてみて下さい。

% sudo gem install pathtraq

なんか変なところとかあれば何らかの手段でご連絡下さい(コメント欄に書いて下さっても嬉しいですし、github でプルリクエストを送って下さるともっともっと嬉しいです!)。問題点があれば直します。バグをご指摘下さいました id:kmachu さんありがとうございました。gem 作る前に修正できたのでとっても助かりました。

ところでそれにつけてもサイボウズ・ラボの人はとっても対応が早くて素晴しいと思いました!

どこに要望を送ればいいのかなぁ、なんて悩みながら rubinius で遊んでたら猫が倒れてウンコ漏らしたから動物病院にあわてて連れていって入院させて家に戻ったらその子猫が今度は下痢しだしてまた病院に連れていって、それでほっとして気付いたらとっくに改善されていたというこの早さ。誠にありがとうございました。その心意気に感動したので Pathtraq を再インストールしました、アンインストールしてごめんなさい。

それにしても、日記に書くだけで問題点があっという間に解決されちゃうなんて、インターネットってすごいですね!

そういうわけですので、皆様 RubyPathtraq でなんかして色々遊びましょう。まぁ難しいこと考えなくても、普通にキーワード・URL検索APIRSSで返ってくるから色々監視するのに便利だったりすると思いました。

あと、皆様この頃やたらと暑いので猫の体調には気をつけてあげて下さい。

ブログ通信簿をやってみました

ブログ通信簿なるものをやってみました。

http://blogreport.labs.goo.ne.jp/tushinbo.rb

なんか普通らしいです。

http://file.dynamic-semantics.com/hatena/20080723-1.png

フリーターを目指せというアドバイスはどうかと思いました。

ところで URL の末尾が .rb になっているので、ひょっとして Ruby で作ったのでしょうかね。

ProtoObject の謎と superclass

とにかくなにかしておかないと、いつまでたっても Smalltalk 使えるようにならないので、まず手始めとして squeak のクラスブラウザで探検を始めております。何も分からない初心者がまず最初にやってみることと言えばクラス階層山登りですよね。適当なクラスを選んで眺めてみて、飽きたらスーパークラスに移動、そして眺める、の繰り返しです。そうこうしているウチに結局行き着いたのは ProtoObject だったわけですが、これはなんともえへへな感じになっていて陽気です。

ProtoObject subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.
ProtoObject superclass: nil

え、ProtoObject のサブクラスが ProtoObject なんですか!それは珍妙で素晴しい!

でも、これって継承関係が循環しちゃわないのでしょうかと心配になるところですが、よく見たら "ProtoObject superclass: nil" って書いてあります。どういう事? ProtoObject のサブクラスは ProtoObject だって書いてあるのに、まるでそれを急転直下否定するかのように ProtoObject のスーパークラスnil ってなに?実際どうなのかを試してみました。

ProtoObject superclass. "=> nil"
ProtoObject subclasses. "=> {Object . ObjectOut . ImageSegmentRootStub . MessageCatcher}"

結局 ProtoObject のスーパークラスには nil が指定されていて、ProtoObject のサブクラスに ProtoObject は入っていません。じゃあどういうわけで最初に "ProtoObject subclass: #ProtoObject" だなんて書いているのでしょうか。とっても不思議です。というか後から superclass を指定できるのであれば、subclass で指定したものってどうなるの?という疑問が湧いてきます。

まずは ProtoObject のまねをして、適当なクラスを作成して superclass: nil してみます。

Object subclass: #ClassA
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'.
ClassA superclass: nil

と書いて、accept してみると、びっくり!

ProtoObject subclass: #ClassA
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'.
ClassA superclass: nil

と勝手に書き換えられちゃいました。なんて強引なんでしょうか。でもこれでなんとなく "ProtoObject subclass: #ProtoObject" の正体が少しだけ分かった気がします。もしかしたら最初はなんか他のクラスの subclass だったかも知れないけど、superclass: nil で勝手に ProtoObject に書き変わって万歳な感じなんですね。

ホント?

なにか不安を感じたので、まずそもそも循環するような継承関係を作れないのかを確かめてみました。

ClassB subclass: #ClassB
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

という感じで accept してみたら、やっぱりなんか色々怒られました。ClassBuilder の validateSuperClass が云々だそうですが、ということはメッセージsubclass の本体は ClassBuilder というものなんですね。ここで validate してるからエラーになっているわけで、逆に言うとひょっとしてこの ClassBuilder というものを通さないでクラスを作成したりなんかしたりしちゃえば、色々無理のあることが出来たりするのでしょうか。とにかく普通のやり方では循環する継承関係を作成するのは困難であるようであり、そこから推測するに "ProtoObject subclass: #ProtoObject" も普通でないのでしょうから、やはり "ProtoObject superclass: nil" というのはそういうエラーを避けるための裏技の一種なのでしょうか?

いやいや、でも結局 superclass が ProtoObject じゃなくて nil なわけだから、これはどうしたものでしょう。では次に nil に対して subclass してみます?だって superclass が nil なわけだし、ひょっとしてメッセージ subclass を送れちゃったりするわけでしょうか。

nil subclass: #ClassC
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

これを accept すると、またしても書き換えられちゃいました!なんか反則っぽいよ!

ProtoObject subclass: #ClassC
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

そんなわけでまたしても ProtoObject が登場しました。一応 nil について確かめたところ、これは UndefinedObject のインスタンスで、インスタンスメソッドとして subclass が存在しているのでメッセージを送れちゃうわけですね。でもなぜ ProtoObject に書き換えられてしまうのでしょうか。良く分かりませんが、とにかくこれで ProtoObject がなぜああいう書き方になっているのかについては想像が出来るようになりました。

nil subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.

直接このように書いてしまうと、これが書き換えらえて次のようになるはずです。

ProtoObject subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.

でもこれは循環した継承関係になってしまうだけでなく ClassB の例において確かめたように validate で色々怒られてしまうわけです。よってこれを避けるためには ProtoObject superclass: nil を使うしかないということですね。きっと。だから最初の "ProtoObject subclass: #ProtoObject" の部分は多分 "nil subclass: #ProtoObject" の nil が書き換えられた後の残骸に過ぎなくって、ということだろうと予測します。

では本題です。結局のところ Class superclass ってのが出来るのなら、subclass での指定を無視してこれで好きに superclass を設定出来ることになるわけですよね。というわけでやってみます。

Object subclass: #ClassD
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

という ClassD を作成します。この ClassD に対して superclass を送ってみます。

ClassD superclass. "=> Object"
ClassD superclass: ClassD. "=> ClassD"
ClassD superclass. "=> ClassD"

これにより ClassD のスーパークラスが ClassD になってしまいました。おおあろうことかこれで循環した継承関係が出来てしまいました。じゃあ...

ClassD new asdfghjkl

としてみたら、

Recursive not understood error encountered

2034326876 Behavior>new
2034326472 UndefinedObject>?
2034302584 Compiler>evaluate:in:to:notifying:ifFail:logged:
2034306924 [] in ParagraphEditor>evaluateSelection
2034306424 BlockContext>on:do:
2034298236 ParagraphEditor>evaluateSelection
2034298144 ParagraphEditor>doIt
2034298420 [] in ParagraphEditor>doIt:
2034298052 Controller>terminateAndInitializeAround:
2034297960 ParagraphEditor>doIt:
2034297344 ParagraphEditor>dispatchOnCharacter:with:
2034297252 TextMorphEditor>dispatchOnCharacter:with:
2034297148 ParagraphEditor>readKeyboard
2034296964 TextMorphEditor>readKeyboard
2034290228 [] in TextMorph>keyStroke:
2034290136 TextMorph>handleInteraction:fromEvent:
2034290044 TextMorphForEditView>handleInteraction:fromEvent:
2034289900 TextMorph>keyStroke:
2034289808 TextMorphForEditView>keyStroke:
2034289716 TextMorph>handleKeystroke:
2034289348 KeyboardEvent>sentTo:
2034289256 Morph>handleEvent:
2034289164 Morph>handleFocusEvent:
2034289440 [] in HandMorph>sendFocusEvent:to:clear:
2034289532 [] in PasteUpMorph>becomeActiveDuring:
2034289072 BlockContext>on:do:
2034288980 PasteUpMorph>becomeActiveDuring:
2034288796 HandMorph>sendFocusEvent:to:clear:
2034288592 HandMorph>sendEvent:focus:clear:
2034287264 HandMorph>sendKeyboardEvent:
2034287172 HandMorph>handleEvent:
2034278244 HandMorph>processEvents
2034278360 [] in WorldState>doOneCycleNowFor:
2034278152 SequenceableCollection>do:
2034278060 WorldState>handsDo:
2034277968 WorldState>doOneCycleNowFor:
2034275992 WorldState>doOneCycleFor:
2034275900 PasteUpMorph>doOneCycle
2034032168 [] in >spawnNewProcess
2034032352 [] in BlockContext>newProcess

とか出て squeak が終了してしまいました。まったくもういきなり。

さてそろそろまとめますと、今回の教訓は、Smalltalk はこういうのなんでもアリ!ということです。クラスベースのオブジェクト指向言語ならば普通は継承関係が循環しないように作るものだろうと思いますが、Smalltalk であればそういう配慮を無効にすることすら出来てしまうわけですね。Rubyだとこうは書けないもんね。色々すごいなぁ、と思いました!

こんな感じで Smalltalk はとっても陽気で面白い言語ですね。でもひょっとしてこれって squeak だけの話だったりしないですよね?