Sto cercando di astrarre il modello di applicare una certa semantica a una monade libera su qualche functor. L'esempio corrente che sto usando per motivare questo è applicare gli aggiornamenti a un'entità in un gioco. Così ho importare alcune librerie e definire alcuni tipi di esempio e una classe di entità ai fini di questo esempio (sto usando l'attuazione monade libera nel controllo-monade-libero):Applicare la semantica alle monadi libere
{-# 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
ora sollevare un po 'di base aggiornamenti in monade:
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
Ora abbiamo la monade libera, abbiamo bisogno di fornire la possibilità di diverse implementazioni, o interpretazioni semantiche, di esempio monade, come test
sopra. Il modello migliore che posso venire con per questo è dato dalla funzione seguente:
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)
Poi, con alcune funzioni semantiche di base che può dare i due seguenti interpretazioni possibili, uno come valutazione di base e uno come una monade scrittore registrazione preformatura:
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
Testing questo GHCI:
> 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.
questo tutto funziona benissimo, ma mi dà una sensazione un po 'a disagio che potrebbe essere mo in generale, o potrebbe essere meglio organizzato. Dover fornire una funzione per fornire la continuazione non era ovvio in un primo momento e non sono sicuro che sia l'approccio migliore. Ho fatto diversi sforzi per ridefinire interpret
in termini di funzioni nel modulo Control.Monad.Free, come ad esempio foldFree
e induce
. Ma sembra che non tutto funzioni.
Sono sulla linea giusta con questo, o sto facendo un errore di valutazione? La maggior parte degli articoli sulle monade libere che ho trovato si concentrano sulla loro efficienza o su diversi modi per implementarli, piuttosto che su modelli per utilizzarli in questo modo.
Sembra inoltre auspicabile incapsulare questo in una sorta di classe Semantic
, quindi potrei semplicemente creare diverse istanze monad dalla mia monade libera avvolgendo il functor in un newtype e rendendolo un'istanza di questa classe. Non riuscivo a capire come farlo comunque.
AGGIORNAMENTO -
vorrei potuto accettare entrambe le risposte in quanto sono entrambi estremamente informativo e pensieroso scritta. Alla fine, però, la modifica alla risposta accettata contiene la funzione che cercavo:
interpret :: (Functor m, Monad m) => (forall x. f x -> m x) -> Free f a -> m a
interpret evalF = retract . hoistFree evalF
(retract
e hoistFree
sono in pacchetto gratuito di Edward Kemmet in Control.Monad.Free).
Tutti e tre di pipes
, operational
e sacundim's free-operational package sono molto pertinenti e sembrano essere molto utili per me in futuro. Grazie a tutti.
Questo è davvero affascinante! Tempo per me di dare un'occhiata a queste diverse versioni di 'interpret' ... –