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

fuz*_*fuz 22 haskell

我最近开始了一个小爱好项目,在那里我尝试实施技巧卡片游戏Skat(3名玩家).为了能够让不同类型的玩家(如AI,网络和本地)一起玩,我使用类型类设计了一个界面Player:

class Monad m => Player p m | p -> m where
  playerMessage :: Message answer -> p -> m (Either Error answer,p)
Run Code Online (Sandbox Code Playgroud)

我用a StateT来结束这三个玩家:

type PST a b c m x = StateT (Players a b c) m x
Run Code Online (Sandbox Code Playgroud)

但是现在,我必须在每个类型签名中写一大堆上下文:

dealCards :: (Player a m, Player b m, Player c m, RandomGen g)
  => g -> PST a b c m (SomeOtherState,g)
Run Code Online (Sandbox Code Playgroud)

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

Hei*_*mus 11

  • 您可以从播放器类中观察到的唯一功能是类型的功能

    playerMessage' :: Message answer -> m (Either Error answer, p)
    
    Run Code Online (Sandbox Code Playgroud)

    因此,您可以完全消除该类并使用普通数据类型

    data Player m = Player { playerMessage'
                  :: Message answer -> m (Either Error answer, Player m) }
    
    Run Code Online (Sandbox Code Playgroud)

    这基本上是我以前的答案.

  • 另一种解决方案是使用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
    
    Run Code Online (Sandbox Code Playgroud)

    换句话说,约束成为数据类型的一部分.

  • 在我看来,最好的解决方案是废弃整个产品,并根据我的操作包中的TicTacToe示例重新设计它.这种设计允许您在一个专门的monad中编写每个玩家(人类,AI,重放......),然后将所有内容注入一个通用的解释器.

  • @yairchu让我们一起坐吧!但是,我会离开我的,因为它可能会产生一些额外的代表.:d (2认同)
  • @FUZxxl:呃,确实忘记了`m`.固定.@yairchu:但是,但是......我自己的声誉点很少......:'( (2认同)

Rot*_*sor 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
Run Code Online (Sandbox Code Playgroud)

我认为应该可以以Monad类似的方式消除约束.

实际上,在这样的情况下,我觉得类型类被过度使用.也许这是一个Haskell新手在我说话,但我会写这个:

data Player m = Player { playerMessage :: Message answer -> m (Either Error answer, Player m) }
Run Code Online (Sandbox Code Playgroud)

  • 请注意,从概念上讲,部分应用从函数类型中消除的任何参数类型都是存在的; 它存在,即使你不能做任何事情.直接使用存在类型主要是为了实现这个概念,这很少是必要的. (2认同)