Monad的Num实例; 只有在看似无关的代码存在的情况下重叠实例?

Jam*_*ham 0 haskell overlapping-instances

如果我可以将Monads视为Nums(当然适用的话),我会有一些代码更清晰.足够轻松完成:

{-# LANGUAGE FlexibleInstances #-}

import Control.Monad (liftM, liftM2)
import Data.Char (digitToInt)

instance (Monad m, Num a) => Num (m a) where
  (+) = liftM2 (+)
  (-) = liftM2 (-)
  (*) = liftM2 (*)
  abs = liftM abs
  signum = liftM signum
  fromInteger = return . fromInteger

square :: (Monad m, Num a) => m a -> m a
square x = x * x

-- Prints "Just 9", as expected
main = putStrLn $ show $ square $ Just 3
Run Code Online (Sandbox Code Playgroud)

但是当我将以下函数添加到文件中时...

digitToNum :: (Num a) => Char -> a
digitToNum = fromIntegral . digitToInt
Run Code Online (Sandbox Code Playgroud)

...我收到以下错误:

monadNumTest.hs:15:14:
    Overlapping instances for Num (m a)
      arising from a use of `*'
    Matching instances:
      instance (Monad m, Num a) => Num (m a)
        -- Defined at monadNumTest.hs:6:10
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
    (The choice depends on the instantiation of `m, a'
     To pick the first instance above, use -XIncoherentInstances
     when compiling the other instance declarations)
    In the expression: x * x
    In an equation for `square': square x = x * x
Run Code Online (Sandbox Code Playgroud)

这对我来说没有意义,因为(1)digitToNum从未被调用过,(2)Ratio不是a Monad.所以我不确定这是一个什么问题或为什么这是一个问题.任何有关这方面的提示将不胜感激.

这是GHC 7.4.2,使用Haskell Platform 2012.4.0.0.

Dav*_*ani 8

这里的关键问题是haskell的一个原则,即编写其他实例不应该改变现有代码的操作.这增加了haskell代码的健壮性,因为如果依赖的模块添加新实例,代码将不会中断或具有不同的行为.

因此,在选择可用于类型的实例时,Haskell不会考虑实例的上下文.例如,在匹配检查类型是否与instance (Monad m, Num a) => Num (m a)类的实例匹配时Num,它只会检查它是否匹配m a.这是因为任何类型以后都可以成为类的实例,并且如果实例选择使用上下文信息,则添加该实例将改变现有程序的操作.

例如,没有什么能阻止在您的模块或您依赖的模块中添加以下代码:

instance Monad Ratio where
   return = undefined
   (>>=) = undefined
Run Code Online (Sandbox Code Playgroud)

当然,这样的例子是无用的,但是haskell无法判断这一点.它也可能有一个有用的定义MonadRatio(我没有看过成).

总之,你要做的不是一个好主意.您可以使用OverlappingInstances和停止这些限制IncoherentInstances,但由于上述原因,大多数haskell程序员不建议将这些标志用于主流.