Perhaps a thought experiment will de-mystify this extension a bit.
Let's pretend that we've dropped the restriction that functions defined with multiple pattern cases must be all in one place, so that you can write
foo ("bar", Nothing) = ... at the top of a module, and then have cases like
foo ("baz", Just x) = ... elsewhere. In fact, let's go even further, and allow cases to be defined in different modules entirely!
If you think that sounds like it would be confusing and error-prone to use, you're correct.
To recover some semblance of sanity, we could add some limitations. For instance (ha, ha), we could require the following properties to hold:
- Anywhere such a function is used, the arguments given must match exactly one pattern. Anything else is a compiler error.
- Adding new patterns (including by importing another module) should never change the meaning of valid code--either the same patterns are chosen, or a compiler error is produced.
It should be clear that matching simple constructors like
Nothing is straightforward. We also can handwave things a bit and assume that the compiler can disambiguate literals, like
On the other hand, binding arguments with patterns like
(x, Just y) becomes awkward--writing such a pattern means giving up the ability to write patterns like
(True, _) or
(False, Just "foobar") later, since that would create ambiguity. Worse yet, pattern guards become nearly useless, because they need very general matches. Many common idioms will produce endless ambiguity headaches, and of course writing a "default" fall-through pattern is completely impossible.
This is roughly the situation with type class instances.
We could regain some expressive power by relaxing the required properties as such:
- Anywhere such a function is used, it must match at least one pattern. No matches is a compiler error.
- If a function is used such that multiple patterns match, the most specific pattern will be used. If there is no unique most specific pattern, an error is produced.
- If a function is used in a way that matches a general instance, but could be applied at run-time to arguments that would match a more specific instance, this is a compiler error.
Note that we are now in a situation where merely importing a module can change the behavior of a function, by bringing into scope a new, more specific pattern. Things might get murky in complicated cases involving higher-order functions, as well. Still, in many cases problems are unlikely--say, defining a generic fall-through pattern in a library, while letting client code add specific cases if needed.
That's roughly where
OverlappingInstances puts you. As suggested in the example above, if creating new overlaps is always either impossible or desired, and different modules won't end up seeing different, conflicting instances, then it's probably fine.
What it really comes down to is that the limitations removed by
OverlappingInstances are there to make using type classes behave sensibly under the "open world" assumption that any possible instance could later be added. By relaxing those requirements, you're taking on that burden yourself; so think through all the ways that new instances could be added and whether any of those scenarios are a significant problem. If you're convinced that nothing will break even in obscure and devious corner cases, then go ahead and use the extension.