0

Monad如果我可以将s 视为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

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

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 平台 2012.4.0.0。

4

2 回答 2

8

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

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

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

instance Monad Ratio where
   return = undefined
   (>>=) = undefined

当然,这样的实例是无用的,但 haskell 无法判断。也可能有一个有用的Monadfor定义Ratio(我没有研究过)。

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

于 2012-12-16T06:02:13.220 回答
2

正如@nanothief 解释的那样,Haskell 的类型类是根据“开放世界假设”设计的。解决这个问题的标准方法是使用newtype包装器,它使实例头不那么通用。例如

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]

(显然,更长的算术链会有更大的好处。)

可能值得推导EqShowWrappedMonad以使其成为m a.

于 2012-12-16T10:16:37.033 回答