解决MultiParameterTypeClass中的歧义
根据对此问题的反馈,我使用 MultiParamTypeClass 来表示强化学习环境Environment,使用 3 个类型变量:e对于环境实例本身(例如,下面的 Nim 之类的游戏),s对于特定对象使用的状态数据类型游戏,以及a特定游戏使用的动作数据类型。
{-# LANGUAGE MultiParamTypeClasses #-}
class MultiAgentEnvironment e s a where
baseState :: e -> s
nextState :: e -> s -> a -> s
reward :: (Num r) => e -> s -> a -> [r]
data Game = Game { players :: Int
, initial_piles :: [Int]
} deriving (Show)
data State = State { player :: Int
, piles :: [Int]} deriving (Show)
data Action = Action { removed :: [Int]} deriving (Show)
instance MultiAgentEnvironment Game State Action where
baseState game = State{player=0, piles=initial_piles game}
nextState game state action = State{player=player state + 1 `mod` players game,
piles=zipWith (-) (piles state) (removed action)}
reward game state action = [0, 0]
newGame :: Int -> [Int] -> Game
newGame players piles = Game{players=players, initial_piles=piles}
main = do
print "Hello, world!"
let game = newGame 2 [3,4,5]
print game
正如预期的那样,我已经遇到了歧义问题。见下文,其中动作类型变量a在 typeclass 中被认为是不明确的Environment。
(base) randm@soundgarden:~/Projects/games/src/Main$ ghc -o basic basic.hs
[1 of 1] Compiling Main ( basic.hs, basic.o )
basic.hs:4:5: error:
• Could not deduce (MultiAgentEnvironment e s a0)
from the context: MultiAgentEnvironment e s a
bound by the type signature for:
baseState :: forall e s a. MultiAgentEnvironment e s a => e -> s
at basic.hs:4:5-23
The type variable ‘a0’ is ambiguous
• In the ambiguity check for ‘baseState’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
baseState :: forall e s a. MultiAgentEnvironment e s a => e -> s
In the class declaration for ‘MultiAgentEnvironment’
|
4 | baseState :: e -> s
| ^^^^^^^^^^^^^^^^^^^
我如何解决这种歧义?我是否错误地使用类型类来实现接口(即baseState, nextState, reward)?
回答
虽然您可以打开AllowAmbiguousTypes,但这只会将您的问题推到更远的地方。也就是说,最终,您将尝试调用baseState,而 GHC 将需要知道是什么a。您有三个不错的选择:
- 使用函数依赖,
- 使用关联的类型系列,或
- 不要为此使用类。
让我们详细看看每个选项。
功能依赖
该GHC遇到的问题是在于,它知道哪些e和s你想,当你调用像一个函数使用baseState(它可以决定那些从输入和输出baseState功能),但它不知道哪个a使用。GHC 都知道,MultiAgentEnvironment对于给定的e和,可能有多个实例化s。通过函数依赖,你可以告诉 GHC 一个给定的e并且s完全定义a应该是什么。通俗地说,通过对 的函数依赖a,你是说对于任何给定的环境和状态,只有一种可能的动作类型是有意义的。如果这是真的,那么您可以像这样添加它们:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
class MultiAgentEnvironment e s a | e s -> a where
baseState :: e -> s
nextState :: e -> s -> a -> s
reward :: (Num r) => e -> s -> a -> [r]
由于您的每个函数都具有e和s在其中,a是唯一可能不明确的类型参数,并且有了这个基金,它就不再是模棱两可了。也就是说,如果你知道的环境类型决定双方的状态和动作毫不含糊地(即,一个给定的环境永远只能有一种可能的状态和动作类型),那么你可以使用2个fundeps更以降低不确定性,如在:
class MultiAgentEnvironment e s a | e -> s a where
关联类型族
函数依赖名声不好,更现代的替代方法通常是使用关联类型系列。实际上,就您的目的而言,它们的工作方式非常相似。再一次,您必须接受环境类型决定操作类型(也许还有状态类型)。如果是这样,你可以这样写:
{-# LANGUAGE TypeFamilies #-}
class MultiAgentEnvironment e where
type EState e
type EAction e
baseState :: e -> EState e
nextState :: e -> EState e -> a -> EState e
reward :: (Num r) => e -> EState e -> EAction e -> [r]
然后,当您创建实例时,它将如下所示:
instance MultiAgentEnvironment Game where
type EState Game = State
type EAction Game = Action
baseState game = State{player=0, piles=initial_piles game}
nextState game state action = ...
使用数据类型而不是类
最后一个选择是完全放弃使用类型类。相反,您可以通过将数据表示为数据类型来使数据显式。例如,您可以定义:
{-# LANGUAGE RankNTypes #-}
data MultiAgentEnvironment e s a = MultiAgentEnvironment
{ baseState :: e -> s
, nextState :: e -> s -> a -> s
, reward :: forall r. (Num r) => e -> s -> a -> [r]
}
您只需创建数据类型的值,而不是创建类型类的实例:
gameStateActionMAE :: MultiAgentEnvironment Game State Action
gameStateActionMAE = MultiAgentEnvironment
{ baseState = game -> State{player=0, piles=initial_piles game}
, nextState = game state action -> State{player=player state + 1 `mod` players game,
piles=zipWith (-) (piles state) (removed action)}
, reward = game state action -> [0, 0]
}
这种方法的一个很好的优点是您可以MultiAgentEnvironment使用相同的类型创建多个不同的s,但它们具有不同的行为。使用它们也非常简单:MultiAgentEnvironment e s a您现在将其作为常规的旧参数而不是作为约束。事实上,如果你打开RecordWildCardspragma,那么任何用于开始的函数
foo :: MultiAgentEnvironment e s a => x -> y
foo x = ...
现在可以写成
foo :: MultiAgentEnvironment e s a -> x -> y
foo mae@MultiAgentEnvironment{..} x = ...
并且主体应该几乎相同(好吧,除非主体调用需要 的子函数MultiAgentEnvironment,在这种情况下,您需要手动传递mae)。