It seems to me that with this encoding, any two inhabited types are equal. This is certainly not a faithful rendering of the corresponding GADT. Why do you think these functional presentations of equality constraints make sense?

GADTs are not endofunctors on *. If I define

data OnlyBool b where That's :: OnlyBool Bool

it is not sensible to expect that OnlyBool has a sensible definition of fmap: as I can define a function of type Bool -> (), I'd expect to get an element of OnlyBool (), which really shouldn't happen. Correspondingly, the conditions necessary for deploying the Yoneda lemma may not always be in place.

GADTs are functors, but from the discrete category |*| (ie, same objects as *, but the morphisms are only the equations, not the functions) to *. See the POPL 2008 paper by m'colleagues Ghani and Johann for more detail on this way of seeing things.

Ok, what about types whose constructors are not exposed? In other words:

module S (succ, pred) where
data S n = Succ { pred :: n }
succ = Succ

For types with no exposed constructors (like the above S example) and no undefine or empty pattern matching then you can restrict the set of morphisms from the given type, however you are still correct that a type like Bool where the constructors are exposed cannot be restricted, at all, in which case the GADT is strictly more powerful. However, the convention with dependently-typed programming is to use empty types without constructors anyway.

Edit: So my point is, yes you can't do the Bool example, but since it is an index inhabited by only one type, it is isomorphic to defining it to be a different index with no constructors exposed for that index.

Let me try to understand the plan. Use the function space in place of a notion of equality, but restrict the availability of functions so that only those corresponding to valid equations are definable. It does make sense to treat the type-level things that we use for indexing as morally a different kind of thing to actual types of values, so we can stop worrying about (Bool -> ()) as somehow meaning an equation. The idea is to construct a subcategory D (for "data" or "discrete") of * such that D and |D| coincide. It's perfectly fine to say that types in D must be constructed in a particular way, hiding constructors, etc.

If you're going to try to make that work, you need to stop asking "what about..?" questions and trading this or that example, and give a systematic treatment. Check that for your construction of D, the functions really do give you the discrete category, and you're sorted. Make sure that the equality GADT works out sensibly for types drawn from D, because really, if GADTs offer anything extra, it's equality.

