Suggestion: refine overlap handling for instance declarations
Claus Reinke
claus.reinke at talk21.com
Tue Mar 14 07:54:46 EST 2006
overlapping instances with best-fit overlap resolution are useful
because they give us an alternative way to write programs that
we cannot write directly as long as current Haskell isn't expressive
enough:
(a) we can't specify that two types in an instance are not equal;
but we can use overlap resolution to ensure that equal types
are handled by another, more specific instance
(b) we can't enable library users to add instances for their types,
yet ensure that there will be some instance for every type;
but we can use overlap resolution to provide a default
instance, to be overridden by more specific instances in
library and user code
(c) we can't rely on mutual exclusion properties specified in instance
contexts to be used to select the appropriate instance because
Haskell only looks at instance heads;
but we can sometimes wrap our types in extra constructors
so that the mutual exclusion is reflected in more and less
specific instance heads, with overlap resolution choosing
the right one (this is a real pain, brittle against change, and
not always possible..)
(d) ..this seems to cover most uses discussed so far, but I don't
claim this list to be complete. if you know of examples not
covered by a-c, please add them by reply!
there hasn't been any enthusiasm about changing (c) for Haskell',
which isn't to my liking, but nothing I can do much about. I would,
however, like to continue arguing in favour of refining overlap
handling until we get at least some progress into Haskell' (and
addenda). so I propose to add the something like the following
to Haskell':
---------- we know how to handle (a) [no overlap needed]:
syntax:
permit type inequalities as guards in instance declarations:
<topdecls> -> ..
| 'instance' [<ctxt> '=>'] <head> [ '|' <ineqs> ] [<body>]
<ineqs> -> <typevar> '/=' <type> [ ',' <ineqs>]
semantics (sketch):
- add inequality guards from an instance as guards to the
rules for that instance in the CHR
- add inequality guards as attributes to the typevars
after successful guard evaluation and rule commit
remarks:
- we only need the extra guard syntax because of (c) above
- we restrict inequalities to variables on lhs to simplify semantics
- attributed variables are logic programming folklore:
variable attributes are simply goals to be reexamined whenever
the attributed variable is instantiated; we need them here to
ensure that after we have selected a CHR rule based on an
inequality, that inequality is not violated by later instantiations
-------------------- we can make (b) more explicit:
instead of enabling overlapping instances and overlap resolution
on a per-module or per-session basis, we can specify which
instances we want to use only as defaults:
syntax:
<topdecls> -> ..
| ['default'] 'instance' [<ctxt> '=>'] <head> [ '|' <ineqs> ] [<body>]
semantics (sketch):
- default instances may be overridden by more specific instances
including other default instances, but no such overlap is permitted
unless there is a single most specific instance
- default instances only apply if there can be no more specific
instances; the easiest way to model this is by collecting all
instances for the class, select those that are more specific
than some default instance, and add type inequality guards
that exclude those instantiations from the rule for the
default instance
- no other instances are affected by overlap resolution
remarks:
- by declaring all instances as 'default', we could recover the
current, indiscriminate overlap handling; but we hardly ever
want that, as declaring default instances allows us to be
more precise, limiting the impact of overlap handling
- once we have type inequality guards in the language, we
can be a bit stricter about ruling out overlaps than GHC
is at the moment (see Simon's earlier example)
- default instances with ground heads (no variables) can be
treated like non-default instances (there can't be any more
specific instances)
- without further analysis, application of default instances has
to be delayed until the whole program is visible; that does
not prevent separate compilation, it only prevents early
application of some instances (those marked as defaults);
in other words, separately compiled modules are
parameterized by the set of more specific instances for
their default instances
- we don't want to go through all that trouble for every
instance in every class, which is the motivation for being
very specific about where we want overlaps to be
permitted and resolved
cheers,
claus
More information about the Haskell-prime
mailing list