我在使用zoom
提供的Control.Lens
函数时遇到了困难。使用我的自定义monad变换器HearthMonad
,我无法弄清楚如何满足GHC"模糊类型"投诉。
相关代码位于drawCard
。
我该如何解决这个问题?我是否必须创建自己的自定义缩放运算符来处理Monad m
中的Hearth m
?
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
module EngineZoom where
--------------------------------------------------------------------------------
import Control.Applicative
import Control.Lens
import Control.Monad.State
import Data.List
import Data.Maybe
--------------------------------------------------------------------------------
type PlayerHandle = String
data Card = Card String
deriving (Show, Eq, Ord)
data Player = Player {
_playerHandle :: PlayerHandle,
_hand :: [Card]
} deriving (Show, Eq, Ord)
makeLenses ''Player
data GameState = GameState {
_gamePlayers :: [Player]
} deriving (Show, Eq, Ord)
makeLenses ''GameState
newtype Hearth m a = Hearth {
unHearth :: StateT GameState m a
} deriving (Functor, Applicative, Monad, MonadState GameState, MonadIO, MonadTrans)
type HearthMonad = MonadIO
runHearth :: (HearthMonad m) => m ()
runHearth = evalStateT (unHearth runGame) mkGameState
mkGameState :: GameState
mkGameState = GameState { _gamePlayers = map mkPlayer ["Bob", "Joe"] }
mkPlayer :: PlayerHandle -> Player
mkPlayer handle = Player { _playerHandle = handle, _hand = [] }
runGame :: (HearthMonad m) => Hearth m ()
runGame = do
card <- drawCard "Bob"
liftIO $ print card
getPlayer :: PlayerHandle -> Lens' GameState Player
getPlayer handle f st = fmap put' get'
where
players = st^.gamePlayers
put' player = let
g p = case p^.playerHandle == handle of
True -> player
False -> p
in set gamePlayers (map g players) st
get' = f $ fromJust $ find (\p -> p^.playerHandle == handle) players
drawCard :: (HearthMonad m) => PlayerHandle -> Hearth m Card
drawCard handle = do
let card = Card "Yeti"
--getPlayer handle.hand <>= [card]
zoom (getPlayer handle) $ hand <>= [card]
return card
EngineZoom.hs:86:5:
Could not deduce (Control.Lens.Internal.Zoom.Zoomed (Hearth m)
~ Control.Lens.Internal.Zoom.Zoomed m0)
from the context (HearthMonad m)
bound by the type signature for
drawCard :: HearthMonad m => PlayerHandle -> Hearth m Card
at EngineZoom.hs:82:13-60
NB: `Control.Lens.Internal.Zoom.Zoomed' is a type function, and may not be injective
The type variable `m0' is ambiguous
Relevant bindings include
drawCard :: PlayerHandle -> Hearth m Card
(bound at EngineZoom.hs:83:1)
In the expression: zoom (getPlayer handle)
In a stmt of a 'do' block:
zoom (getPlayer handle) $ hand <>= [card]
In the expression:
do { let card = Card "Yeti";
zoom (getPlayer handle) $ hand <>= [card];
return card }
答案 0 :(得分:5)
问题是你的newtype只能拥有一个状态,即GameState
。缩放实际上会将状态更改为镜头的目标,但由于Hearth
不能将Player
作为状态,zoom (getPlayer handle)
不能与Hearth
一起使用。
简单的解决方案是用type Hearth = StateT GameState
替换newtype并缩放工作。如果你想要一个新类型,你需要将状态参数化,这是一个例子:
import Control.Lens.Internal.Zoom
newtype HearthS s m a = Hearth {
unHearth :: StateT s m a
} deriving (Functor, Applicative, Monad, MonadState s, MonadIO, MonadTrans)
type Hearth = HearthS GameState
type instance Zoomed (HearthS s m) = Focusing m
instance Monad z => Zoom (HearthS s z) (HearthS t z) s t where
zoom l (Hearth m) = Hearth (zoom l m)