Pivoting off some recent questions, I figured I\'d turn the spotlight on the old bogeyman, OverlappingInstances
.
A few years ago I might\'ve been asking thi
One principle that the haskell language attempts to abide by is adding extra methods/classes or instances in a given module should not cause any other modules that depend on the given module to either fail to compile or have different behaviour (as long as the dependent modules use explicit import lists).
Unfortunately, this is broken with OverlappingInstances. For example:
Module A:
{-# LANGUAGE FlexibleInstances, OverlappingInstances, MultiParamTypeClasses, FunctionalDependencies #-}
module A (Test(..)) where
class Test a b c | a b -> c where
test :: a -> b -> c
instance Test String a String where
test str _ = str
Module B:
module B where
import A (Test(test))
someFunc :: String -> Int -> String
someFunc = test
shouldEqualHello = someFunc "hello" 4
shouldEqualHello
does equal "hello" in module B.
Now add the following instance declaration in A:
instance Test String Int String where
test s i = concat $ replicate i s
It would be preferable if this didn't affect module B. It worked before this addition, and should work afterwards. Unfortunately, this isn't the case.
Module B still compiles, but now shouldEqualHello
now equals "hellohellohellohello"
. The behaviour has changed even though no method it was originally using had changed.
What is worse is there is no way to go back to the old behaviour, as you cannot choose to not import an instance from a module. As you can imagine, this is very bad for backwards compatibility, as you cannot safely add new instances to a class that uses overlappinginstances, as it could change the behaviour of code that uses the module (especially true if you are writing library code). This is worse than a compile error, as it could be very difficult to track down the change.
The only safe time to use overlapping instances in my opinion is when you are writing a class that you know will never need additional instances. This may occur if you are doing some tricky type based code.