haskell - 解决 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
正如预期的那样,我已经遇到了模棱两可的问题。见下文,其中 action 类型变量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
。您有三个不错的选择:
- 使用功能依赖,
- 使用关联的类型族,或
- 不要为此使用课程。
让我们详细看看每个选项。
功能依赖
e
GHC 遇到的问题是,当您调用类似的函数时,它知道s
您要使用哪个baseState
(它可以从函数的输入和输出中确定那些baseState
),但它不知道a
要使用哪个。MultiAgentEnvironment
据 GHC 所知,对于给定的e
and ,可能会有多个实例化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
是唯一可能模棱两可的类型参数,并且有了这个fundep,它就不再模棱两可了。也就是说,如果您知道环境类型明确地确定了状态和动作(即,给定的环境只能具有一种可能的状态和动作类型),那么您可以使用两个基金来进一步降低歧义,如在:
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
作为约束,而是将其作为常规的旧参数。事实上,如果你打开RecordWildCards
pragma,那么任何以前开始的函数
foo :: MultiAgentEnvironment e s a => x -> y
foo x = ...
现在可以写成
foo :: MultiAgentEnvironment e s a -> x -> y
foo mae@MultiAgentEnvironment{..} x = ...
并且主体应该几乎相同(好吧,除非主体调用需要 的子函数MultiAgentEnvironment
,在这种情况下您需要手动传递mae
)。
推荐阅读
- python - 如何在 Python 中编码元组?
- django - 在 drf-yasg OpenAPI 和 Swagger 视图中显示 SlugRelatedField 的可能值(选择)
- cryptography - pkcs v1.5 填充和 pkcs v2.1 填充有什么区别?
- asp.net-core - Asp.net Core 将端口替换为文本
- ios - Swift:每次都让大标题开始变小或变大
- javascript - 从经典 HTML 调用角度组件方法
- sql - 在 Oracle 中构建多列作为枢轴
- realm - Keycloak - 与来自不同领域的用户一起管理领域
- javascript - 即使数据发生变化,DOM 也不会在 vue.js 中更新
- bash - 压缩bash中数字范围的范围