Design of interface abstraction

╄→гoц情女王★ 提交于 2019-12-05 05:03:01

First of all, keep in mind that the main purpose of type classes is to permit overloading of functions, i.e. that you can use a single function at different types. You don't really require that, so you are better off with a record type along the lines of

data Player = Player { playCard :: [Card] -> IO (Card, Player), ... }


Second, the problem of some players needing IO and some not can be solved with a custom monad. I have written corresponding example code for a game of TicTacToe, which is part of my operational package.

A much better design would be not to have IO as part of any Player type. Why does the player need to do IO? The player probably needs to get information and send information. Make an interface that reflects that. If/when IO is needed it will be performed by playSkat.

If you do it this what you can have other versions of playSkat that don't do any IO and you can also test your players much more easily since they only interact via the class methods and not through IO.

fuz

That's how I finally designed the abstraction:

All things the engine may want from one of the players are encoded in a big GADT called Message, because I do not always need an answer. The parameter of the GADT is the requested return value:

data Message answer where
  ReceiveHand :: [Card] -> Message ()
  RequestBid  :: Message (Maybe Int)
  HoldsBid    :: Int -> Message Bool
  ...

The different kinds of players are abstracted over a type class with one single function playerMessage that allows the engine to send a message to a player and requests for an answer. The answer is wrapped up in an Either, so the player can return an appropriate error if it is not possible to return an answer (for instance, if the function is not implemented or the network is on strike, etc). The parameter p is a state record for the player to store private data and configuration. The player is abstracted over a monad m to allow some players to use IO while others don't need it:

class Monad m => Player p m | p -> m where
  playerMessage :: Message answer -> p -> m (Either String answer,p)

Edit

I asked another Question, because I was not happy with typing the contexts again and again, so I finally changed the code to reify the typeclass Player. The players have no state by them self, but they can use partial applied functions to simulate this. See the other question for details.

Haven't at all thought this through, but maybe still worth considering. Here I noticed that you have both p in and p out in the type class functions, I guessed that means those "update" p. A state monad somehow.

class (MonadIO m, MonadState p m) => Player p where
  playCard :: [Card] -> m Card
  notifyFoo :: Event -> m ()

Again, this is just a spontaneous thought. I don't guarantee it to be wise (or even compilable).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!