このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

カウンター・モナド

<html><!-- counter-monad.html -->
<head>
  <title>Counter Monad</title>

  <script src="CounterMonad.js" ></script>

</head>

<body>
  <h1>Counter Monad</h1>

</body>
</html>
/* CounterMonad.js */

// オペレーション・モノイド
var CounterOp = {
  // 定数
  UP : 117, // 'u' = upオペレーション
  DOWN : 100, // 'd' = downオペレーション

  // モノイド単位
  unit : function() {
    return "";
  },
  // モノイド乗法
  mult : function(x, y) {
    return ("" + x + y);
  }
};

// カウンター・オブジェクト(シングルトン)
var Counter = {
  // 定数
  QUEUE_MODE : 1, // オペレーション・リクエストをキューイングする
  DIRECT_MODE : 0, // オペレーション・リクエストをすぐに実行する

  UPPER_BOUND : 9,
  // LOWER_BOUNDは0に固定する

  // フィールド、ここでは宣言しているだけ、初期化はinitで行う
  _mode : undefined, // 実行モード
  _value : undefined, // 状態値
  _operations : undefined, // 累積オペレーション値
  _action : undefined // オペレーション・リクエストの実行方式

};

/* カウンター・リクエストの実行方式(ストラテジー) */

// 有界カウンタ方式
var BoundedCounterAction = {
  /* カウンターはシングルトンなので、名前で直接参照している。
   * 行儀がいいとは言えないが、よしとする。
   */
  // カウンタのアップ
  doUp : function() {
    if (Counter._value < Counter.UPPER_BOUND) Counter._value++;
  },
  // カウンタのダウン
  doDown : function() {
    if (Counter._value > 0) Counter._value--;
  },
  name : "BoundedCounterAction" // 識別用
};

// サイクリック・カウンタ方式
var CyclicCounterAction = {
  // カウンタのアップ
  doUp : function() {
    if (Counter._value < Counter.UPPER_BOUND) {
      Counter._value++;
    } else {
      Counter._value = 0;
    }
  },
  // カウンタのダウン
  doDown : function() {
    if (Counter._value > 0) {
      Counter._value--;
    } else {
      Counter._value = Counter.UPPER_BOUND;
    }
  },
  name : "CyclicCounterAction" // 識別用
};

/*  以下にメソッド群 */

Counter.init = function(action) {
  Counter._mode = Counter.QUEUE_MODE;
  Counter._value = 0;
  Counter._operations = CounterOp.unit();
  if (action) {
    Counter._action = action;
  } else {
    Counter._action = BoundedCounterAction;
  }
};

Counter.setMode = function(mode) {
  Counter._mode = mode;
};

Counter.operate = function (op) {
  var old = Counter._operations;
  Counter._operations = CounterOp.mult(old, op);
  if (Counter._mode == Counter.DIRECT_MODE) {
    Counter.execOperations();
  }
};

// キューイングされたオペレーションの実行
Counter.execOperations  = function() {
  var ops = Counter._operations;
  for (var i = 0; i < ops.length; i++) {
   var op = ops.charCodeAt(i);
   switch (op) {
    case CounterOp.UP:
     Counter._action.doUp(); break;
    case CounterOp.DOWN:
     Counter._action.doDown(); break;
    default: throw "Unbelievable!";
   }
 }
 // 累積されたカウンタ操作リクエストをクリアする
  Counter._operations = CounterOp.unit();
};

/* 対話的に使用する便利関数 */
/*
 * start(a) -- 初期化
 *             aにはカウンタ操作の実装を渡す
 *             デフォルトは、BoundedCounterAction
 *             他に、CyclicCounterAction が使える
 * u()      -- カウントアップ
 * d()      -- カウントダウン
 * x()      -- 溜まっているオペレーション・リクエストの実行
 * q()      -- キューの表示
 * v()      -- 値の表示
 * qmode(f) -- キューイングするかどうかの設定
 *             fがtrueならキューイングモードになる
 */

// initって名前はナニカとかち合っているようだ(恐い)
function start(a) {
  Counter.init(a);
}

function u() {
  Counter.operate("u");
}

function d() {
  Counter.operate("d");
}

function x() {
  Counter.execOperations();
}

function q() {
  return Counter._operations;
}

function v() {
  return Counter._value;
}

function qmode(f) {
  if (f) {
    Counter.setMode(Counter.QUEUE_MODE);
  } else {
    x(); // キューをフラッシュする
    Counter.setMode(Counter.DIRECT_MODE);
  }
};