在Haskell中使用类型类代表游戏状态

时间:2017-01-04 23:16:56

标签: haskell typeclass

我实现了一些搜索游戏树的功能,以供我自己娱乐。例如,一个这样的函数将执行Minimax算法。这些函数使用两种对象:

  1. 游戏状态,例如国际象棋棋盘上的棋子位置,下一位移动球员等等。
  2. 移动,例如"典当e2到e5"
  3. 我的目标是拥有适用于不同游戏的搜索功能,因此我将游戏特定的知识传递为辅助功能,例如:

    • 从给定的游戏状态生成所有可能的移动
    • 给定状态和移动,将移动应用于状态以获得结果状态
    • 了解这是否是获胜位置

    这样,我的搜索功能确实不是游戏特定的,但解决方案仍然感觉不对。它还使我的参数列表相当长:

    -- Given a game state find the best next move
    bestMove :: s -> (s -> [m]) -> (s -> m -> s) -> m    -- more parameters in my real code
    bestMove currentState possibleMoves applyMove = ...
    

    问题

    我可以用类型类替换这些辅助函数吗?我正在寻找类似的东西:

    class Move m where
      apply :: State s => s -> m -> s
    
    class State s where
      possibleMoves :: Move m => s -> [m]
    
    bestMove :: State s => Move m => s -> m
    bestMove currentState = ... -- e.g. = head $ possibleMoves state
    

    问题是State的任何实例只能与Move的一个特定实例一起使用,反之亦然:

    {-# LANGUAGE InstanceSigs #-} 
    
    data ChessState = ...
    data ChessMove  = ...
    
    instance Move ChessMove where
      apply :: ChessState -> ChessMove -> ChessState
      apply s m = ...
    

    apply的类型签名当然是错误的。它应该是

    apply :: S s => s -> ChessMove -> s
    

    但我确实需要特定于ChessState的属性才能创建ChessMove

    我对整个想法完全走错了道路,还是有办法对ChessStateChessMove之间的关系进行编码?我在MultiParamTypeClasses取得了一些进展,但它仍然没有像我希望的那样。

2 个答案:

答案 0 :(得分:4)

你可以考虑只传递整个游戏树:

data GameTree m s = GameTree s [(m, GameTree m s)]

bestMove :: GameTree m s -> (s -> Int) -> Maybe m
bestMove gameTree evaluateState = ...

依靠懒惰来扩展你实际看到的游戏树的部分。

答案 1 :(得分:3)

首先,如果你可以做一些没有类型的东西,那么远离它们通常是一个好主意。您可以随时为特定应用程序创建一个更简单的包装器,尤其是在适当地翻转参数时:

bestMove :: (s -> [m]) -> (m -> s -> s) -> s -> m

bestChessMove :: ChessState -> ChessMove
bestChessMove = bestMove possibleChessMoves applyChessMove

那就是说,我没有找到你想象的类是不可见的(不像许多OO类,有人想挤进Haskell ......)。而且你在正确的轨道上它应该是一个多参数类:

{-# LANGUAGE MultiParamTypeClasses #-} 

class MoveState m s where
  apply :: m -> s -> s
  possibleMoves :: s -> [m]

instance MoveState ChessMove ChessState where
  apply = ...
  possibleMoves = ...

然后

bestMove :: MoveState m s => (s -> Cost) -> s -> m

实际上,这个类更通用而不是有意义:对于任何给定的状态类型,您可能只有一种移动类型。表达此信息的一种方法是functional dependency | s -> m;我通常更喜欢的是将Move类型简单化为州级的“属性”:

{-# LANGUAGE TypeFamilies #-} 

class GameState s where
  type Move s :: *
  apply :: Move s -> s -> s
  possibleMoves :: s -> [Move s]