Hatena::ブログ(Diary)

ひだちのいろの日記 RSSフィード

2011/08/04

[]Javascript で複数のコールバックをまとめて受け取る

複数のコールバックをまとめて受け取るためのクラスを書いてみました。

JavaScript は何かとコールバックが多用される言語です。単発のコールバックならば問題はありませんが、複数のコールバックを同時に使用して、それらが全て呼ばれたときに処理を進めるなどの処理は記述が複雑になりがちです。

そこでこの Callback クラス。

var cb = new Callback();
request(..., cb.here());
request(..., cb.here());
request(..., cb.here());
cb.then = function(result1, result2, result3) {

}

上記のように「複数のコールバック呼び出しが完了した時点で処理をすすめる」パターンを簡素に書くことができます。

各コールバックに渡された引数もまとめて最後の then 関数に渡され、しかもその順番は cb.here を呼び出した順番と対応することが保証されます。(コールバックが呼び出された順ではありません)

これで複数リソースの読み込みも怖くない!

var Callback = (function () {

  /**
   * 複数のコールバックをまとめて一つのコールバックにします。
   * @constructor
   * @example
   * var cb = new Callback();
   * asyncFunc1(..., cb.here()); // f('guitar') とコールバックされる
   * asyncFunc2(..., cb.here()); // f('fiddle','banjo') とコールバックされる
   * asyncFunc3(..., cb.here()); // f()  とコールバックされる
   * asyncFunc4(..., cb.here([])); // f('mandolin') とコールバックされる
   * cb.then = function(a, b, c, d) {
   *   // cb.here() を呼んだ数だけ引数が渡る
   *   // a は 'guitar'
   *   // b は ['fiddle', 'banjo']
   *   // c は null
   *   // d は ['mandolin']
   * }
   */
  var Callback = function() {
    this.resultList = [];
    this.stepCount = 0;
    this.callCount = 0;
    /**
     * すべてのコールバックをまとめて最終的に呼び出される関数。
     */
    this.then = null;
  };
  
  /**
   * コールバック関数を生成して返す。
   * 生成した関数が空引数で呼ばれた場合は null を。
   * 1引数で呼ばれた場合はその値を。
   * 2引数以上で呼ばれた場合は配列を登録し、
   * すべてのコールバックが呼び出された時点で
   * 登録した引数を then に渡して呼び出す。
   * here に空配列を渡した場合は引数の数にかかわらず配列を登録する。
   * @return {function(...)}
   */
  Callback.prototype.here = function() {
    var registerArray = arguments.length == 1 &&
        arguments[0] instanceof Array &&
        arguments[0].length == 0;
    if (arguments.length != 0 && !registerArray) throw new Error(
        "Callback#here に引数が渡されました。" +
        "xx.here() と書くべき場所で xx.here としている可能性があります。");
    return (function(callback, stepCount) {
      return function() {
        var result = arguments;
        if (!registerArray && result.length == 0) {
          callback.resultList[stepCount] = null;
        } else if (!registerArray && result.length == 1) {
          callback.resultList[stepCount] = result[0];
        } else {
          callback.resultList[stepCount] = [];
          for (var i = 0; i<result.length; i++) {
            callback.resultList[stepCount][i] = result[i];
          }
        }
        if (++callback.callCount >= callback.stepCount) {
          if (callback.then != null) {
            callback.then.apply(null, callback.resultList);
            Callback.call(this);
          } else throw new Error("then が未登録です");
        }
      }
    })(this, this.stepCount++);
  };

  return Callback;
})();

コードはご自由にお使いください。

2011/07/17

こんな俺言語を考えてみた

架空の言語の仕様を考えるのが趣味です、というわけで

こんな俺言語を考えてみました。

言語実装の経験はないのでおかしなところは大目に見つつ教えてください。

1. 変数がある

a:Int = 2
b = 3 // 型は推測される

2. 変数には再代入できない

a = 2
a = 3 // ERROR!

3. 関数がある

// 常に 0 を返す関数
// 引数が同じなら戻り値も同じ, 故に引数がない関数は値と同列
zero = { 0 }
zero // 0

4. 引数をとる

inc = { n:Int, n+1 } // n が引数, 1 足す
inc(1)               // 2

5. 引数をいっぱいとる

sum = { a:Int, b:Int, a+b }

6. デフォルト値を持つ引数

round = { f:Double, virtual base:Double=1, (略) } // f を base の倍数に丸める
                                                  // base はデフォルト値を持つ
round(3.5) // 4

7. スコープを外に出す

Point = { x:Int, y:Int, this } // this で現在のスコープが外に出る
p = Point(-1, 1)               // p が外に出たスコープ
p.x                            // 値は-1, スコープの中にアクセス可能
p.y                            // 値は1

8. スコープに変数を加える

Point = { x:Int, y:Int, r = x*x + y*y, this }
p = Point(3, 4)
p.r   // 5

9. 他のスコープを展開する

Point = { x:Int, y:Int, r = x*x + y*y, this }
p = Point(3, 4)

v = { extract p, r } // v=5, 関数の定義に似ているが引数のない関数は値と同列

10. this から隠したい変数

Point := { x:Int, y:Int, private r=sqrt(x*x+y*y), this }
p = Point(3,4)
p.r // エラー
{ extract p, r } // エラー

11. 展開後の変数を this から隠す

p = Point(3,4)
v1 = { private extract p, x }      // 使える
v2 = { private extract p, this }.x // エラー

12. this したものを外部から与える

p = Point(3,4)
getR = { private extract _:Point, sqrt(x*x+r*r) }
v = getR(p) // v=5

// p.getR とはできないんだよなぁ…

13. 「.」はシンタックスシュガー

p.r               // どっちも
{ extract p, r }  // おなじ

14. カーリー

sum = { a:Int, b:Int, a+b }
sum1 = sum(1, _)
v = sum1(2) // v=3

15. カーリー化その2

sum2 = sum(b=2)
v = sum2(1) // v=3

16. カーリー化していても展開

{
  extract Point(y=2) // abstract x, y=2, r=(略) が展開される
  r // エラー
}

17. x を後付け

{
  extract Point(y=2)
  x=1
  r // エラーじゃない
}

18. 別々の実態から

{
  extract Point(x=1) // x=1, abstract y, r=(略) が展開される
  extract Point(y=2) // abstract x, y=2, r=(略) が展開される
  // r の実体が同じなので衝突してもエラーにならない
  r
}

とりあえずここまで

2011/04/14

[]xyzzy でメジャーモードを簡単に作れるように

11/04/20 キーワードを読み込むときに大文字小文字を区別するように変更

xyzzy でメジャーモードを作るとき、ちゃんとしたものを作るとなれば時間も手間もかかるのですが、簡単なものならだいたいやるべきことは決まっています。

そこでなんちゃってメジャーモードを簡単に作れるように共通処理を関数化してみました。

(defun majar-mode-template (name &key keyword syntax key ext)
  """
  メジャーモードのテンプレートです。
  name     メジャーモード名のシンボルです。xxx-mode の xxx にあたります。
           またモード表示行の括弧内には Xxx(nstring-capitalize name) を
           表示します。
  keyword  t ならばファイル名 Xxx をキーワードファイルとして読み込みます。
  syntax   シンタックステーブルを渡すとそれを利用します。
  key      キーマップを渡すとそれを利用します。
  ext      正規表現を渡すとマッチするファイル名とモードを関連付けます。
  """
  (flet ( 
    ; フォーマットに従ってシンボルを生成する関数
    (symbol (f) (intern (format nil f (symbol-name name)))))
    (let* (
      (mode (symbol "~A-mode"))
      (mode-name (nstring-capitalize (format nil "~A" (symbol-name name))))
      (mode-hook (symbol "*~A-mode-hook*"))
      (keyword-table (symbol "*~A-keyword-hash-table*"))
      (keyword-file mode-name)
      (syntax-table (symbol "*~A-mode-syntax-table*"))
      (key-map (symbol "*~A-key-map*")))
      (eval `(progn 
        ; スペシャル変数の定義
        (defvar ,mode-hook nil)
        
        ,(when key `(defvar ,key-map ',key))
        ,(when keyword `(defvar ,keyword-table nil))
        ,(when syntax `(defvar ,syntax-table ,syntax))
        ; モード関数の作成
        (defun ,mode ()
          (interactive)
          
          ; ローカル変数の削除
          (kill-all-local-variables)
        
          ; 名前二つほど設定
          (setq buffer-mode ',mode)
          (setq mode-name ,mode-name)
          
          ; キーワードの読み込み処理
          ,(if keyword `(progn
            (make-local-variable 'keyword-hash-table)
            (setq keyword-hash-table
              (setq ,keyword-table (or ,keyword-table
                (load-keyword-file ,keyword-file nil))))))
          
          ; シンタックステーブルの適用
          ,(if syntax `(use-syntax-table ,syntax-table))
          
          ; キーマップの適用
          ,(if key `(use-keymap ,key-map))
          
          ; フックを走らせる
          (run-hooks ',mode-hook))))
      
      ; モードを拡張子と対応付ける
      (when ext
        (push (cons ext mode) *auto-mode-alist*)))))

この関数を使うと、たとえば謹製なんちゃって scala-mode は以下のように書けます

(provide "scala-mode")
(majar-mode-template 'scala
  :keyword t
  :syntax (let ( (st (make-syntax-table)) )
    (set-syntax-start-c++-comment   st #\/)
    (set-syntax-end-c++-comment     st #\LFD)
    (set-syntax-start-multi-comment st "/*")
    (set-syntax-end-multi-comment   st "*/")
    (set-syntax-string              st #\")
    (set-syntax-match               st #\( #\))
    (set-syntax-match               st #\{ #\})
    (set-syntax-match               st #\[ #\])
    st)
  :key (let ( (m (make-sparse-keymap)) )
    (define-key m #\RET 'newline-and-indent)
    ... 好きなようにキー登録
    m)
  :ext "\\.scala$")

2010/11/21

[]Gst::Identity のバグ

  • Ubuntu 10.04
  • gstreamer-0.10.30
  • gstreamermm-0.10.8

以上の環境において

Glib::RefPtr<Gst::Identity> identity =
  Gst::Identity::create();

とすると identity の src パッドを他のエレメントに接続する段になってエラーとなるようです。以下ならうまくいきます。

Glib::RefPtr<Gst::Element> identity =
  Gst::ElementFactory::create_element("identity");

2010/10/28

[]TABを押したとき状況に応じてインデントしたり補完したり

debbrev を用いた補完は便利なんだけどデフォルトキーバインドが押しにくい。

補完機能のキーバインドは C-SPC が VisualStudioeclipse などで 使われているけど、emacs ではマーク設定に使用するので衝突してしまう。

そこでシェルで補完機能に使われている TAB に白羽の矢が立つのだが、こちらもインデントで使用済。

でもインデントと補完なら住み分けができるんじゃない? ってことでカーソルが行頭にあるかカーソル前が空白のときはインデント、そうでなければ補完を行う Emacsスニペットを書いてみた。C++ モードの挙動を変更する形で。

(add-hook 'c++-mode-hook 
  '(lambda ()
    (define-key c++-mode-map "\t" 
      (lambda ()
	(interactive)
	(if (looking-back "\s\\|^") (c-indent-command) (command-execute 'dabbrev-expand))))))