PySpec3について

そろそろberryMQも実装が落ち着いてきているので(マルチスレッドのロックとか、アマアマな部分はたくさんあると思うけど)、pySpec3も考え始めようかな、と思っているところです。

berryMQはもともとPySpecのリライトのためのバックエンドとして作り始めたのだけど、その後に知って、現在絶賛猛プッシュ中のSphinxに出会ってからはちょっと別路線を考え中。Cucumberみたいなのも面白そうだしね。あと、pyspecは記号が多くて、シフトキーで指が疲れるという問題もあるので。ということでSphinx拡張&ビルダーとして実装する方向で検討中。TDD→BDDはドキュメント化の流れなので、いっそのこと、ドキュメントに埋め込んでしまう方が潔いはず。とちぎRuby会議02では「文芸的テスト」と言っていたけど。

Sphinx拡張は、htmlやlatex、pdfの場合には整形してテスト仕様書として出力し、pyspecという名前のビルダーだったら、構築後のイベントを拾って、中のテストを全部たどって実行・・・という流れかな。普通にUnitTest的に実装していたpyspec2までは、before, specは関数として個別に実行していたけど、くっつけたコードを作ってevalしてやればいいか。エラーの見せ方とかは工夫が必要かな。

BDD的なサンプル

.. pyspec::

   .. before:: 空のスタック

      empty_stack = Stack()

   .. spec:: 空かどうか判断できる

      empty_stack.empty() should be True

cucumber的なサンプル

.. feature:: ログインページ

   :when: ログインページを表示して
   :and: フォームのユーザ名に"test", パスワードに"testpass"と入れて送信ボタンを押すと
   :then: ログインに成功する

最近悩んでいるのが、テストコードがシンプルになるように強制するかどうか。たとえば、ユニットテストフレームワーク上で、
ちょっとの行数しか書きたくなくならないような、文法にしてもいいけど、あまり強制しすぎて、テストのためのアダプターコードが増え過ぎちゃうと、逆に仕様変更に弱いコードになりそうな気もするし。適度に無駄は必要なんだろうな、と思っているところ。

berryMQ作業メモ 9/27版

http://bitbucket.org/shibu/berrymq/
http://berrymq.shibu.jp/

プロセス間通信の実装メモ

  • JSON-RPCをバックエンドに利用
    • 最初のテストは、JSON-RPCレベルで直アクセスでおこなった。(test_interprocess_low.py)
    • 次に、実際のAPIの体裁を整えるラッパー部分を実装している最中。(test_interprocess.py)
  • Connectionクラスを作って、そいつがJSON-RPCの次に低レベルな部分を担当
    • Connectionクラスは基底クラスと、Style02のプロセス間通信のクライアント/サーバ
    • ConnectionQueueクラスはStyle03のプロセス間通信のサーバ側
    • ConnectionQueueClientクラスはStyle03のプロセス間通信のクライアント側
    • ConnectionInteractiveクラスは、Style01のプロセス間通信のクライアント/サーバ
  • JSON-RPCに公開するのは、ExportedFunctionクラスのオブジェクト
    • こいつはフォワードするだけで、中身はほぼ空。矢面に立つクラスは仕事しない方がいいと思われるので。主にセキュリティ的な意味で。
  • 仕事するのはConnectionPointクラス
    • 他のノードとのコネクション情報などを管理する。
    • berryMQのローカル通信で来たメッセージを外部に中継する
    • 外部から来たメッセージをローカルに中継する。また、他のノードにも中継する

ネットワークのプログラムの開発手順はいつも悩む。今回はJSON-RPCの直接アクセス部分と、高水準なAPIの2段階に分けて行った。今回はやらなかったけど、JSON-RPCやXML-RPCであれば、Excelか何かに応答手順を書いておいて、クライアント、サーバの両方のモックを用意してテストする、というのもできるかもしれない。以前はMockSocketなんぞを作ってみたけど、あまり低水準すぎてもテストはしにくいので。

berryMQ作業メモ 9/24版

http://bitbucket.org/shibu/berrymq/
http://berrymq.shibu.jp/

