フリーマンモナドにあるファンクタを使ってあるセマンティクスを適用するパターンを抽象化しようとしています。これを動機づけするために使用している実行例は、ゲーム内のエンティティに更新を適用することです。だから私は(私がコントロール-モナドフリーで自由なモナドの実装を使用しています)いくつかのライブラリをインポートし、この例の目的のためにいくつかの例タイプとエンティティクラスを定義します。フリーモナドへのセマンティクスの適用
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Free
import Control.Monad.Identity
import Control.Monad.Writer
-- Things which can happen to an entity
data Order = Order deriving Show
data Damage = Damage deriving Show
class Entity a where
evolve :: Double -> a -> a
order :: Order -> a -> a
damage :: Damage -> a -> a
-- Make a trivial entity for testing purposes
data Example = Example deriving Show
instance Entity Example where
evolve _ a = a
order _ a = a
damage _ a = a
-- A type to hold all the possible update types
data EntityUpdate =
UpdateTime Double
| UpdateOrder Order
| UpdateDamage Damage
deriving (Show)
-- Wrap UpdateMessage to create a Functor for constructing the free monad
data UpdateFunctor cont =
UpdateFunctor {updateMessage :: EntityUpdate, continue :: cont} deriving (Show, Functor)
-- Type synonym for the free monad
type Update = Free UpdateEntity
私は今、いくつかの基本的なを持ち上げますモナドへのアップデート:
liftF = wrap . fmap Pure
updateTime :: Double -> Update()
updateTime t = liftUpdate $ UpdateTime t
updateOrder :: Order -> Update()
updateOrder o = liftUpdate $ UpdateOrder o
updateDamage :: Damage -> Update()
updateDamage d = liftUpdate $ UpdateDamage d
test :: Update()
test = do
updateTime 8.0
updateOrder Order
updateDamage Damage
updateTime 4.0
updateDamage Damage
updateTime 6.0
updateOrder Order
updateTime 8.0
は、今、私たちは私たちは、上記のようにtest
モナドのインスタンスの異なる実装、または意味の解釈の可能性を提供する必要があり、無料のモナドを持っています。その後、いくつかの基本的なセマンティックな機能で、我々は次の二つの可能な解釈、基本的な評価として1とライターモナドとして1を与えることができます
interpret :: (Monad m, Functor f, fm ~ Free f c) => (f fm -> fm) -> (f fm -> a -> m a) -> fm -> a -> m a
interpret _ _ (Pure _ ) entity = return entity
interpret c f (Impure u) entity = f u entity >>= interpret c f (c u)
:私はこのために思い付くことができる最高のパターンは以下の関数で与えられます予備成形ログ:GHCiの中
update (UpdateTime t) = evolve t
update (UpdateOrder o) = order o
update (UpdateDamage d) = damage d
eval :: Entity a => Update() -> a -> a
eval updates entity = runIdentity $ interpret continue update' updates entity where
update' u entity = return $ update (updateMessage u) entity
logMessage (UpdateTime t) = "Simulating time for " ++ show t ++ " seconds.\n"
logMessage (UpdateOrder o) = "Giving an order.\n"
logMessage (UpdateDamage d) = "Applying damage.\n"
evalLog :: Entity a => Update() -> a -> Writer String a
evalLog = interpret continue $ \u entity -> do
let m = updateMessage u
tell $ logMessage m
return $ update m entity
テストこの:
> eval test Example
Example
> putStr . execWriter $ evalLog test Example
Simulating time for 8.0 seconds.
Giving an order.
Applying damage.
Simulating time for 4.0 seconds.
Applying damage.
Simulating time for 6.0 seconds.
Giving an order.
Simulating time for 8.0 seconds.
このすべてが正常に動作しますが、それは私に、それはMOことができることを少し違和感を与えます一般的な、またはより良い組織することができます。継続を提供する機能を提供することは、当初は明らかではなかったし、私はそれが最良のアプローチであるかどうかはわからない。私は、foldFree
とinduce
のような、Control.Monad.Freeモジュールの機能に関して、interpret
を再定義するいくつかの努力をしました。しかし、彼らはすべてうまくいかないようです。
私はこれと正しい行にいるのですか、誤った判断をしていますか?私が見つけたフリーモナドに関する記事のほとんどは、実際にこのようなものを使用するためのパターンではなく、効率や実装方法に集中しています。
これをある種のSemantic
クラスでカプセル化することも望ましいと思われるので、新しいタイプのファンクタをラップしてこのクラスのインスタンスにすることで、フリーモナドとは異なるモナドインスタンスを作ることができます。しかし、私はこれをどうやってやっているのかはかなり分かりませんでした。
UPDATE -
私は、彼らは両方とも非常に有益で思慮深く書かれているように私は両方の答えを受け入れたことがしたいです。
interpret :: (Functor m, Monad m) => (forall x. f x -> m x) -> Free f a -> m a
interpret evalF = retract . hoistFree evalF
(retract
とhoistFree
はControl.Monad.FreeでエドワードKemmetの自由なパッケージにあります):しかし最後には、受け入れ答えを編集した後、私がいた機能が含まれています。
pipes
、operational
、sacundim's free-operational packageの3つはすべて、将来的に私にとって非常に有用であるように見えます。皆さん、ありがとうございました。
これは本当に魅力的です!私がこれらの異なるバージョンの 'interpret'を非常に見ている時間。 –