Zulip Chat Archive

Stream: new members

Topic: Would you use a Prop or a Type?


Dan Plyukhin (Jan 30 2025 at 20:20):

I'm working on mechanizing a lambda calculus. I have inductive types for the syntax:

mutual
  inductive Expr where ...
  inductive Val where ...
  inductive Ty where ...
end

The static semantics is defined by a typing relation, which is currently a Prop:

inductive Typed : Typing.Context -> Expr -> Ty -> Prop where ...

I have a lot of recursive definitions that tell me some property about a typed expression:

def foo: (M: Expr) -> (exists G T, Typed G M T) -> A := ...
def bar: (M: Expr) -> (exists G T, Typed G M T) -> B := ...
def qux: (M: Expr) -> (exists G T, Typed G M T) -> C := ...

But recursive calls are a huge pain, because I have to re-convince Lean that the subexpressions are typed.

def foo (M: Expr) -> (exists G T, Typed G M T) -> A :=
  | (app M1 M2), M_typed =>
    let a1 := foo M1 (... convince Lean that M1 is typed ...)
    let a2 := foo M2 (... convince Lean that M2 is typed ...)
    ...

How would you change things to make foo and bar and qux easier to define? My main idea is to change Typed from a Prop to a Type and so I could just write something like def foo : Typed M -> A = ... but I'm worried this could somehow bite me later in the project. For example, you can't use the cases tactic on a Type.

Dan Plyukhin (Jan 30 2025 at 20:41):

Err, now I realize converting it to a Type wouldn't be enough because there are still a G and a T getting existentially quantified. That is, I couldn't define def foo : (exists G T, Typed G M T) -> A because foo's argument would be a Prop. I guess I could use PSigma instead. But surely there's a simpler way?

Edward van de Meent (Jan 30 2025 at 20:42):

you might ask for G and T to be provided explicitly to foo?

Dan Plyukhin (Jan 30 2025 at 20:45):

True! But I'd still have to construct a new G and T when I make recursive calls to foo, right? Ideally I could just write:

let a1 := foo M1
let a2 := foo M2
...

Edward van de Meent (Jan 30 2025 at 20:47):

could be... although maybe that's where you can write some lemmas to help?

Edward van de Meent (Jan 30 2025 at 20:48):

if you use the "exists G T" version, you'd need to provide a proof of this anyway

Edward van de Meent (Jan 30 2025 at 20:52):

btw you can use the cases tactic on a value of a type. for example:

example (n : Nat) : Or (n = 0) (exists (n':Nat), n = n' + 1) := by
  cases n
  . simp only [Nat.self_eq_add_left, Nat.add_one_ne_zero, exists_const, or_false]
  . simp only [Nat.add_one_ne_zero, Nat.add_right_cancel_iff, exists_eq', or_true]

Kyle Miller (Jan 30 2025 at 20:52):

In case it's helpful as a case study, in docs#Rack.PreEnvelGroupRel' I found it was easier to make a Type-valued relation for recursion purposes, and then I "propified" it in docs#Rack.PreEnvelGroupRel (That's the same as using Nonempty; I don't remember why it is a custom type.)

Edward van de Meent (Jan 30 2025 at 20:53):

readability?

Edward van de Meent (Jan 30 2025 at 20:53):

naming?

Edward van de Meent (Jan 30 2025 at 20:53):

there can be many good reasons

Kyle Miller (Jan 30 2025 at 20:55):

Good guesses, but we'll never know. I wrote it, and I don't remember ;-)

Dan Plyukhin (Jan 30 2025 at 20:55):

@Edward I think that sounds similar to what I have already---for each typing rule in Typed, I have inversion lemmas that tell me the subexpressions are typed. Something like:

lemma app_inv1 G M1 M2 T : Typed G (app M1 M2) T -> (exists G T, Typed M1 T) := ...
lemma app_inv2 G M1 M2 T : Typed G (app M1 M2) T -> (exists G T, Typed M2 T) := ...

So my definition of foo can go something like this:

def foo (M: Expr) -> (exists G T, Typed G M T) -> A :=
  | (app M1 M2), M_typed =>
    let a1 := foo M1 (by app_inv1)
    let a2 := foo M2 (by app_inv2)
    ...

It's minimal, but still tedious

Dan Plyukhin (Jan 30 2025 at 20:58):

@Kyle ooh cool idea!

Edward van de Meent (Jan 30 2025 at 21:04):

depending on how you define Typed, since you're doing matching, you could maybe recover these values from just matching on M_typed?

Dan Plyukhin (Jan 30 2025 at 21:06):

Assuming Typed is a Type right? Because we can't match on a Prop at runtime IIRC

Dan Plyukhin (Jan 30 2025 at 21:07):

In that case indeed the inversion lemmas could just be replaced with getters on M_typed

Dan Plyukhin (Jan 30 2025 at 21:09):

Well, I'd still have to pack the proof together with witness context and type

Edward van de Meent (Jan 30 2025 at 21:10):

yea, i don't think youll be able to avoid that

MrQubo (Mar 02 2025 at 16:23):

Maybe something like that could work?

structure Typed where
  M : Expr
  T : Ty
  proof : exists G, M.isOfType T := by is_of_type_tactic
attribute [simp] Typed.proof

You would have to work out good tactic, but assuming it worked, you can construct Typed just by providing Expr and Ty.


Last updated: May 02 2025 at 03:31 UTC