こちらに書くのは久しぶり。プロセス間通信の実装中。

新機能案

  • Growlへ転送機能
    • GrowlTransfer(フィルタ)みたいな感じで実装
    • UDPは動いたけど、TCPの方が主流?GNTP for Python(http://github.com/kfdm/gntp/)はGrowlからの返信待ちで止まる。
    • Growlからのコールバックもメッセージとして受け取れるといいかも。
  • コールバック
    • 呼ぶときに、「受信したらこのメッセージを逆送信」みたいな指定をする。同じIDを持たせる。
  • Quick Sequence Diagram Editorが面白い
    • 通信状況(送信スタート、中継、受信完了、返信開始・・・みたいな感じの)情報のログを取る
    • twitterを呼ぶ側のノードで(中継ON)になっていたら、その情報をメッセージに載せる。乗っていたらログを出力。
    • これもテキスト情報として出力するのもありだけど、QSDEの受け口に情報を流し込むのもありかな。

berryMQ作業メモ 8/1版

http://bitbucket.org/shibu/berrymq/

ちょこっとずつ実装してます。

前回から完了した項目

  • Python2.X版
    • Python2.4サポートを追加(2.4, 2.5, 2.6に対応)
  • Python3.X版
    • 基本機能を移植
    • シングルスレッド版を移植
  • Python2.X, 3.X共通
    • テストをunittest.pyベースに書き換える
    • 名前空間の機能をRuby版から移植。
    • json rpc機能を実装
    • マルチスレッド対応
  • Ruby
    • テストを増やした

これから実装、移植、テストを行う機能

  • 全部
    • ネットワーク連携機能を実装する
    • pull型のAPIを検討
    • 優先順位つきのキューの検討
  • Ruby
    • マルチスレッド対応

今回の一番大きかった作業はJSON RPCかな。表面的な部分はほとんど変わってないですが。まずはRubyのマルチスレッド対応からかな。現状のpush型のAPIだけだと、メッセージが来たらほとんど即時でクライアント側で呼び出されるので、優先順位は入れてもほとんど無意味だけど、pull型のAPIができたらちょっとは意味があるっぽい。今はtwitter()というメソッド名でメッセージを送るけど、優先順位はtwitter_louder(), twitter_loudest()の順番に高くなる、というAPIにする予定です。よく分からない数値よりも分かりやすいでしょ?

id:Voluntas とチャットしながら仕様を決めているけど、意見交換するといいね。

berryMQって何ができるの?何のために実装しているの?

いろんなところでいろんな人に言っているけど、いったん文字にまとめておきます。

何ができるか?

すぐに思いつく用途としては、

  • モジュールを疎結合にして設計をシンプルに
  • GUIの状態遷移など、ゆるい連携機能に
  • ログ処理
  • スケールアップする仕組みとして
  • 言語、マシン間の分散プラットフォームとして

といった感じでしょうか?用途は今後も考えて、いろいろ増やしていきたいと思っています。

何のために実装しているの?

berryMQ実装メモ

http://bitbucket.org/shibu/berrymq/

今まではmqasとかいう、タイプしていても手がつりそうな名前でしたが、 id:voluntas とチャットしていて、berryMQという名前になりました。オンメモリで永続化などはしない、簡易軽量なMessage Queueのライブラリ。ライブラリの目標としては、簡単にプログラミングできることが一番重要な目標。小さいとか、そういうイメージが伝えられるような名前に。そんでもって、親しみやすい名前に。ということで、実装メモなどはこちらに書いていきます。上記のところには日本語は書きません。

実装済み機能

  • Python2.X版
    • 基本的なメッセージ送信
    • オブジェクトを受信先とする
    • クラスを受信先とする
    • 関数を受信先とする
  • Ruby
    • 名前空間
    • 基本的なメッセージ送信
    • オブジェクトを受信先とする

これから実装、移植、テストを行う機能

  • Python2.X版
    • テストをunittest.pyベースに書き換える
    • 名前空間の機能をRuby版から移植。
    • json rpc機能を実装
    • ネットワーク連携機能を実装する
    • マルチスレッド対応
  • Python3.X版
    • 基本機能を移植
    • テストをunittest.pyベースに書き換える
    • 名前空間の機能をRuby版から移植。
    • json rpc機能を実装
    • ネットワーク連携機能を実装する
    • マルチスレッド対応
  • Ruby
    • テストを増やす
    • json rpc機能を実装
    • ネットワーク連携機能を実装する
    • マルチスレッド対応

これから実装方法を決める機能

  • priority queue
  • 同期送信
  • 同期送信で送信エラーがあったときのコールバック

Erlang実装パターン2

id:voluntas からskypeで教わった話も一緒に入れています。

EDocスタイルの型宣言

コメントで@spec, @typeというのを使って型の宣言をする。ドキュメントジェネレータ、静的プログラムチェックツールで使用される。これを言語仕様として拡張して入れてしまおうというEEP-8(http://articles.shibu.jp/article/30833304.html)もある。ただし、使っているのはRabbitMQぐらいらしい。EUnitはEDoc。

@typeは型の宣言。@specは関数の入出力の宣言に使うみたい。typeは関数宣言部でも@specの補助として使えるっぽい。@throwsを使うと、例外が投げられる可能性についても書ける。

%% @type moduleName() = atom()
%% @type functionName() = atom()

%% @throws {bad_test, term()}
%%       | {generator_failed, exception()}

%% @spec (testIterator()) -> [integer()]
iter_id(#iter{pos = N, parent = Ns}) ->
    lists:reverse(Ns, [N]).

配列の操作。

nextが空か、そうじゃないかで関数を分けている。典型的なパターン。

# python
def iter_next(I):
  if len(I.next) == 0:
    空のときの処理
  else:
    そうじゃないときの処理

関数直下のif文はこのように分解する。もちろん、caseも使えるはず。

%% Erlang
%% @spec (testIterator()) -> none | {testItem(), testIterator()}

iter_next(I = #iter{next = []}) ->
    nextが空の時の処理;
iter_next(I = #iter{next = [T | Ts]}) ->
    nextが空じゃない時の処理.

if文はcaseに置き換える

Pythonの場合はこう。

if is_string(S):
  true時の処理
else:
  そうではない時の処理

Erlangだとこうかく。

case is_string(S) of
  true -> true時の処理;
  false -> そうではない時の処理;
end

例外処理

try
  正常時の文
catch
  例外の型 -> キャッチしたときの処理
end

throw({型のアトム, 追加情報})
で例外を投げる。

文字列の結合

"test: " ++ S

と結合できる。

try ofという書き方もある

もちろん、 ->をたくさん書けば返り値でパターンマッチで処理の振り分けもできる。ただし、こういうコードは今のところお目にかかったことがない。プログラミングErlangの本の中にもない。 id:voluntas によると、これをやるぐらいなら、tryの返り値に対して、case ofを使った方がいいよ、とのこと。僕もそう思う。

    try M:module_info(exports) of
        Es ->
            Fs = get_module_tests_1(M, Es),
            W = ?DEFAULT_MODULE_WRAPPER_NAME,
            case lists:member({W,1}, Es) of
                false -> Fs;
                true -> {generator, fun () -> M:W(Fs) end}
            end
    catch
        error:undef ->
            throw({module_not_found, M})
    end.

宣言的なコメント

将来はこうしたいけど、今はうまくいかないからこっちを使っている、というコメント。テスティングフレームワークみたいに、自分で自分のテストなど、ドッグフード系のシステム開発にはこういうのは不可欠かも。Erlangに限った話ではないけど。

%%{application, eunit}. % this currently causes a loop
%% We use the below until loop detection is implemented

タプルをそのまま画面に出力できない

io:format(atom).

はOKだけど

io:format({atom, "sample test"}).

はエラーで落ちる。ちなみに、formatは引数を一つだけとれる。一つしか取れない。

whenはパターンマッチ後に使える

whenを指定すると、パターンマッチの情報を詳細に指定できる。whenにはオリジナルの関数はつっこめないけど、マクロはOK。