10

考虑以下示例程序:

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 认为另一个也有呢?

4

3 回答 3

37

跟进 ehemient 的出色回答:Haskell 类型类使用开放世界假设:稍后会出现一些白痴并添加一个不重复与您的实例重叠的实例声明。 把它想象成一个对手的游戏:如果对手可以让你的程序模棱两可,编译器就会咩咩。

如果你使用 GHC,你当然可以对编译器说“你的妄想症见鬼去吧;请允许我模棱两可的实例声明”:

{-# LANGUAGE OverlappingInstances #-}

如果您的程序后来的演变导致您没有预料到的重载解决方案,编译器会获得 1,000 我告诉你的点 :-)

弃用说明

自 GHC 7.10 起,此 pragma 已被弃用,应改为使用 per-instance pragma。更多细节可以在GHC 文档中找到。

于 2009-07-02T04:38:17.470 回答
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

?

于 2009-06-30T16:55:19.313 回答
1

当您有重叠的实例时,尝试将它们的行为附加到newtypes:

type    SuperEgo :: (k -> Type) -> (k -> Type)
newtype SuperEgo m a = SuperEgo (m a)

type    Elevator :: (k -> k1 -> Type) -> (k -> k1 -> Type)
newtype Elevator trans m a = Elevator (trans m a)
instance SuperMonad m (SuperEgo m) where
  lifts :: m ~> SuperEgo m
  lifts = SuperEgo

instance (SuperMonad m super, Monad super, MonadTrans trans) => SuperMonad m (Elevator trans super) where
  lifts :: m ~> Elevator trans super
  lifts = Elevator . lift . lifts

Monads 现在可以派生 via SuperEgo M以获取身份实例

{-# Language DerivingVia #-}

data Ok a = Ok a
  deriving (SuperMonad Ok)
  via SuperEgo Ok

定义一个 monad 转换器比较麻烦,所以我将展示如何为现有的 Monad 转换器定义一个提升实例,例如StateT s. 这使用了更冗长的独立派生,您需要自己填写类上下文:

deriving
  via Elevator (StateT s) super
  instance (Monad super, SuperMonad m super) => SuperMonad m (StateT s super)
于 2021-10-19T19:36:25.837 回答