[GHC] #12088: Promote data family instance constructors
GHC
ghc-devs at haskell.org
Fri Aug 5 15:34:51 UTC 2016
#12088: Promote data family instance constructors
-------------------------------------+-------------------------------------
Reporter: alexvieth | Owner:
Type: feature request | Status: new
Priority: normal | Milestone:
Component: Compiler (Type | Version: 8.1
checker) |
Resolution: | Keywords: TypeInType
Operating System: Unknown/Multiple | Architecture:
Type of failure: GHC rejects | Unknown/Multiple
valid program | Test Case:
Blocked By: | Blocking:
Related Tickets: #11348 | Differential Rev(s): Phab:D2272
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by goldfire):
I have a very different way of thinking about all of this that may shed
some light on the matter.
(Before we get any further, if you'd like my input, do email directly.
It's only dumb luck that I happened to catch up on this ticket so soon
after requesting my feedback.)
Most declarations declare actually ''two'' things: a type and a
definition. For `data T = MkT Int`, we have the type `T :: Type` and the
definition, consisting of `MkT`. For `type family F a where F Bool = Int`,
we get the type `F :: Type -> Type` and the definition `F Bool = Int`.
What's odd about open families is that the type and definition are
separated, quite clearly in the code. But when we think about the fact
that closed definitions are declaring two things at once, perhaps it's the
''closed'' definitions that are odd and the open ones are more natural.
(Indeed, at the term level, we make the type and body separate top-level
declarations.)
So, perhaps that's the answer: treat closed declarations as two separate
pieces, each with its own node in the dependency graph. Now, any
declaration (where "declaration" means either a type declaration or a
definition declaration, but not both wrapped together) can depend on any
other (also separated) declarations.
(Let's hold off a moment on worrying about how to implement this. I'll get
to that later. The goal here is simply to describe what programs are
accepted.)
Let's review the examples from above:
------------------------------------
'''From the original description:'''
{{{
data family T a
data instance T Int = MkT
data Proxy (a :: k)
data S = MkS (Proxy 'MkT)
}}}
Here are the dependencies, where I list a declaration and the things it
depends on (not using transitivity):
* `T` (type only): nothing
* `T Int` (defn only): type of `T`
* type of `Proxy`: nothing
* defn of `Proxy`: type of `Proxy`
* type of `S`: nothing
* defn of `S`: type of `MkT` (= defn of `T Int`)
In the last bullet, we see that the type of a data constructor is
considered in the same declaration as the definition of the parent tycon.
The definition of a constructor is also in this same declaration. (This
suggests further opportunities for splitting, though. Could one
constructor mention another constructor, promoted, from the same type? I
think so.)
These bullets also show that all definitions automatically depend on their
own types. No surprise there.
--------------------------------------
'''From comment:10:'''
{{{
-- module A
type family Open (t :: Type) :: Type
data family F (t :: Type) :: Open t -> Type
}}}
{{{
-- module B
import A
data Q
type instance Open Q = Bool
data instance F Q r where
F0 :: F Q 'True
}}}
* `Open` (type only): nothing
* `F` (type only): type of `Open`
* type of `Q`: nothing
* defn of `Q`: type of `Q`
* `Open Q` (defn only): type of `Open`, type of `Q`
* `F Q r` (defn only): type of `F`, type of `Q`, defn of `Open`
The last bullet above reveals something new: when the type of `A` depends
on the type of `B`, then the definition of `A` depends on the definition
of `B`. In this case, the type of `F` depends on the type of `Open`, and
thus the definition of `F` (that is, `instance F Q r`) depends on the
definition of `Open`.
We now have a small conundrum: what on earth is the definition of `Open`,
an open type family? The only possible answer: all instances that are in
scope. These instances are considered as an inseparable clump. Any attempt
to break them up by bizarre mutual dependencies is met with failure. I do
think there is room to be cleverer here, but I think we'd be too clever by
half if we tried.
In this example, it means that we check the `Open Q` instance before the
`F Q r` instance.
--------------------------------------------
'''Also from comment:10:'''
{{{
-- module A2
type family Open1 (t :: Type) :: Type
type family Open2 (t :: Type) (q :: Open1 t) :: Type
}}}
{{{
-- module B2
import A2
type instance Open1 () = Bool
type instance Open2 () 'True = Char
}}}
* `Open1` (type only): nothing
* `Open2` (type only): type of `Open1`
* `Open1 ()` (defn only): type of `Open1`
* `Open2 ()` (defn only): type of `Open2`, defn of `Open1`
It's now clear that we must check the instance for `Open1` before that of
`Open2`. No difficulty here.
-------------------------------------------
'''From comment:11:'''
{{{
data T1 = MkT1 (...something requiring IA...)
data T2 = MkT2 (...something requiring IB...)
type instance F1 Int = ...T2... -- IA
type instance F2 Int = ...T1... -- IB
}}}
* type of `T1`: nothing
* defn of `T1`: defn of `F1`
* type of `T2`: nothing
* defn of `T2`: defn of `F2`
* `F1 Int` (defn only): type of `F1`, type of `T2`
* `F2 Int` (defn only): type of `F2`, type of `T1`
No problem here either. Go in this order: type of `T1`, type of `T2`, type
of `F1`, type of `F2`, `F1 Int`, `F2 Int`, defn of `T1`, defn of `T2`.
The reason there is no problem is that we have separated types from
definitions.
----------------------------------------------
'''Reactions to comment:12:'''
* It's hard to bad open type families from appearing somewhere, because
we'd have to make the ban transitive. We'd thus have two classes of closed
type families: those that mention open families somewhere (transitively)
and those that don't. This would be awful.
* Your "another idea" might be something like what I've proposed here.
* In answer to your "question": I think only Agda handles this. It's
induction recursion. And I think it's rather like what I've sketched here.
-----------------------------------------------
'''Implementation notes'''
Checking types and definitions separately is a bit of a bother for
datatypes, because the datacons and the tycons mutually depend on each
other. But I think it's possible -- just a little knotty. Let's assume
that the dependency analysis succeeds and that there is a dependency
ordering with no (perhaps transitive) self-dependency.
1. At the beginning of the "type-checking" (ahem, desugaring) pass in
!TcTyClsDecls, go through the declarations in reverse order and create a
knot for each definition (with the list of datacons knot-tied) and each
type (with the tycon knot-tied).
2. Then check the declarations in order.
a. When a type declaration is processed, close out the tycon's knot.
You will now have a tycon usable in every way except to access its
datacons.
b. When a definition declaration is processed, close out its knot. The
datacons will now be untied and usable.
'''Proposition.''' If the dependency analysis is correct, the above
algorithm accesses nothing that is knot-tied.
I propose naming this algorithm the "Celestial Dance of the Black Holes".
It's as dangerous as it sounds. But I believe it's implementable. Perhaps
not by humans. :)
The alternative to the Celestial Dance is to create intermediate
structures with mutable references and such and then zonk them to get the
desired outcome. The zonker will have to be creative about knot-tying, but
that's much simpler. Indeed this may be the better approach, perhaps
leaning more heavily on the rather new `TcTyCon` than we have been.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12088#comment:13>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list