如果我可以将Monad
视为Num
s(当然适用),我会有一些更清晰的代码。很容易完成:
{-# 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
但是当我将以下函数添加到文件中时......
digitToNum :: (Num a) => Char -> a
digitToNum = fromIntegral . digitToInt
...我收到以下错误:
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
这对我没有意义,因为(1)digitToNum
从未被调用过,(2)Ratio
不是Monad
。所以我不确定这是一个什么问题或为什么这是一个问题。任何提示都将受到赞赏。
这是GHC 7.4.2,使用Haskell Platform 2012.4.0.0。
答案 0 :(得分:8)
这里的关键问题是haskell的一个原则,即编写其他实例不应该改变现有代码的操作。这增加了haskell代码的健壮性,因为如果代码依赖的代码添加新实例,则代码不会中断或具有不同的行为。
因此,在选择可用于类型的实例时,Haskell不会考虑实例的上下文。例如,在匹配检查类型是否与类instance (Monad m, Num a) => Num (m a)
的实例Num
匹配时,它只会检查它是否可以匹配m a
。这是因为以后任何类型都可以成为类的实例,如果实例选择使用了上下文信息,那么添加该实例会改变现有程序的操作。
例如,没有什么可以阻止在您的模块或您依赖的模块中添加以下代码:
instance Monad Ratio where
return = undefined
(>>=) = undefined
当然,这样的例子没用,但是haskell没办法判断。对Monad
Ratio
有一个有用的定义(我还没有调查过)。
总之,你要做的事情并不是一个好主意。您可以使用OverlappingInstances
和IncoherentInstances
来阻止这些限制,但由于上述原因,大多数haskell程序员不建议将这些标记用于主流使用。
答案 1 :(得分:2)
正如@nanothief解释的那样,Haskell的类型类是用"open world assumption"设计的。解决此问题的标准方法是使用newtype
包装器,这使得实例头不那么通用。 e.g。
newtype WrappedMonad m a = WrapMonad { unwrapMonad :: m a }
instance Monad m => Monad (WrappedMonad m) where
return = WrapMonad . return
(WrapMonad a) >>= f = WrapMonad (a >>= unwrapMonad . f)
instance (Monad m, Num a) => Num (WrappedMonad m a)
(+) = liftM2 (+)
fromInteger = return . fromInteger
-- etc.
现在你应该可以通过首先包装所有输入来使用它,并且只能在最后解包:
unwrapMonad . sum . map WrapMonad $ [[1, 2], [10,20], [100,200]]
-- [111,211,121,221,112,212,122,222]
(显然,对于更长的算术链,会有更大的好处。)
可能值得在Eq
上推导出Show
和WrappedMonad
等,以使其成为m a
的任何功能的精确替代。