我最近开始了一点hobby project,在那里我尝试实施技巧卡片游戏Skat(3名玩家)。为了能够让不同类型的玩家(如AI,网络和本地玩家)一起玩,我使用类型类Player
设计了interface:
class Monad m => Player p m | p -> m where
playerMessage :: Message answer -> p -> m (Either Error answer,p)
我使用StateT
来结束这三个玩家:
type PST a b c m x = StateT (Players a b c) m x
但是现在,我必须在每种类型的签名中写一大堆上下文:
dealCards :: (Player a m, Player b m, Player c m, RandomGen g)
=> g -> PST a b c m (SomeOtherState,g)
我怎样才能避免一次又一次地写这个大背景?
答案 0 :(得分:11)
您可以从播放器类中观察到的唯一一个类型为
的函数playerMessage' :: Message answer -> m (Either Error answer, p)
因此,您可以完全消除该类并使用普通数据类型
data Player m = Player { playerMessage'
:: Message answer -> m (Either Error answer, Player m) }
这基本上是my previous answer。
另一种解决方案是使用GADT将上下文移动到数据类型中。
data PST a b c m x where
PST :: (Player a m, Player b m, Player c m)
=> StateT (Players a b c) m x -> PST a b c m x
换句话说,约束成为数据类型的一部分。
在我看来,最好的解决方案是废弃整个事情,并根据我TicTacToe example的operational package重新设计它。这种设计允许您在一个专门的monad中编写每个玩家(人类,AI,重放......),然后将所有内容注入一个共同的解释器。
答案 1 :(得分:6)
<强>更新强>
当我尝试实现dealCards
时,我意识到我的解决方案通过让玩家可以互换来降低类型安全性。通过这种方式,您可以轻松地使用一个玩家而不是另一个玩家。
如果你不介意使用ExistentialQuantification
,我认为它可以(而且应该?)在这里使用。毕竟,dealCards
功能不应关心或了解a
,b
和c
,对吗?
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Monad.State
import System.Random
type Message answer = answer
type Error = String
class Monad m => Player p m | p -> m where
playerMessage :: Message answer -> p -> m (Either Error answer,p)
data SomePlayer m = forall p. Player p m => SomePlayer p
data Players m = Players (SomePlayer m) (SomePlayer m) (SomePlayer m)
type PST m x = StateT (Players m) m x
dealCards :: (RandomGen g, Monad m) => g -> PST m x
dealCards = undefined
我认为应该可以以类似的方式消除Monad
约束。
实际上,在这种情况下,我觉得类型类被过度使用了。也许这是一个Haskell新手在我这里说话,但我会这样写:
data Player m = Player { playerMessage :: Message answer -> m (Either Error answer, Player m) }
答案 2 :(得分:2)
显然,更好的答案是让设计首先不需要所有这些类型参数。但是,如果你真的无法摆脱它们,并回答提出的问题,这是我玩过的一个技巧:
class (Storable.Storable (X, y), Y y) => Signal y
现在写'(信号y)=&gt; ...'将暗示所有其他类型类,并阻止像Storable这样的实现细节进入每个API。但是,您必须为Signal声明实例。它很简单,因为它没有方法,但是当你有很少的实例但很多功能时,它可能最适合。