[Haskell-cafe] local instances / scoped instances
anthony_clayden at clear.net.nz
Wed Jul 5 11:40:40 UTC 2017
Can anybody explain the idea of typeclass instances being
declared with a scope? Im not sure if these two terms
mean the same. Perhaps the difference is:
* local instances are declared in the familiar
let-style: `let <instance> in <expr>`
So the scope is the body of the `in` expr.
* scoped instances are declared as usual at the top
level in a module.
Scope is controlled by the export/import naming.
(That is, theres a way to name/identify instances for
export/import. Note that currently you cant
control scope of instances;
Theyre imported to any module that (transitively)
imports the module where an instance is declared.)
(I'm doubtful that controlling scope of the
name-for-instance would actually have
the desired effect. Compare if I declare:
> data T = MkT Int
> f :: T -> String
and export `f`, but not `T` nor `MkT`;
nevertheless the type of `f` in some distant module
is still `T -> String` (or `M.T -> String`). So detail
of the type itself is exported.
What are the use cases for instances being anything other
than top-level/global scope?
One seems to be so that a method can use a value/function
declared in a surrounding scope, without needing an explicit
parameter passed around the whole program.
This is explored in [Kis04] (and linked references, esp.
Implicit configurations), particularly for passing
run-time parameters into methods.
(What imperative programmers might do via global variables.
In Haskell: nowadays Implicit Parameters.)
[Kis04] (and especially the link to The pioneering
work of Kaes) laments that Kaes original description
of typeclasses/instances has never been delivered.
Im not convinced Kaes is actually describing local
instances. Hes giving the formal semantics
for instances using let-binding;
because hes using let as the standard way in Lambda
Calculus to introduce any binding.
Usual let-binding in LC provides a fresh name for each
binding. That is, if that same name is in an outer scope,
the outer binding is shadowed/unreachable within the
scope of the fresh name.
But instance binding is different:
* Theres no fresh name: the class name is exactly
the one already in scope.
* Theres no shadowing: this instance is _in
addition to_ those already in scope.
thats what overloading needs.
Overloading a class method with two or more instances in the
same scope means theres no principal type for the method
[W&B88 Appendix A.7].
Thats why in Haskell classes/instances/methods are at top
level only. The methods then do have a most-general
principal type `show :: (Show a) => a -> String`.
This early work was of course before MPTCs, FunDeps,
Overlaps, Flexible Instances/Contexts,
constrained existentials, constrained GADTs.
AFAICT it was expected there would be only one instance per
type. So making instances global probably wasnt too
Ive seen some later ideas (especially scoped
instances blocking the export of an instance)
where the intent seems to be to have
_different_ instances in different scopes for the
same class/same type.
For example (apparently) a pretty print format for
`show`. Or an alternative `Ord` for case-insensitive
sorting of Chars/Strings.
What are the use cases here? In particular, whats wrong
* using a different class/method name for different
behaviour (for example supplying a different comparison
via `sortBy`); or
* wrapping in a newtype and declaring an instance of
`Show/Ord` on the different nominal type?
Im worried: classes come with laws.
For example wrt case-insensitive `Ord`: `Eq` is a
superclass. So if we get case-insensitive `A <=
`a <= A`, the laws expect ` A ==
Specifically the base library `Data.Set` relies on those
laws for `Ord` to build balanced trees.
(Theres also a whole load of puzzlement to do with
how the instance scoping rules work, as explored in [Kis04].
But I dont want to worry about the implementation yet.)
[Kis04] Local instances: frequently still discussed and
still not implemented, Kiselyov O.
??originally 2004, current version 2014
See also papers linked/referenced from that site.
(Thank you Oleg for bringing out the issues.)
[Kaes88] Parametric Overloading in Polymorphic Programming
Languages, Kaes S. 1988
[W&B88] How to make ad-hoc Polymorphism less ad-hoc, Wadler
P. & Blott S. 1988
More information about the Haskell-Cafe