Zulip Chat Archive

Stream: Is there code for X?

Topic: Converting Expr projection to recursor application


Easyoakland (Jul 18 2025 at 06:52):

The doc comment for Lean.Expr.proj says

When exporting Lean developments to other systems, proj can be replaced with typeName.rec applications.

Does this code already exist somewhere? I know how it will work conceptually, but wonder if it already exists.

Robin Arnez (Jul 18 2025 at 11:03):

That statement is a half-lie since projections are (usually) the primitive for structures, see e.g.:

example : @Prod.rec = fun {_ _ _} mk t => mk t.1 t.2 := rfl

but

import Lean

open Lean Meta

def projToRec (e : Expr) : MetaM (Option Expr) := do
  let .proj nm idx struct := e | return none
  let ind  getConstInfoInduct nm
  let [ctorName] := ind.ctors | throwError "invalid projection"
  let ctor  getConstVal ctorName
  let rec  getConstInfoRec (nm ++ `rec)
  let casesOn := mkCasesOnName nm
  let type  inferType struct
  let type  withTransparency .all (whnf type)
  let propRecursor := rec.levelParams.length == ind.levelParams.length
  type.withApp fun fn paramsAndIndices => do
    unless fn.isConstOf nm do
      throwError "invalid projection {e}"
    let params := paramsAndIndices.extract 0 ind.numParams
    let indices := paramsAndIndices.extract ind.numParams
    let ctorType  instantiateForall ctor.type params
    let indType  instantiateForall ind.type params
    forallTelescope indType fun indexVars _ => do
    withLocalDeclD `x (mkAppN (mkAppN fn params) indexVars) fun structVar => do
      let indexVars := indexVars.push structVar
      let rec mkCtorNth (type : Expr) (i n : Nat) : Expr :=
        match type with
        | .forallE nm t b bi => .lam nm t (mkCtorNth b i (n + 1)) bi
        | _ => .bvar (n - i - 1)
      let rec mkCasesOnApp (motive : Expr) (i : Nat) (indicesAndStruct : Array Expr) : MetaM Expr := do
        let levels  if propRecursor then pure fn.constLevels! else pure (( getLevel motive) :: fn.constLevels!)
        let expr := mkAppN (.const casesOn levels) params
        let expr := .app expr ( mkLambdaFVars indexVars motive)
        let expr := mkAppN expr indicesAndStruct
        return .app expr (mkCtorNth ctorType i 0)
      let rec traverseCtor (type : Expr) (i : Nat) : MetaM Expr := do
        let .forallE nm t b bi := type | throwError "invalid projection {e}, out of range"
        if i < idx then
          traverseCtor (b.instantiate1 ( mkCasesOnApp t i indexVars)) (i + 1)
        else
          mkCasesOnApp t i (indices.push struct)
      termination_by idx - i
      some <$> traverseCtor ctorType 0

should produce something definitionally equivalent to a projection.

Aaron Liu (Jul 18 2025 at 17:02):

I ran into an edge case where it doesn't work

open Qq

def pp (α : Q(Prop)) (P : Q($α  Prop)) (h : Q(Exists $P)) : Q($α) := q(($h).1)

#check by_elab do
  let some u  projToRec (pp q(7 < 3) q(fun h => [][20] = 4) q(sorry)) | failure
  pure u

Robin Arnez (Jul 20 2025 at 13:16):

You're right; I think it should work now (I edited the original message)

Aaron Liu (Jul 20 2025 at 14:18):

My edge case generator generated another edge case where it doesn't work

open Qq

def pp (α : Q(Prop)) (β : Q(Prop)) (h : Q($α ×' $β)) : Q($α) := q(($h).1)

#check by_elab do
  let some u  projToRec (pp q(1 + 1 = 2) q(False  ¬False) q(rfl, .inr not_false)) | failure
  pure u

Aaron Liu (Jul 20 2025 at 14:32):

oh never mind this one is just the elaborator screwing with me

open Qq

def pp (α : Q(Prop)) (β : Q(Prop)) (h : Q($α ×' $β)) : Q($α) := Expr.proj `PProd 0 h

#check by_elab do
  let some u  projToRec (pp q(1 + 1 = 2) q(False  ¬False) q(rfl, .inr not_false)) | failure
  pure u

Aaron Liu (Jul 20 2025 at 14:36):

ok this one is an actual counterexample

open Qq

set_option bootstrap.inductiveCheckResultingUniverse false in
set_option genSizeOf false in
set_option genInjectivity false in
inductive PProd' : Sort u  Sort v  Sort (max u v) where
  | mk {α : Sort u} {β : Sort v} (a : α) (b : β) : PProd' α β

def pp (α : Q(Type)) (β : Q(Prop)) (h : Q(PProd' $α $β)) : Q($α) := q(($h).1)

#check by_elab do
  let some u  projToRec (pp q(False ⊕' ¬False) q(1 + 1 = 2) q(⟨.inr not_false, rfl)) | failure
  pure u

Robin Arnez (Jul 20 2025 at 18:50):

Yeah, this one's impossible to convert to recursor though: lean4#7637

Easyoakland (Jul 25 2025 at 06:49):

Thanks!

@Aaron Liu what is this edge case generator you speak of?

Aaron Liu (Jul 25 2025 at 06:50):

Easyoakland said:

Aaron Liu what is this edge case generator you speak of?

I generate edge cases with my brain


Last updated: Dec 20 2025 at 21:32 UTC