Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of Void using type constructor injectivity #3687

Open
david-christiansen opened this issue Mar 10, 2017 · 10 comments
Open

Proof of Void using type constructor injectivity #3687

david-christiansen opened this issue Mar 10, 2017 · 10 comments

Comments

@david-christiansen
Copy link
Contributor

I hadn't realized that type constructor injectivity caused inconsistency without excluded middle in the past. But @leodemoura pointed me at an example on the Lean issue tracker that proves Void without any postulates, relying on type constructor injectivity alone. I ported it to Idris.

The following code type checks in master for me:

module ProveVoid

%default total

data In : (f : Type -> Type) -> Type where
  MkIn : In f

-- This step requires definitional type constructor injectivity and is
-- the source of the problem.
injIn : In x = In y -> x = y
injIn Refl = Refl

P : Type -> Type
P x = (a : (Type -> Type) ** (In a = x, a x -> Void))

inP : Type
inP = In P

oops : P ProveVoid.inP -> Void
oops (a ** (Ha0, Ha1)) =
  let ohNo : (P (In P) -> Void) = replace {P=\ x => x (In P) -> Void} (injIn Ha0) Ha1
  in ohNo (P ** (Refl, ohNo))

total -- for extra oumph!
ohNo : Void
ohNo =
  let foo : P ProveVoid.inP = (P ** (Refl, oops))
  in oops foo
@edwinb
Copy link
Contributor

edwinb commented Mar 11, 2017

Yikes! On the one hand, we can fix this by turning off type constructor injectivity. On the other hand, this would break a lot of other things (mostly involving interfaces). Any ideas?

@edwinb
Copy link
Contributor

edwinb commented Mar 11, 2017

So this is leading me down a rabbit hole, which is very interesting. But I suppose there is one question which could lead to at least a temporary fix: even if type constructors aren't injective in general, are there any circumstances under which we can know they are injective?

@leodemoura
Copy link

Here is the original source for this example:
https://lists.chalmers.se/pipermail/agda/2010/001526.html

are there any circumstances under which we can know they are injective?

@edwinb I'm also curious about this question, but I didn't manage to find an answer in the literature.
I came across this example when trying to implement OTT in Lean. AFAICT, type constructor injectivity in used in the OTT coercion reduction rules. I'm not claiming OTT is unsound, they consider only a finite set of type constructors (e.g., Sigma, W, Pi, ...), and type constructor injectivity is harmless for them. They point out that in OTT other inductive datatypes can be compiled into this finite set. This observation provides a partial answer (i.e., a sufficient condition) to the question above. For example, a Triple a b c would be compiled using two Sigmas, then using injectivity for Sigma we can deduce that Triple is also injective. In contrast, In would be compiled into the Unit type, and the parameter f would vanish.

@ahmadsalim
Copy link

ahmadsalim commented Mar 11, 2017

@edwinb An option would be to not treat big forced arguments (functions that involve Type) as injective I think.

@ahmadsalim
Copy link

@david-christiansen
Copy link
Contributor Author

@edwinb I'm sorry I threw this up on your day off!

I don't know what the fix is, exactly because of the interface issue. I suppose the place to start is to look at how Agda's instance arguments work around the lack of definitional type constructor injectivity.

@ahmadsalim ahmadsalim removed this from the 1.0 milestone Apr 1, 2017
@yanok
Copy link
Contributor

yanok commented Jan 27, 2018

Regardless of type constructor injectivity (which is still good to get rid of), I think this exact example is just another instance of #3194: if I change built-in pair type to MPair defined in the same file, it doesn't longer type check and universe inconsistence is reported:

module ProveVoid

data MPair : (a : Type) -> (P : a -> Type) -> Type where
     MkMPair : .{P : a -> Type} -> (x : a) -> (pf : P x) -> MPair a P

%default total

data In : (f : Type -> Type) -> Type where
  MkIn : In f

-- This step requires definitional type constructor injectivity and is
-- the source of the problem.
injIn : In x = In y -> x = y
injIn Refl = Refl

P : Type -> Type
P x = MPair (Type -> Type) (\a => (In a = x, a x -> Void))

inP : Type
inP = In P

oops : P ProveVoid.inP -> Void
oops (MkMPair a (ha0, ha1)) =
  let ohNo : (P (In P) -> Void) = replace {P=\ x => x (In P) -> Void} (injIn ha0) ha1
  in ohNo (MkMPair P (Refl, ohNo))

total -- for extra oumph!
ohNo : Void
ohNo =
  let foo : P ProveVoid.inP = (MkMPair P (Refl, oops))
  in oops foo

and of course if I move MPair definition to a separate module it type checks again.

@milessabin
Copy link

milessabin commented Jan 29, 2018 via email

@ahmadsalim
Copy link

@milessabin Implementation (type class instace) resolution relies on type constructor injectivity to resolve that e.g. a = String when Show a = Show String I believe.

I am not sure how essential it is, i.e., whether there is an acceptable workaround. I believe someone (@yanok ? ) tried to turn it off and it broke a few things, so it is definitely used currently.

@ahmadsalim
Copy link

Well, maybe https://lirias.kuleuven.be/bitstream/123456789/582749/1/CW705.pdf could work better, but I don't know how much effort it requires and whether it does avoid issues with type constructor injectivity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants