考虑以下示例程序:
next :: Int -> Int
next i
| 0 == m2 = d2
| otherwise = 3 * i + 1
where
(d2, m2) = i `divMod` 2
loopIteration :: MaybeT (StateT Int IO) ()
loopIteration = do
i <- get
guard $ i > 1
liftIO $ print i
modify next
main :: IO ()
main = do
(`runStateT` 31) . runMaybeT . forever $ loopIteration
return ()
它只能使用get
代替lift get
,因为instance MonadState s m => MonadState s (MaybeT m)
是在MaybeT模块中定义的。
许多此类实例都是以组合爆炸的方式定义的。
如果我们有以下类型类,那会很好(虽然不可能?为什么?):
{-# LANGUAGE MultiParamTypeClasses #-}
class SuperMonad m s where
lifts :: m a -> s a
让我们尝试将其定义为:
{-# LANGUAGE FlexibleInstances, ... #-}
instance SuperMonad a a where
lifts = id
instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b) where
lifts = lift . lifts
使用lifts $ print i
代替liftIO $ print i
,这很不错。
但使用lifts (get :: StateT Int IO Int)
代替(get :: MaybeT (StateT Int IO) Int)
不起作用。
GHC(6.10.3)给出以下错误:
Overlapping instances for SuperMonad
(StateT Int IO) (StateT Int IO)
arising from a use of `lifts'
Matching instances:
instance SuperMonad a a
instance (SuperMonad a b, MonadTrans t, Monad b) =>
SuperMonad a (t b)
In a stmt of a 'do' expression:
i <- lifts (get :: StateT Int IO Int)
我可以看到为什么“instance SuperMonad a a
”适用。但为什么GHC认为另一个呢?
答案 0 :(得分:35)
跟进ephemient的优秀答案:Haskell类型类使用开放世界的假设:稍后会出现一些白痴,并添加一个不重复的实例声明和但与实例重叠。 将其视为对手游戏:如果对手可能使您的程序模糊不清,编译器就会惨淡。
如果您正在使用GHC,您当然可以对编译器说“你的偏执狂地狱;请允许我使用我的模糊实例声明”:
{-# LANGUAGE OverlappingInstances #-}
如果程序的后续演变导致你没想到的重载分辨率,那么编译器会得到1000个I-tell-you-so点: - )
答案 1 :(得分:8)
仅仅因为您没有在当前模块中定义实例并不意味着无法在其他地方定义实例。
{-# LANGUAGE ... #-}
module SomeOtherModule where
-- no practical implementation, but the instance could still be declared
instance SuperMonad (StateT s m) m
假设您的模块和SomeOtherModule
在一个程序中链接在一起。
现在,回答这个问题:您的代码是否使用
instance SuperMonad a a
-- with a = StateT Int IO
或
instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b)
-- with a = StateT Int IO
-- t = StateT Int
-- b = IO