Weeで継続

InfoQに、Weeという継続ベースのWebアプリケーションフレームワークが紹介されていたので、試してみました。

gemでインストール後、インストールディレクトリ下のexamples/demo.rbを実行すると、自動的にWEBrickが起動します。

Counterというカウントの増減ができるページが表示され、++で1増加、--で1減算となるのですが、途中で戻るボタンを押下し、その後++/--を押下しても戻った地点から計算されます。

Counter

同じことをHTTP Sessionを使ってやると、戻るボタンを押下しても最後にsubmitした値からの加減値となり、挙動が異なります。数値をhiddenに埋め込むとできるので、この例だとメリットを感じづらいところはありますが。

Weeのアプリケーションではaction実行時にbacktrackというメソッドが呼ばれ、そこでステートを保存することができます。

1
2
3
4
  def backtrack(state)
    super
    state.add_ivar(self, :@count, @count)
  end

それがURLのpage-idと関連付けられることで、戻るボタン押下時にステートが戻るようになっているとのこと。

The solution to this problem is to take snapshots of the components state after an action is performed and restoring the state before peforming actions. Each action generates a new state, which is indicated by a so-called page-id within the URL.

次にexamples/continuation.rbを動かしてみました。こちらは、callccメソッドにMessageBoxコンポーネントを引数で渡して呼ぶと、htmlが表示されます。表示されたページにはOK/Cancelボタンがあり、どちらかを押下するかによって次の処理が決まる、というものです。

clickメソッドが1回呼び出されると、その中のcallccメソッドの呼び出し回数分(条件によって2~3回)ページが表示されている、ということになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$LOAD_PATH.unshift "../lib"
require 'rubygems'
require 'wee'
require 'demo/messagebox'

class MainPage < Wee::Component

  def initialize
    super
    add_decoration(Wee::PageDecoration.new("Test"))
  end

  def click
    if callcc Wee::MessageBox.new('Really quit?')
      callcc Wee::MessageBox.new('You clicked YES')
    else
      callcc Wee::MessageBox.new('You clicked Cancel')
      callcc Wee::MessageBox.new('super')
    end
  end

  def render(r)
    r.anchor.callback_method(:click).with('show')
  end

end

Wee.runcc(MainPage)

あまり継続の良さを理解できていませが、複数のリクエストに跨ってステートフルなコンポーネントを簡単に扱うことができるのがWeeの特徴の1つなのだと思います。

上記ソースのcallccはWeeのメソッドで、Kernel.callccとは異なりますが、その実装は以下のようにKernel.callccを呼び出していました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    #
    # Similar to method #call, but using continuations.
    #
    def callcc(component)
      delegate = Wee::Delegate.new(component)
      answer = Wee::AnswerDecoration.new

      add_decoration(delegate)
      component.add_decoration(answer)

      answ = Kernel.callcc {|cc|
        answer.answer_callback = cc
        session.send_response(nil)
      }

      remove_decoration(delegate)
      component.remove_decoration(answer)
      return *answ.args
    end

Sorry, comments are closed for this article.