Skip to content

feat: LambdaCalculus.LocallyNameless.Coc#392

Open
matthunz wants to merge 19 commits intoleanprover:mainfrom
matthunz:coc
Open

feat: LambdaCalculus.LocallyNameless.Coc#392
matthunz wants to merge 19 commits intoleanprover:mainfrom
matthunz:coc

Conversation

@matthunz
Copy link

@matthunz matthunz commented Mar 3, 2026

Summary

Adds basic syntax and typing judgments for the Calculus of Constructions following Coquand's algorithm. I tried to keep everything as close to other lambda calculi in this project as well as general code style. Some of the lemmas from that paper are noticeably missing but I'm hoping this can be an OK first cut 😃

LambdaCalculus.LocallyNameless.Coc

  • Term syntax
inductive Term (Var : Type u) : Type u
  /-- Bound term variables using a de-Bruijn index. -/
  | bvar : ℕ → Term Var
  /-- Free term variables. -/
  | fvar : Var → Term Var
  /-- Function application. -/
  | app : Term Var → Term Var → Term Var
  /-- Lambda abstraction. -/
  | abs : Term Var → Term Var → Term Var
  /-- Pi type. -/
  | pi : Term Var → Term Var → Term Var
  /-- Type universe. -/
  | type : ℕ → Term Var
  • Typing judgements
inductive Typing : Env Var → Term Var → Term Var → Prop
  /-- Variable lookup in Γ -/
  | var : Γ.Wf → ⟨x, A⟩ ∈ Γ → Typing Γ (.fvar x) A
  /-- Function application -/
  | app : Typing Γ M (.pi A B) → Typing Γ N A → Typing Γ (.app M N) (B ^ᵗ N)
  /-- Lambda abstraction -/
  | abs (ρ : Finset Var) :
      Typing Γ A K →
      (∀ x ∉ ρ, Typing ({⟨x, A⟩} ∪ Γ) (N ^ᵗ .fvar x) (B ^ᵗ .fvar x)) →
      Typing Γ (.abs A N) (.pi A B)
  /-- Pi type -/
  | pi (ρ : Finset Var) :
      Typing Γ A (.type k) →
      (∀ x ∉ ρ, Typing ({⟨x, A⟩} ∪ Γ) (B ^ᵗ .fvar x) (.type i)) →
      (i = k ∨ i = 0) →
      Typing Γ (.pi A B) (.type i)
  /-- Type universe -/
  | type : Typing Γ (.type s) (.type (s + 1))
  /-- β-conversion -/
  | conv : Typing Γ M A → A =β B → Typing Γ B (.type i) → Typing Γ M B

Cslib.Foundations.Syntax.HasBetaEquiv

  • HasBetaEquiv (similar to HasAlphaEquiv) for the notation A =β B
class HasBetaEquiv (t : Type u) where
  /-- β-equivalence relation for type t. -/
  BetaEquiv : t → t → Prop

@chenson2018
Copy link
Collaborator

Hi, thanks for your interest in contributing!

Can I ask why the jump to CoC for this named representation of binding? It is a little uncommon to do this style at all, so there's not much else to build off yet. I was hoping that STLC and System F would get filled out first to work out any kinks before more complex type systems.

We are planning to add well scoped indices as an alternative in the next few months or so, but if your interest is formalizing CoC right now would you be interested in doing so in the locally nameless style? This would be much easier to review in comparison to what we already have and prior work in Rocq.

@matthunz matthunz changed the title feat: LambdaCalculus.Named.Coc feat: LambdaCalculus.LocallyNameless.Coc Mar 4, 2026
@matthunz
Copy link
Author

matthunz commented Mar 4, 2026

Hey 👋 thanks so much for the feedback!

I wanted to go the named representation route at first because it felt more natural to me coming from functional programming but I'm definitely unsure of how to prove things like well-formedness with that approach.

I'd be thrilled if we can fit this in here somehow given the current plans, here's what I did for now:

  • Changed to locally-nameless AST b3c0b23
  • Added the requisite locally-closed and well-formed proofs 8a20752 13ec089

I think the biggest remaining features would now be reduction and the related proofs to tie everything together, which might be a somewhat significant undertaking 😅 happy to defer to your judgement on what the scope of this PR should be

Copy link
Collaborator

@chenson2018 chenson2018 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this looks good, in particular thank you for mostly following conventions from the System F formalization. There are a few important places however where I'm not sure about the definitions. Because these are tricky to get right I suggest closely following prior work like the formalization from Charguéraud I link below.

/-- Pi type. -/
| pi : Term Var → Term Var → Term Var
/-- Type universe. -/
| type : Term Var
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want this to be ℕ → Term Var as well.

Copy link
Author

@matthunz matthunz Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense 👍 I was just going with the simple universal type at first
Changed in 12c85b0

Comment on lines +55 to +57
abbrev Env (Var : Type u) := Finset (Var × Term Var)

def Env.dom [DecidableEq Var] : Env Var → Finset Var := Finset.image Prod.fst
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect we reuse LocallyNameless.Context instead of this abbrev.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I didn't notice that, changed in 0d6e3b9


/-- Variable opening of the ith bound variable. -/
@[scoped grind =]
def openingRec (i : ℕ) (s : Term Var) : Term Var → Term Var
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should name this consistently as openRec and open' with the other typesystems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh agreed, changed in 52d3606

Comment on lines +90 to +91
| abs (L : Finset Var) : σ.LC → (∀ x ∉ L, LC (t₁ ^ᵗ fvar x)) → LC (abs σ t₁)
| pi (L : Finset Var) : σ.LC → (∀ x ∉ L, LC (t₁ ^ᵗ fvar x)) → LC (pi σ t₁)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For CoC, the variable σ is a term, as opposed to System F where it was a type. I'd update the variable names accordingly to use t₁ and t₂.

Copy link
Author

@matthunz matthunz Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense I think my copy-paste is showing there lol, changed in 58caddf


/-- A locally closed term is unchanged by opening. -/
lemma openingRec_lc [HasFresh Var] {σ τ : Term Var} (lc : σ.LC) : σ = σ⟦X ↝ τ⟧ := by
classical
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for decidable equality? I'd rather make this explicit as a typeclass parameter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah exactly I was just following what the linter said there, adjusted to match the conventions in 827087b

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, you mean the linter.unusedDecidableInType linter? This will trigger because it is unaware of the typeclass requirements that happen within the proof for free_union. I have unset this in several places for lambda calculi.

open Term

/-- β-equivalence. -/
inductive BetaEquiv : Term Var → Term Var → Prop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definition looks very different from what I expected. See for instance the definition in the Rocq formalization I closely followed for other type systems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see BetaEquiv was a little lacking 😅 this should now match Chargueraud's representation d6afe92

BetaEquiv := BetaEquiv

/-- Typing judgement -/
inductive Typing [DecidableEq Var] : Env Var → Term Var → Term Var → Prop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting to see a mutual block also defining well-formed contexts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the hint! Your idea about having these be mutually-recursive and Chargueraud's repo finally got me to see how to do this e69ac78

/-- An environment is well-formed if it binds each variable exactly once to a well-formed type. -/
inductive Env.Wf : Env Var → Prop
| nil : Wf {}
| cons : Wf Γ → Term.Wf Γ τ → x ∉ Γ.dom → Wf ({⟨x, τ⟩} ∪ Γ)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is right. Because of the dependent types aspect, we need a typing derivation before adding to the context, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah the context of dependent types was the part I struggled with quite a bit. I had a version of Coquand's algorithm for type checking using this simple context but for this actual formalization I guess that feels pretty weak.

Changed to Chargueraud's representation in e69ac78

open Term

/-- β-reduction. -/
inductive BetaEquiv : Term Var → Term Var → Prop
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have mentioned previously. This should use the reduction_sys attribute to get notation, because this is still reduction and not the equivalence (reflexive symmetric transitive closure), right? I would remove the HasBetaEquiv class and use this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's super interesting 🤔 I'm not sure I fully understand how this works but it looks like if I have @[reduction_sys "β"] attribute on BetaEquiv, then using A ↠β B in the conversion Typing judgement would work like multi-step reduction? It seems to be working in 4f202b4

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this looks correct. It is a custom attribute that we use in CSLib, with some docs at here.

@chenson2018
Copy link
Collaborator

chenson2018 commented Mar 5, 2026

At first glance your changes about the feedback I gave look good. I will give it a closer review hopefully later this week.

It seems there is some new material about reduction now? You had asked about scope, and while there's no hard limit I think at ~450 LoC this is a nice upper bound for what is preferable to review at once.

@matthunz
Copy link
Author

matthunz commented Mar 5, 2026

Thank you for the mentoring here!

Now that β-reduction is in this port would just need β*-reduction, the Church-Rosser theorem, and conversion to be fully complete (as long as what's here is correct so far...).

I'll hold off for now, please let me know if you have any more notes 😄

@chenson2018
Copy link
Collaborator

Thank you for the mentoring here!

No problem, happy to help!

Now that β-reduction is in this port would just need β*-reduction, the Church-Rosser theorem, and conversion to be fully complete (as long as what's here is correct so far...).

By β*-reduction, you mean the reflexive transitive closure, right? When you did @[reduction_sys "β"] this actually registers both a notation ⭢β for the single step reduction you tagged, but also ↠β for this multiple reduction. This second arrow notation is for Relation.ReflTransGen, which is able to work with general relations. So you have this defined already, but let's hold off on adding any additional proofs about it to keep this PR size manageable.

And in general yes, we'll want all these other things you mention, but in small chunks! 😁

I'll hold off for now, please let me know if you have any more notes 😄

I think there will be one or two definitions we might what to generalize, but I can help out with this when I do my second round of reviewing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants