Haskell - 如何避免一遍又一遍地键入相同的上下文?

时间:2011-07-15 11:19:03

标签: haskell

我最近开始了一点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)

我怎样才能避免一次又一次地写这个大背景?

3 个答案:

答案 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 exampleoperational package重新设计它。这种设计允许您在一个专门的monad中编写每个玩家(人类,AI,重放......),然后将所有内容注入一个共同的解释器。

答案 1 :(得分:6)

<强>更新 当我尝试实现dealCards时,我意识到我的解决方案通过让玩家可以互换来降低类型安全性。通过这种方式,您可以轻松地使用一个玩家而不是另一个玩家。


如果你不介意使用ExistentialQuantification,我认为它可以(而且应该?)在这里使用。毕竟,dealCards功能不应关心或了解abc,对吗?

{-# 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声明实例。它很简单,因为它没有方法,但是当你有很少的实例但很多功能时,它可能最适合。

相关问题