But I don't think it'll be easy (in fact, I rather suspect it won't work). The function space is readily reflexive and transitive, but symmetry is tricky. And if you expose more functions, like succ and pred, in order to deliver the necessary injectivity of constructors and substitutivity of equal for equal, you may make more things equal than you intend or can handle.

Remember, if you're the one claiming that "GADTs are just such-and-such by translation", you're the one who needs to give a general translation scheme for datatypes and pattern matching programs. And then it's up to the sceptics to find a counterexample.

I understand. I obviously don't think my treatment is realistic (because of the requirement to avoid undefined and empty pattern matches, which I consider impractical for typical Haskell programming). I was just excited because I had my first major leap in developing an intuition for GADTs and thought it might help other people understand them better, too.

It's not like I'm trying to publish a paper. I just try to write my blog posts at the moment I learn a subject and the freshness of it is still on my mind so I can speak like a beginner to another beginner, because I often find that the more comfortable and familiar I am with a subject the greater difficulty I have in formulating it in terms a novice might understand.

Of course, we don't hold blogs to the same standards of rigour and scholarship as peer-reviewed articles. But be careful you don't lead beginners towards misunderstanding. Equality is the essence of what GADTs add. It's not clear to me how a tricky functional construction sheds much light in that direction.

Yeah, it's mostly useful for understanding GADTs that are intended to be endofunctors (like the operational example I gave). Some people brought up the Refl example in the previous reddit thread where I asked about GADTs but only now do I appreciate what they meant when they said that Refl was the nub of what a GADT was for.

As I've just said in a different reply, I'd be quite happy if you could show me a GADT translation scheme which yielded an equality that worked just for types specially constructed to represent indices.

I'm no fan of that terminology, and have no respect for the presumption moral authority that goes with using it, but that is my point. The essence of typed programming is to introduce conceptual distinctions between copies of the natural numbers.

You seem to have a misunderstanding of evil. You say earlier that it is impossible to implement because it is evil, which is not what evil is about at all. The evil distinction is a preference in category theory, which encourages people to think "up to isomorphism" rather than about equality. Even in a proof assistant, we can implement category theory and use evil all we want. It just feels kind of ugly.

Even apart from that, as dolio was explaining above, propositional equality is not inherently evil. Some definitions of functor equality might be, but that's a different story.

Yeah, I think the Yoneda lemma may only hold in the case of a single index, which is the only case I considered since it never occured to me to think of two index GADTs. One other type I was considering was the Isomorphism type:

data Iso a b = Iso (a -> b, b -> a)

If a and b are uninhabited types and you can't use undefined, then the only type you can build would be:

It's reasonable to consider Eq as a GADT with one parameter and one index. Consider (Eq a) to be the predicate which captures "being a". It's a bit like defining vectors with no cons, then abstracting over the type level Z{-ero-}. From that viewpoint, you get

I agree, but in a programming language without undefined you still should be able to define a function absurd :: Void -> a by empty pattern matching. Therefore a and b are isomorphic Iso absurd absurd :: Iso a b, but not equal.

This is the well-known encoding of type equalities using the Leibniz equality. See for example the work of Oleg (and Jeremy Yallop) on encoding leibniz equality, and therefore GADTs, with OCaml first-class modules.

You can do the same thing in any CCC with an initial object. Actually, I think you need uniqueness typing or something to prevent it, which, by the definition of uniqueness typing, would stop the category from being cartesian. The real problem is that is f and g are isomorphic functors, and a and b are isomorphic, then f(a) is isomorphic to g(b). Z is of course isomorphic to S Z, so List a Z is isomorphic to List a (S Z). The problem is that you can't fix it without an evil definition, which GADTs give you.

I think the answer is that I would need to formulate it in a different category than Haskell functions. I still think it may be achievable at the value level with the appropriate choice of category, but then it wouldn't directly correspond to GADTs.

I don't mind losing the evil-ness, which I don't consider that useful. For all the applications of Equal that I can think of, equalness up to isomorphism is sufficient, but maybe I'm missing an important class of applications.

This looks like an incomplete/inaccurate rephrasing of the fact that GADTs are equivalent in power to type equivalence constraints combined with existential quantification. The Yoneda-inspired transformation looks like just an awkward way of expressing the existential (and in fact, I think for your length-indexed list you said it used Rank2Types but actually used ExistentialQuantification without realizing?). Without the equivalence constraints, I think you can't express Equal with your technique at all.

I assume that's why you wanted to eliminate empty pattern matching, to avoid having to deal with some other empty type. But, no. You don't get to do that. This encoding is just incorrect.

And even if we assumed that the above type were only inhabited exactly when a = b, you'd still have the problem of proving that within the theory such that you get the proper eliminator for equality, and not just a token that you can only prove in cases of equality, but doesn't really carry any information. And it's not at all obvious how something like the above would accomplish that.

is evil; the definition of evil is basically when isomorphic elements don't share the property. For example, 2 -> a and (a, a) are isomorphic, so if there is a value of type Equal (a, a) (a, a) then there should also be a value of type Equal (a, a) (2 -> a), unless Equal is evil.

No. A definition is evil if it distinguishes between two isomorphic objects. The identity type does not do this by itself. This must be true, because plain old intensional type theory has identity types, and homotopy type theory (which allows you to prove identity based on higher equivalences like isomorphism) is an extension of intensional type theory. That the identity type in intensional type theory acts like homotopy equivalence automatically is sort of the jumping off point of the whole thing. Intensional type theory lacks the ability to prove that certain equivalent things are identical (because it can also be extended into theories that distinguish between equivalent types), but it does not, on its own, distinguish between equivalent things.

I'm sure that one can prove that (a, a) and 2 -> a are distinct in GHC, but this is due to additional axioms (effectively) that GHC sneaks in, via definitions by case analysis on types (type families/classes) and injectivity of type constructors (perhaps some other things). It is not fundamental to the identity type.

In homotopy type theory, such a value exists. In plain intuitionistic type theory, no such value exists, but there is also no value inhabiting the negation of that type (nor the double negation). And presumably in something more like GHC, the negation is (non-trivially) inhabited.

The Cons constructor is always permissible, since we can always pass it an id for its final field, to satisfy the type (which would constrain m to n). However, the existence of the Nil constructor depends on the existence of a function of type (Z -> S n).

Fortunately, no such function exists, so we can guarantee that such a Nil constructor can never be built. S n has no constructors, so the only way we can build it is to start from a pre-existing value of type S n, and this is what the id function does.

Strictly false. Since Z is empty, there is precisely one function from Z to S n: the empty function. In Haskell, I think the closest we could get is

Yeah, that argument of mine has been torn apart. Not only is there no way to rule out undefined and friends (because of undecidability), but the category of Haskell functions permits all sort of shenanigans that make it impossible to restrict morphisms between types.

What you really want, I think, is to be able to use the right category for these arrows. If you could use the discrete category ℕ for the numbers instead of Hask, you'd be fine: there would be an arrow from n to n and nowhere else, i.e. you'd have an equality type. This gives you equational constraints. So for vectors, you would get (using Agda):

Yeah, that is a great idea. It would be even cooler if there were some way to generalize data types to define constructors in this category so that my Yoneda Lemma trick would still work. I'm not even sure what that would mean. :)

Fortunately for you, arrows in C form a set: Hom(X,Y) for X, Y in C is a set in Set! And I do believe the whole point of the Yoneda embedding is to embed a category into Set. I'm not entirely sure, but this is interesting enough to me because it's almost exactly what I've been looking at recently. Come to #agda on freenode and lets chat with edwardk some more. :)

You could always imagine going the way of Agda: enforcing termination checks for these kinds of functions. The only problem is that it is quite difficult to check termination of part of a program without enforcing termination of all of it. The fine folks that develop Trellys have tried to give a type system in which some terms are terminating without enforcing the whole system to be.

Well, I was thinking of possibly just using a different value-level category than Haskell functions for expression reachability relationships, one in which you can properly restrict the set of permissible morphisms, however I'm not sure what that category would be.