Haskell:重叠实例

yai*_*chu 9 haskell typeclass

请考虑以下示例程序:

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 ()
Run Code Online (Sandbox Code Playgroud)

它只能使用get而不是lift get因为instance MonadState s m => MonadState s (MaybeT m)在MaybeT模块中定义.

许多这样的实例是以组合爆炸方式定义的.

如果我们有以下类型类,那会很好(虽然不可能?为什么?)

{-# LANGUAGE MultiParamTypeClasses #-}

class SuperMonad m s where
  lifts :: m a -> s a
Run Code Online (Sandbox Code Playgroud)

让我们尝试将其定义为:

{-# 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
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

我明白为什么" instance SuperMonad a a"适用.但为什么GHC认为另一个呢?

Nor*_*sey 35

跟进ephemient的优秀答案:Haskell类型类使用开放世界的假设:一些白痴可以在以后出现并添加一个不重复您的实例重叠的实例声明. 把它想象成一个对手游戏:如果对手可以使你的程序模棱两可,那么编译器就会黯然失色.

如果你正在使用GHC,你当然可以对编译器说"你的偏执狂地狱;请允许我使用我的模糊实例声明":

{-# LANGUAGE OverlappingInstances #-}
Run Code Online (Sandbox Code Playgroud)

如果程序的后续演变导致你没想到的重载决议,那么编译器会得到1000个I-tell-you-so点:-)

  • 重叠实例远远落在我的扩展列表上(甚至超过UndecidableInstances,这只会使编译器的工作更加困难) - 不仅不可移植,而且还打破了Haskell通常提供的安全保证.我建议OP把它搞砸,然后手动处理"升降"或"升力",而不是添加这个黑客......但这是我的看法. (4认同)

eph*_*ent 8

仅仅因为您没有在当前模块中定义实例并不意味着无法在其他地方定义实例.

{-# LANGUAGE ... #-}
module SomeOtherModule where

-- no practical implementation, but the instance could still be declared
instance SuperMonad (StateT s m) m
Run Code Online (Sandbox Code Playgroud)

假设您的模块SomeOtherModule在一个程序中链接在一起.

现在,回答这个:您的代码是否使用

instance SuperMonad a a
  -- with a = StateT Int IO
Run Code Online (Sandbox Code Playgroud)

要么

instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b)
  -- with a = StateT Int IO
  --      t = StateT Int
  --      b = IO
Run Code Online (Sandbox Code Playgroud)

  • 您的结构使编译器无法明确地确定代码中使用的实例.如果可以明确地解决,那么其他地方的重叠也无关紧要. (2认同)