在我的小项目中,玩家可以创建/加入/观看游戏会话或列出正在进行的会话。一个非常简单的 Yesod 应用程序公开了一些端点以匹配用例。
Step 1)我想抽象出“能够举办多个正在进行的比赛”的概念:
class Host m k s where
create :: s -> m k
read :: k -> m s
write :: k -> s -> m ()
update :: (s -> s) -> k -> m ()
delete :: k -> m ()
Host m k s
的实例意味着m
可以承载s
的游戏(状态),由k
识别(键控)。
Step 2) 一个非常实用的主机是共享
Map
:
newtype InMemory k s a = InMemory {run :: TVar (M.Map k s) -> STM a}
持有
TVar
的Map k s
在InMemory
中被捕获。 InMemory
键控 Int
可以主持:
instance Host (InMemory Int a) Int a where
create s = InMemory $ \tvar -> do
id <- succ . M.size <$> readTVar tvar
modifyTVar tvar (M.insert id s)
return id
read id = InMemory $ \tvar -> do
s <- M.lookup id <$> readTVar tvar
maybe err return s
where
err = throwSTM $ NotFound id
write id s = modify $ M.insert id s -- modify is just a small helper, returns InMemory
update f id = modify $ M.update (Just . f) id
delete id = modify $ M.delete id
步骤 3) 任何可以生成正确 tvar 的
m
也是主机。例如,Yesod 应用程序可以使HandlerFor
成为主机:
newtype App = App {rooms :: TVar (M.Map Int Game)} -- (1)
instance MonadReader (HandlerFor App) (M.Map Int Game) where ... -- (2) HandlerFor App can produce the correct tvar
-- (1) && (2) ==> the HandlerFor App is a Host
所以让我们有这个概念:
-- smt is a just a small helper, needs MonadIO
-- this instance declaration is *wrong* and not what I want to say
instance (C.MonadIO m, R.MonadReader (TVar (M.Map k s)) m) => Host m GameId s where
create s = GameId <$> stm (create s)
write (GameId id) s = stm (write id s)
update f (GameId id) = stm (update f id)
delete (GameId id) = stm (delete id)
read (GameId id) = stm (read id)
想法是任何可以产生正确数据的
m
都可以托管。
不麻烦来了。这个:
instance R.MonadReader (TVar (M.Map k s)) m => Host m GameId s where
不是说“如果
m
满足上下文那么它可以托管”,而是说“所有m
是主机必须满足上下文”这不是我想说的。所以我坚持在 Haskell 中表达以下内容:
IF
满足上下文 THEN 它可以托管,如果不满足则此行具有 abs。没有任何效果。不要在m
上强制上下文,如果m
可以托管但不满足上下文,这不是错误。m
关于这个主题本身就有一个问题(如何选择加入一个实例,但在其他方面不受干扰),显然这在 Haskell 中是不可能表达的。所以问题是: