Skip to content

Commit

Permalink
WIP: refactor: factor out a QPFExpr abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkeizer committed Nov 1, 2024
1 parent fc4fe05 commit fc03cd7
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 90 deletions.
110 changes: 24 additions & 86 deletions Qpf/Macro/Comp.lean
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Mathlib.Data.Vector.Basic

import Qpf.Qpf
import Qpf.Macro.Common
import Qpf.Macro.QpfExpr

import Qq

Expand Down Expand Up @@ -110,9 +111,6 @@ where
let _ ← synthQPF F functor
return ⟨depth, F, args⟩




def List.indexOf' {α : Type} [BEq α] : α → (as : List α) → Option (Fin2 (as.length))
| _, [] => none
| a, b :: bs => if a == b then
Expand All @@ -123,125 +121,67 @@ def List.indexOf' {α : Type} [BEq α] : α → (as : List α) → Option (Fin2
| some i => some <| .fs i


def Fin2.toExpr {n : Nat} : Fin2 n → Q(Fin2 $n)
| .fz => q(Fin2.fz)
| .fs i => q(Fin2.fs $(toExpr i))

instance {n : Nat} : ToExpr (Fin2 n) where
toExpr := Fin2.toExpr
toTypeExpr := q(Fin2 $n)


def Vec.toExpr {α : Q(Type u)} : {n : Nat} → Vec Q($α) n → Q(Vec $α $n)
| 0, _ => q(Vec.nil)
| _+1, as =>
let a : Q($α) := as.last
let as := toExpr as.drop
q(Vec.append1 $as $a)

instance {α : Q(Type u)} {n : Nat} : ToExpr (Vec Q($α) n) where
toExpr := Vec.toExpr
toTypeExpr := q(Vec $α $n)

def DVec.toExpr {n : Nat} {αs : Q(Fin2 $n → Type u)} (xs : DVec (fun (i : Fin2 n) => Q($αs $i))) :
Q(DVec.{u} $αs) :=
match n with
| 0 => q(Fin2.elim0)
| _+1 =>
let a : Q($αs 0) := xs.last
let as := toExpr xs.drop
q(fun
| .fz => $a
| .fs i => $as i
)

/-! ## instances
Our automation needs to refer to instances of `MvQPF` by name.
Since the name of those instances might change, we define our own aliasses
to guard against this.
-/
protected def instMvQPFComp (F : TypeFun n) (Gs : Fin2 n → TypeFun m)
[q : MvQPF F] [qG : ∀ i, MvQPF (Gs i)] :
MvQPF (Comp F Gs) :=
inferInstance

structure ElabQpfResult (u : Level) (arity : Nat) where
F : Q(TypeFun.{u,u} $arity)
qpf : Q(@MvQPF _ $F) := by exact q(by infer_instance)
deriving Inhabited
-- structure QpfExpr (u : Level) (arity : Nat) where
-- F : Q(TypeFun.{u,u} $arity)
-- qpf : Q(@MvQPF _ $F) := by exact q(by infer_instance)
-- deriving Inhabited

def isLiveVar (varIds : Vector FVarId n) (id : FVarId) := varIds.toList.contains id

open PrettyPrinter in
mutual
partial def handleLiveFVar (vars : Vector FVarId arity) (target : FVarId) : TermElabM (ElabQpfResult u arity) := do
partial def handleLiveFVar (vars : Vector FVarId arity) (target : FVarId) : TermElabM (QpfExpr u arity) := do
trace[QPF] f!"target {Expr.fvar target} is a free variable"
let ind ← match List.indexOf' target vars.toList with
| none => throwError "Free variable {Expr.fvar target} is not one of the qpf arguments"
| some ind => pure ind

let ind : Fin2 arity := cast (by simp) ind.inv
let prj := q(@Prj.{u} $arity $ind)
let prj := QpfExpr.prj ind
trace[QPF] "represented by: {prj}"
return prj

pure { F := prj, qpf := q(Prj.mvqpf _) }

partial def handleConst (target : Q(Type u)) : TermElabM (ElabQpfResult u arity) := do
partial def handleConst (target : Q(Type u)) : TermElabM (QpfExpr u arity) := do
trace[QPF] "target {target} is a constant"
let const := q(Const.{u} $arity $target)
let const := QpfExpr.const _ target
trace[QPF] "represented by: {const}"
pure { F := const, qpf := q(Const.mvqpf)}

return const

partial def handleApp (vars : Vector FVarId arity) (target : Q(Type u)) : TermElabM (ElabQpfResult u arity) := do
partial def handleApp (vars : Vector FVarId arity) (target : Q(Type u)) : TermElabM (QpfExpr u arity) := do
let vars' := vars.toList

let ⟨numArgs, F, args⟩ ← (Comp.parseApp (isLiveVar vars) target)
trace[QPF] "target {target} is an application of {F} to {args.toList}"
let F ← QpfExpr.ofTypeFun F

/-
Optimization: check if the application is of the form `F α β γ .. = G α β γ ..`.
In such cases, we can directly return `G`, rather than generate a composition of projections.
-/

let is_trivial := args.toList.mapM Expr.fvarId? == some vars'

if is_trivial then
trace[QPF] "The application is trivial"
let qpf ← synthInstanceQ q(MvQPF $F)
return { F, qpf }
let some F := F.cast?
| throwError "internal error: failed to cast {F} to be of arity {arity}"
return F
else
let G ← args.mmap (elabQpf vars · none false)

let Ffunctor ← synthInstanceQ q(MvFunctor $F)
let Fqpf ← synthInstanceQ q(@MvQPF _ $F)

let G : Vec _ numArgs := fun i => G.get i.inv
let GExpr : Q(Vec (TypeFun.{u,u} $arity) $numArgs) :=
Vec.toExpr (fun i => (G i).F)
let GQpf : Q(∀ i, MvQPF.{u,u} ($GExpr i)) :=
let αs := q(fun i => MvQPF.{u,u} ($GExpr i))
@DVec.toExpr _ _ αs (fun i => (G i).qpf)

let comp := q(@Comp $numArgs _ $F $GExpr)
trace[QPF] "G := {GExpr}"
trace[QPF] "comp := {comp}"
let C := QpfExpr.comp F G
trace[QPF] "represented by: {C}"
return C

let qpf := q(
@Macro.Comp.instMvQPFComp (q := $Fqpf) (qG := $GQpf)
)
return { F := comp, qpf }


partial def handleArrow (binderType body : Expr) (vars : Vector FVarId arity) (targetStx : Option Term := none) (normalized := false) : TermElabM (ElabQpfResult u arity) := do
partial def handleArrow (binderType body : Expr) (vars : Vector FVarId arity) (targetStx : Option Term := none) (normalized := false) : TermElabM (QpfExpr u arity) := do
let newTarget ← mkAppM ``MvQPF.Arrow.Arrow #[binderType, body]
elabQpf vars newTarget targetStx normalized

/--
Elaborate the body of a qpf
-/
partial def elabQpf {arity : Nat} (vars : Vector FVarId arity) (target : Q(Type u)) (targetStx : Option Term := none) (normalized := false) :
TermElabM (ElabQpfResult u arity) := do
TermElabM (QpfExpr u arity) := do
trace[QPF] "elabQPF :: {(vars.map Expr.fvar).toList} -> {target}"
let isLiveVar := isLiveVar vars

Expand Down Expand Up @@ -309,13 +249,11 @@ def elabQpfCompositionBody (view: QpfCompositionBodyView) :
throwError "Expected all args to be fvars" -- TODO: throwErrorAt

let res ← elabQpf vars target_expr view.target

res.F.check
res.qpf.check
res.check

withOptions (fun opt => opt.insert `pp.explicit true) <| do
let F ← delab res.F
let qpf ← delab res.qpf
let F ← delab res.typeFun
let qpf ← delab res.qpfInstance

withQPFTraceNode "results …" <| do
trace[QPF] "Functor := {F}"
Expand Down
5 changes: 1 addition & 4 deletions Qpf/Macro/Data.lean
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import Mathlib.Data.QPF.Multivariate.Constructions.Cofix
import Mathlib.Data.QPF.Multivariate.Constructions.Fix

import Qpf.Macro.Data.Constructors
import Qpf.Macro.Data.Replace
import Qpf.Macro.Data.Count
import Qpf.Macro.Data.View
import Qpf.Macro.Data.Ind
import Qpf.Macro.Common
import Qpf.Macro.Comp
import Qpf.Macro.QpfExpr

open Lean Meta Elab.Command
open Elab (Modifiers elabModifiers)
Expand All @@ -32,7 +30,6 @@ private def Fin2.quoteOfNat : Nat → Term
| 0 => mkIdent ``Fin2.fz
| n+1 => Syntax.mkApp (mkIdent ``Fin2.fs) #[(quoteOfNat n)]


namespace Data.Command

/-!
Expand Down
156 changes: 156 additions & 0 deletions Qpf/Macro/QpfExpr.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Lean
import Qq

import Mathlib.Data.QPF.Multivariate.Constructions.Comp
import Mathlib.Data.QPF.Multivariate.Constructions.Const
import Mathlib.Data.QPF.Multivariate.Constructions.Prj
import Mathlib.Data.QPF.Multivariate.Constructions.Cofix
import Mathlib.Data.QPF.Multivariate.Constructions.Fix

import Qpf.Util.TypeFun

/-!
## QpfExpr
We build an abstraction to bundle a Lean expression of type `TypeFun n` with
the corresponding `MvQPF` instance, and define methods for the various
constructions under which QPFs are closed ((co)fixpoints, composition, etc.).
-/

open Lean Qq

/-- A Lean expression of type `TypeFun n`, with a bundled `MvQPF` instance -/
inductive QpfExpr (u : Level) (n : Nat)
| qpf
(F : Q(TypeFun.{u, u} $n))
(qpf : Q(MvQPF $F) := by exact q(inferInstance))
/- TODO: add a pfunctor special case, as follows:
/-- In `.pfunctor P`, `P` is a Lean Expr of type `MvPFunctor n`.
This is a specialization, which serves to preserve the fact that a QPF is
actually polynomial -/
| pfunctor (P : Expr)
-/
deriving Inhabited

/-! ## Preliminaries -/
section ToExpr

def Fin2.toExpr {n : Nat} : Fin2 n → Q(Fin2 $n)
| .fz => q(Fin2.fz)
| .fs i => q(Fin2.fs $(toExpr i))
instance {n : Nat} : ToExpr (Fin2 n) where
toExpr := Fin2.toExpr
toTypeExpr := q(Fin2 $n)

def Vec.toExpr {α : Q(Type u)} : {n : Nat} → Vec Q($α) n → Q(Vec $α $n)
| 0, _ => q(Vec.nil)
| _+1, as =>
let a : Q($α) := as.last
let as := toExpr as.drop
q(Vec.append1 $as $a)
instance {α : Q(Type u)} {n : Nat} : ToExpr (Vec Q($α) n) where
toExpr := Vec.toExpr
toTypeExpr := q(Vec $α $n)

def DVec.toExpr {n : Nat} {αs : Q(Fin2 $n → Type u)} (xs : DVec (fun (i : Fin2 n) => Q($αs $i))) :
Q(DVec.{u} $αs) :=
match n with
| 0 => q(Fin2.elim0)
| _+1 =>
let a : Q($αs 0) := xs.last
let as := toExpr xs.drop
q(fun
| .fz => $a
| .fs i => $as i
)

end ToExpr

namespace QpfExpr

/-! ## Basic Definitions -/

/-- The raw type function, i.e., an expression of type `TypeFun n` -/
def typeFun {n : Nat} : QpfExpr u n → Q(TypeFun.{u, u} $n)
| qpf F _ => F

/-- The bundled QPF instance, i.e., an expression of type `MvQPF ($typeFun)` -/
def qpfInstance {n : Nat} : (q : QpfExpr u n) → Q(MvQPF $q.typeFun)
| qpf _ q => q


variable {m} [Monad m] [MonadLiftT MetaM m] in
/-- Construct a `QPFExpr` from an expression of type `TypeFun n`, by
synthesizing the corresponding MvQPF instance.
Throws an error if synthesis fails. -/
def ofTypeFun {n : Nat} (F : Q(TypeFun.{u, u} $n)) : m (QpfExpr u n) := do
let qpf ← synthInstanceQ q(MvQPF $F)
return QpfExpr.qpf F qpf

/-- cast a `QpfExpr u n` to be of arity `m`, assuming that `n` and `m` are
actually equal. Returns `none` if `n ≠ m` -/
def cast? {n m} (e : QpfExpr u n) : Option (QpfExpr u m) :=
if h : n = m then
some (h ▸ e)
else
none

/-- check that the expressions contained in an QpfExpr have the correct types -/
def check : QpfExpr u n → MetaM Unit
| qpf F q => do
F.check
q.check

/-! ## Tracing-/

def toMessageData : QpfExpr u n → MessageData
| qpf F q => m!"qpf ({F}) ({q})"
instance : ToMessageData (QpfExpr u n) := { toMessageData }

/-! ## Constructions -/

/-! ### Basic Functors (Constants and Projections) -/

/-- A constant functor, see `MvQPF.Const` -/
def const (n : Nat) (A : Q(Type u)) : QpfExpr u n :=
qpf q(MvQPF.Const $n $A)

/-- `prj i` is a functor that projects to the i-th argument, see `MvQPF.Prj` -/
def prj {u : Level} {n : Nat} (i : Fin2 n) : QpfExpr u n :=
qpf q(MvQPF.Prj $i)

/-! ### (Co)Fixpoint -/

/-- The (inductive) fixpoint of a QPF, see `MvQPF.Fix` -/
def fix : QpfExpr u (n+1) → QpfExpr u n
| qpf F _q => qpf q(MvQPF.Fix $F)

/-- The (coinductive) cofixpoint of a QPF, see `MvQPF.Cofix` -/
def cofix : QpfExpr u (n+1) → QpfExpr u n
| qpf F _q => qpf q(MvQPF.Cofix $F)

/-! ### Composition -/

/--
Our automation needs to refer to this instance by name.
Since the name of this instances might change, we define our own aliasses.
-/
private def instMvQPFComp (F : TypeFun n) (Gs : Fin2 n → TypeFun m)
[q : MvQPF F] [qG : ∀ i, MvQPF (Gs i)] :
MvQPF (MvQPF.Comp F Gs) := by
infer_instance

/-- Compositions of QPFs, see `MvQPF.comp` -/
def comp (F : QpfExpr u n) (Gs : Vec (QpfExpr u m) n) : QpfExpr u m :=
let GExpr : Q(Vec (TypeFun.{u,u} $m) $n) :=
Vec.toExpr (fun i => (Gs i).typeFun)
let GQpf : Q(∀ i, MvQPF.{u,u} ($GExpr i)) :=
let αs := q(fun i => MvQPF.{u,u} ($GExpr i))
@DVec.toExpr _ _ αs (fun i => (Gs i).qpfInstance)

let comp := q(@MvQPF.Comp $n _ $F.typeFun $GExpr)
let qpfInstance := q(
@instMvQPFComp (q := $F.qpfInstance) (qG := $GQpf)
)
qpf comp qpfInstance

0 comments on commit fc03cd7

Please sign in to comment.