나는 어떤 펑터를 통해 무료 모나드에 특정 의미를 적용하는 패턴을 추상화하려하고있다. 이 동기를 부여하기 위해 사용하는 실행중인 예제는 게임의 엔티티에 업데이트를 적용하는 것입니다.무료 모나드에 의미 부여하기
{-# 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
같은 모나드 인스턴스의 다른 구현, 또는 의미 론적 해석의 가능성을 제공해야합니다, 무료 모나드 있습니다. 나는이에 가지고 올 수있는 가장 좋은 패턴은 다음과 같은 기능에 의해 주어진다 :
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.
이 모두 잘 작동하지만 그것은 나에게이 모 될 수 있다는 약간 불안한 느낌을 준다 일반적으로, 또는 더 잘 조직 될 수 있습니다. 계속을 제공하는 기능을 제공하는 것이 처음에는 분명하지 않았으며 이것이 최선의 접근 방식인지 확신 할 수 없습니다. 나는 foldFree
과 induce
과 같은 Control.Monad.Free 모듈의 기능면에서 interpret
을 재정의하려는 몇 가지 노력을했다. 하지만 그들은 모두 잘 작동하지 않는 것 같습니다.
나는 이것과 함께 올바른 행에 있습니까, 아니면 오판을하고 있습니까? 내가 찾은 무료 모나드에 대한 대부분의 기사는 실제로 이와 같은 것을 사용하기위한 패턴보다는 효율성을 높이거나 구현하는 다양한 방법에 중점을 둡니다.
Semantic
클래스에서 이것을 캡슐화하는 것이 바람직하기 때문에 newtype에 functor를 래핑하고이 클래스의 인스턴스로 만들어서 무료 모나드와 다른 모나드 인스턴스를 만들 수 있습니다. 그러나 나는 이것을 어떻게하는지 잘 이해할 수 없었다.
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의 세 가지 모두 매우 관련성이 높으며 나중에 나에게 유용 할 것으로 보입니다. 다들 감사 해요.
이것은 정말 매력적입니다! 이 'interpret interpret'의 다른 버전을 매우 열심히 봐야 할 시간입니다 ... –