In the paper "Type classes: exploring the design space" by Simon Peyton Jones, Mark Jones and Erik Meijer, they define coherence informally as follows:
Every different valid typing derivation for a program leads to a resulting program that has the same dynamic semantics.
First of all, programs don't have types; expressions, variables and functions have types. So I guess I will interpret it as saying that each program fragment (expression, variable or function) must have a unique type derivation.
Then I wonder if Haskell (say Haskell2010) is really coherent? E.g. the expression \x -> x
can be given the type a -> a
, but also Int -> Int
. Is does that mean that coherence is broken? I can think of two rebuttals:
Int -> Int
is not a valid typing derivation, the term\x -> x
gets the inferred typea -> a
which is strictly more general thanInt -> Int
.The dynamic semantics are the same in both cases, it is just that the type
Int -> Int
is less general and will be rejected statically in some contexts.
Which of these are true? Are there other counterarguments?
Now let's consider type classes, because coherence is often used in that context.
There are several ways in which Haskell as implemented by GHC possibly breaks coherence. Obviously the IncoherentInstances
extension and the related INCOHERENT
pragma seem relevant. Orphan instances also come to mind.
But if point 1 above is true, then I would say that even these don't break coherence, because we could just say that the instance selected by GHC is the one true instance that should be chosen (and all the other type derivations are invalid), just like how the type inferred by GHC is the true type that has to be chosen. So point 1 is probably not true.
Then there are more innocent seeming extensions like overlapping instances via the OverlappingInstances
extension or the Overlapping
, Overlaps
and Overlappable
pragmas, but even the combination of MultiParamTypeClasses
and FlexibleInstances
can give rise to overlapping instances. E.g.
class A a b where
aorb :: Either a b
instance A () b where
aorb = Left ()
instance A a () where
aorb = Right ()
x :: Either () ()
x = aorb
The FlexibleInstances
and MultiParamTypeClasses
extensions are included in GHC2021
so I would certainly hope that they don't break coherence. But I don't think point 1 above is true and point 2 doesn't seem to apply here because the dynamic semantics are really different.
I would also like to mention the defaulting system. Consider:
main = print (10 ^ 100)
By default GHC (and maybe Haskell2010?) will default to using Integer
for both 10
and 100
. So the result prints a one with a hundred zeroes. But if we now add a custom defaulting rule:
default (Int)
main = print (10 ^ 100)
Then both 10
and 100
default to the type Int
and due to wrapping this prints only a single zero. So the expression 10 ^ 100
has different dynamic semantics in different context. Is that incoherent?
So my question is: is there a more formal or more detailed definition of coherence that can resolve the questions raised above?