Just $ A sandbox

プログラミングと計算機科学とかわいさ

処理の切り替えとFreezeパターン

以下、Objectというとメソッドと適当な引数を与えると自分自身を返したり返さなかったりするデータ型、またはobjectiveのObjectということにしておきます。

newtype Object xs = Object { runObject :: forall a. Methods xs a -> ctx (Object xs) a }

さてオブジェクトを実行し続けて、あるタイミングで別の処理に切り替えたいということはままある。一番よくある例ではあるタイミングでオブジェクトが死ぬ、死んだらオブジェクトを破棄する処理に移行する、みたいなことがしたい、とか。

そのままオブジェクトを丸ごと破棄してGCに回収させればよいのであれば何も考えなくていいんだけれど、大抵の場合は後処理があり、後処理には死ぬ直前のオブジェクトからデータを拾ったり、ということはまぁあるよねと。

おそらく一番簡単なのは isDead フラグをオブジェクトの内部状態として持たせておいて isDead を毎回確認するというもので、これは意外と悪くないのだけれどフラグが必要なため内部状態が1つ増えるわけである。

管理する内部状態が増え、しかもこのようなものが全てのオブジェクトに生えることを許すわけにはいかないのでこれをやめたい。で、特定のタイミングで処理を切り替えたい場合には、次のようなFreezeパターンを使ってみるのがいいんじゃないかという提案。

data Freeze w a = Freeze w a | Keep w

_Frozen :: Lens' (Freeze w a) w

名前の通り、普通の値は Keep に入れて、死んだオブジェクトあるいは一旦処理を切りたい時に Freeze に入れる。これを実際にオブジェクトを定義する時に使うときには例えば次のようにする。

newtype FreezeT w m a = FreezeT { runFreezeT :: m (Freeze w a) }

op'switch :: Object xs -> FreezeT (Object xs) m a

--

runAndKill :: Object xs -> (Object xs -> m r) -> (Object xs -> DeleteFlag -> m r) -> m r
runAndKill obj update kill = do
  obj' <- op'run obj

  fw <- runFreezeT $ op'switch obj'
  case fw of
    Freeze o a -> kill o a
    Keep o -> update o

  where
    op'run :: Object xs -> m (Object xs)

例えばオブジェクトを一旦Freezeさせてから特定のタイミングで再初期化して復活させる、みたいなこともあると思うのでそういうこともやってみよう。

-- 初期化
op'initialize :: args -> m (Object xs)

-- run -> switchを繰り返す
-- switchは死んだかどうかのチェックに使う
op'run :: Object xs -> m (Object xs)
op'switch :: Object xs -> FreezeT (Object xs) m a

-- 死んだobjectを復活させる
op'reset :: Object xs -> a -> args -> Object xs

-- 死んだobjectを破棄する
op'delete :: Object xs -> a -> m ()

--

runObject args = do
  obj0 <- op'initialize args
  (obj,a) <- mainloop obj0
  op'delete obj a

  where
    mainloop objN = do
      objN' <- op'run objN

      fw <- runFreezeT $ op'switch objN'
      case fw of
    Freeze w a | breakMainLoop -> return (w,a)
    Freeze w a -> mainloop $ op'reset w a args
    Keep w -> mainloop w

こういう感じで、「一旦止めて」「特定のタイミングで別の処理に切り替える」みたいなことがしたいときは便利なんじゃなかろうか。

最近思いついて使っているので共有。