Hi John,
Den søn. 30. dec. 2018 kl. 23.39 skrev John Clements <cleme...@brinckerhoff.org>: > I’m having a great deal of fun working through The Little Typer. I’ve run > into a problem that I’m sure has a simple solution, but I don’t know what it > is. To understand my question, let me first give a simple example. You've actually run into a subtle problem, the solution to which is not particularly simple! > Here’s a dependent type that’s uninhabited when n is zero: > > #lang pie > > (claim no-zero-please (-> Nat U)) > (define no-zero-please > (λ (n) > (rec-Nat n > Absurd > (λ (n-1 prev) Trivial)))) > > (the (no-zero-please 2) sole) > (the (no-zero-please 1) sole) > ;;; (no-zero-please 0) is uninhabited > > Note the use of rec-Nat, which doesn’t require a motive. What if we use > ind-Nat, instead? Uh oh: > > #lang pie > > (claim no-zero-please (-> Nat U)) > (define no-zero-please > (λ (n) > (ind-Nat n > ;; this doesn't work: > (λ (n) U) > Absurd > (λ (n-1 prev) Trivial)))) > > (the (no-zero-please 2) sole) > (the (no-zero-please 1) sole) > ;;; (no-zero-please 0) is uninhabited > > The problem is that the motive has to have the type (-> Nat U), and U doesn’t > have type U. The rec-Nat works because no motive is necessary. In particular, the rec-Nat case works because the types involved don't need to be described by U, they just need to be types: tgt ⇒ Nat base ⇒ t step ⇐ (→ Nat t t) ------------------------------------------ (rec-Nat tgt base step) ⇒ t > This raises the question of whether ind-Nat is strictly more expressive than > rec-Nat, but in the case of Either, the problem is more severe. You're right. You can use rec-Nat in Pie to write things that you can't write with ind-Nat, precisely because not all types are described by U. Nicely caught! > I wanted to try to formulate a type that was uninhabited for n=0 and n=1, and > I thought I was getting there when I formulated a ternary type and > paired it with a type in a rec-Nat: > > #lang pie > > (claim discrete-3 U) > (define discrete-3 (Either (Either Trivial Trivial) Trivial)) > > (claim zogo (-> Nat (Pair discrete-3 U))) > (define zogo > (λ (n) > (rec-Nat n > (the (Pair discrete-3 U) (cons (left (left sole)) Absurd)) > (λ (n-1 prev) > (ind-Either (car prev) > ;; oh no! can't type check motive: > (λ (dc) (Pair discrete-3 U)) > (λ (inner) (ind-Either inner > (λ (dc) (Pair discrete-3 U)) > (λ (dc) (cons (left (right sole)) Absurd)) > (λ (dc) (cons (right sole) Trivial)))) > (λ (dc) (cons (right sole) Trivial))))))) > > > The problem is that in this case, AFAICT, there’s no “non-motive” version of > ind-Either. > > Am I missing something obvious? You're running into some of the limits that we put on Pie to try to keep it small and simple. There's a few ways that the language could change to support what you're trying to do: 1. We could add in a predicative hierarchy of universes. This would mean that U would be U0, and there'd also be U1, U2, U3, etc, such that Un : Un+1. Then, we could either require you to repeat definitions at each level, or we could have a rule called cumulativity, stating: e : Un ---------- e : Un+1 We actually had this in an earlier version of Pie, but it made the source code of the type checker significantly more subtle to understand because the type equality check has to become a type subsumption check, and subtyping is difficult. 2. Instead of having this rule: tgt ⇒ (Either A B) mot ⇐ (→ (Either A B) U) l ⇐ (Π ((a A)) (mot (left a))) r ⇐ (Π ((b B)) (mot (right b))) ------------------------------------------------- (ind-Either tgt mot l r) ⇒ (mot tgt) we could have had this rule: Γ ⊢ tgt ⇒ (Either A B) Γ, e : (Either A B) ⊢ mot type Γ ⊢ l ⇐ (Π ((a A)) (mot (left a))) Γ ⊢ r ⇐ (Π ((b B)) (mot (right b))) ------------------------------------------------- Γ ⊢ (ind-Either tgt ((e) mot) l r) ⇒ [tgt/e] mot that makes the motive into an expression with some free variable e. In other words, the eliminator becomes a binding form that binds a variable in the motive, rather than using lambda as the ultimate variable binder. Note that this uses the _is a type_ form of judgment for the motive, rather than checking that it has the type U. This would allow also types not described by U to be used. 3. We could give up predicativity, and have U : U. This would leave the language inconsistent as a logic, however, and allow the encoding of infinite loops. In your particular case, you can write a family not inhabited for n=0 or 1 this way: #lang pie (claim Two-Or-More (→ Nat U)) (define Two-Or-More (λ (n) (which-Nat n Absurd (λ (n-1) (which-Nat n-1 Absurd (λ (_) Trivial)))))) (claim test1 (Two-Or-More 3)) (define test1 sole) (claim test2 (Two-Or-More 2)) (define test2 sole) ;; These don't type check: ;; (claim test3 (Two-Or-More 1)) ;; (define test3 sole) ;; (claim test4 (Two-Or-More 0)) ;; (define test4 sole) You can also round-trip through a sum type with a non-dependent eliminator. For instance, you could have converted your (Either Trivial Trivial) to a Nat that is 0 for left, and 1 for right, then used which-Nat. But that's ugly, and we certainly don't want to end up Gödel-coding everything! The real solution is to "graduate" to something like Cur, Idris, Agda, Coq, or Lean, or alternatively to build a fancier Pie if you prefer to stay relatively small. > Thanks in advance! Thank you for reading it so carefully, and for asking such penetrating questions! David -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.