Search code examples
haskelloverlapping-instances

Num instance for Monad; overlapping instances only in the presence of seemingly unrelated code?


I have a bit of code that would be more cleanly written if I could treat Monads as Nums (where applicable, of course). Easily enough done:

{-# 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

But when I add the following function to the file …

digitToNum :: (Num a) => Char -> a
digitToNum = fromIntegral . digitToInt

… I receive the following error:

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

This doesn't make sense to me, because (1) digitToNum is never called and (2) Ratio isn't a Monad. So I'm unsure how or why that's a problem. Any tips around this would be appreciated.

This is GHC 7.4.2, using the Haskell Platform 2012.4.0.0.


Solution

  • The key issue at play here is a principle in haskell that writing additional instances should not change the operation of existing code. This increases the robustness of haskell code, as code won't break or have different behaviour if a module it depends on adds a new instance.

    For this reason, when choosing possible instances to use for a type, Haskell does not consider the context of the instances. For example, when matching checking if a type will match the instance instance (Monad m, Num a) => Num (m a) for the class Num, it will only check if it can match m a. This is because any type may later be made an instance of a class, and if instance choice used context information adding that instance would change the operation of existing programs.

    For example, there is nothing stopping the following code being added either in your module, or in a module you depend on:

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

    Sure, such an instance is useless, but haskell has no way of judging that. It also is possible that there is a useful definition of Monad for Ratio (I haven't looked into that).

    In summary, what you are trying to do isn't a good idea. You can stop these limitations using OverlappingInstances and IncoherentInstances, however for the reasons described above these flags are not recommended for mainstream use by most haskell programmers.