我实现了一些搜索游戏树的功能,以供我自己娱乐。例如,一个这样的函数将执行Minimax算法。这些函数使用两种对象:
我的目标是拥有适用于不同游戏的搜索功能,因此我将游戏特定的知识传递为辅助功能,例如:
这样,我的搜索功能确实不是游戏特定的,但解决方案仍然感觉不对。它还使我的参数列表相当长:
-- 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
。
我对整个想法完全走错了道路,还是有办法对ChessState
和ChessMove
之间的关系进行编码?我在MultiParamTypeClasses
取得了一些进展,但它仍然没有像我希望的那样。
答案 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]