Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does coherence mean?

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:

  1. Int -> Int is not a valid typing derivation, the term \x -> x gets the inferred type a -> a which is strictly more general than Int -> Int.

  2. 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?

like image 385
Noughtmare Avatar asked Jun 16 '21 16:06

Noughtmare


People also ask

What is an example of coherence?

The definition of coherence is something logical or consistent and something that makes sense as a whole. An example of coherence is an argument that has no inconsistencies. A property holding for two or more waves or fields when each individual wave or field is in phase with every other one.

What does coherence mean in a sentence?

Coherence in writing is the logical bridge between words, sentences, and paragraphs. Coherent writing uses devices to connect ideas within each sentence and paragraph. Main ideas and meaning can be difficult for the reader to follow if the writing lacks coherence.

What is the meaning of coherence in communication?

The term coherence refers to the smooth flow of ideas in a text. There are two main strategies that will make your writing coherent: organizing your ideas in a logical order, and connecting them effectively by using transition words and phrases.


1 Answers

Incoherence does not result from a lack of uniqueness of types. Take your example:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

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

There is no problem uniquely assigning types here. Specifically, the types/kinds of top-level identifiers defined in the module are:

A :: Type -> Type -> Constraint
aorb :: (A a b) => Either a b
x :: Either () ()

If you are concerned about the type of the expression aorb used on the right hand side of x = aorb, it's unambiguously Either () (). You can use a type wildcard x = (aorb :: _) to verify this:

error: Found type wildcard '_' standing for 'Either () ()'

The reason this program is incoherent (and the reason GHC rejects it) is that multiple type DERIVATIONS for the type of x :: Either () () are possible. In particular, one derivation uses the instance A () b, while the other uses the instance A a (). I emphasize: both derivations lead to the same typing of the top-level identifier x :: Either () () and the same (static) typing of the expression aorb in x = aorb (namely Either () ()), but they lead to a different term-level definition of aorb being used in the code generated for x. That is, x will exhibit different dynamic semantics (term-level computation) with the same static semantics (type-level computation) depending on which of the two valid typing derivations is used.

This is the essence of incoherence.

So, getting back to your original questions...

You should think of the "typing derivation for a program" as being the entirety of the type-checking process, not just the final types assigned to program fragments. Formally, the "typing" of a program (i.e., the types of all its constituent parts) is a theorem that must be proved to accept a program as well typed. The "typing derivation" of a program is the proof of that theorem. The static semantics of the program are determined by the statment of the theorem. The dynamic semantics are determined in part by the proof of that theorem. If two valid derivations (proofs) lead to the same static typing (theorem) but different dynamic semantics, the program is incoherent.

The expression \x -> x may be typed as a -> a or Int -> Int, depending on context, but the fact that multiple typings are possible is unrelated to incoherence. In fact, \x -> x is always coherent, because the same "proof" (typing derivation) can be used to prove the type a -> a or Int -> Int, depending on context. Actually, as pointed out in the comments, this isn't quite right: the proof/derivation is a little different for different types, but the proof/derivation always leads to the same dynamic semantics. That is, dynamic semantics of the term level definition \x -> x are always "take an argument and return it", regardless of how \x -> x is typed.

The extensions FlexibleInstances and MultiParamTypeClasses can introduce incoherence. In fact, your example above is rejected because it is incoherent. Overlapping instances provide a mechanism to regain coherence, by prioritizing some derivations over others, but they don't work here. The only way to get your example to compile is using incoherent instances.

Defaulting, too, is unrelated to coherence. With the default defaulting, the program:

main = print (10 ^ 100)

has a typing that assigns type Integer to 10 and 100. With different defaulting, the same program has a typing that assigns type Int to 10 and 100. In each case, the static typing of the program is different (i.e., the expression 10 ^ 100 has static type Integer in the first case and Int in the second), and programs with different static typing (different type-level theorems) are different programs, so they're allowed to have different dynamic semantics (different term-level proofs).

like image 181
K. A. Buhr Avatar answered Oct 25 '22 23:10

K. A. Buhr