Coherence and orphan instance rules
9 points by abhin4v
9 points by abhin4v
I look forward to more of this! But I also have some nitpicks:
Crucially, instances are not first-class values and they’re not named. This allows maintaining a useful property that we want to have when working with typeclasses: if we call the same method with the same type parameters in different parts of a program, they should all call the same method. This is absolutely essential...
It's very, very, very useful, but not essential. For example...
If we log/print a value with an overloaded function, the printed format for the same argument is the same, regardless of where it was printed...
...but the logging function often wants to be context dependent, so while log.write(thing) wants to write the same output for every thing, it might not want to write the same output for every log.write.
(If you’re familiar with OOP with subtyping, coherence exists in OOP languages as
x.m()calling the same methodmfor the same type ofx, everywhere in the program.)
But that doesn't happen when the method is virtual and overridden in a subclass of x, so the subtyping kinda kills coherence? And if you allow diamond inheritance it really kills it.
And I've heard hearsay that allowing non-coherent instances via language extensions is also pretty common in Haskell code and usually doesn't blow anything up.
However, interestingly, I couldn’t find any formal treatment of orphan instance rules, with proofs that the rules only allow a coherent system and examples of common use cases that they support.
Hmm, really? That IS interesting. I wonder if Rust Belt has one? I wouldn't think a formal proof of Rust's orphan rules would be too hard, I've done an informal one myself. But from the "Rebalancing Coherence" and "Re-rebalancing Coherence" RFC's I suppose they're working on the details still.
happen when the method is virtual and overridden in a subclass of x[…]?
No. Note that this says "for the same type of x" (and not, for example, "for the same type of reference to x". No matter what, x.m() must call the subtype's definition of m. And that's what vtables are for :)
In general, OOP doesn't struggle with coherence because everything is, for lack of a better word, "vertical". Resolution is just hierarchical: the actual definition of a subclass already fixes in place what each of its methods resolves to. Diamond inheritance doesn't change that.
Ad-hoc polymorphism brings in the fun with coherence because it's "horizontal". Instance resolution is not decided by any one definition, it's a global search across every module available to the program.