-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add simple co-inductive predicates #17
base: master
Are you sure you want to change the base?
Changes from 9 commits
a88d3b2
409b3aa
6fb6f9b
8955cdf
d808100
0615cb5
fa1545f
b4b2ad3
92ef36f
a3a1f53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,217 @@ | ||||||||||||||||||
/- | ||||||||||||||||||
Copyright (c) 2020 Microsoft Corporation. All rights reserved. | ||||||||||||||||||
Released under Apache 2.0 license as described in the file LICENSE. | ||||||||||||||||||
Authors: William Sørensen | ||||||||||||||||||
-/ | ||||||||||||||||||
prelude | ||||||||||||||||||
import Lean.Elab.Command | ||||||||||||||||||
import Lean.Elab.DeclarationRange | ||||||||||||||||||
import Lean.Elab.Exception | ||||||||||||||||||
import Lean.Elab.Inductive | ||||||||||||||||||
import Lean.Util.Trace | ||||||||||||||||||
|
||||||||||||||||||
namespace Lean.Elab.Command | ||||||||||||||||||
|
||||||||||||||||||
open Lean Lean.Elab Lean.Elab.Term Lean.Parser Command | ||||||||||||||||||
|
||||||||||||||||||
builtin_initialize | ||||||||||||||||||
registerTraceClass `Elab.CoInductive | ||||||||||||||||||
|
||||||||||||||||||
structure CoInductiveView.CtorView where | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Very minor nit, but I'd consider coinductive to be one word, and thus not capitalize the |
||||||||||||||||||
ref : Syntax | ||||||||||||||||||
modifiers : Modifiers | ||||||||||||||||||
declName : Name | ||||||||||||||||||
binders : TSyntaxArray ``Lean.Parser.Term.bracketedBinder | ||||||||||||||||||
type? : Option Term | ||||||||||||||||||
deriving Inhabited | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this the exact same as the already existing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason I defined this is because the original uses |
||||||||||||||||||
|
||||||||||||||||||
structure CoInductiveView where | ||||||||||||||||||
ref : TSyntax ``Lean.Parser.Command.coinductive | ||||||||||||||||||
declId : TSyntax ``Parser.Command.declId | ||||||||||||||||||
modifiers : Modifiers | ||||||||||||||||||
shortDeclName : Name | ||||||||||||||||||
declName : Name | ||||||||||||||||||
levelNames : List Name | ||||||||||||||||||
binders : TSyntaxArray ``Lean.Parser.Term.bracketedBinder | ||||||||||||||||||
type : Term | ||||||||||||||||||
ctors : Array CoInductiveView.CtorView | ||||||||||||||||||
/- derivingClasses : Array Lean.Elab.DerivingClassView -/ | ||||||||||||||||||
/- computedFields : Array Lean.Elab.Command.ComputedFieldView -/ | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Just remove these if we don't want to support them |
||||||||||||||||||
deriving Inhabited | ||||||||||||||||||
|
||||||||||||||||||
namespace CoInductiveView | ||||||||||||||||||
|
||||||||||||||||||
def CtorView.ofStx (declName : Name) (modifiers : Modifiers) (ref : Syntax) : CommandElabM CoInductiveView.CtorView := do | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring? |
||||||||||||||||||
let mut ctorModifiers ← elabModifiers ref[2] | ||||||||||||||||||
if let some leadingDocComment := ref[0].getOptional? then | ||||||||||||||||||
if ctorModifiers.docString?.isSome then | ||||||||||||||||||
logErrorAt leadingDocComment "duplicate doc string" | ||||||||||||||||||
ctorModifiers := { ctorModifiers with docString? := TSyntax.getDocString ⟨leadingDocComment⟩ } | ||||||||||||||||||
if ctorModifiers.isPrivate && modifiers.isPrivate then | ||||||||||||||||||
throwError "invalid 'private' constructor in a 'private' inductive datatype" | ||||||||||||||||||
if ctorModifiers.isProtected && modifiers.isPrivate then | ||||||||||||||||||
throwError "invalid 'protected' constructor in a 'private' inductive datatype" | ||||||||||||||||||
Comment on lines
+51
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I assume you copied this code from somewhere, can we not reuse that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this is almost identical to the code to generate an |
||||||||||||||||||
|
||||||||||||||||||
checkValidCtorModifier ctorModifiers | ||||||||||||||||||
let ctorName := ref.getIdAt 3 | ||||||||||||||||||
let ctorName := declName ++ ctorName | ||||||||||||||||||
let ctorName ← withRef ref[3] $ applyVisibility ctorModifiers.visibility ctorName | ||||||||||||||||||
let (binders, type?) := expandOptDeclSig ref[4] | ||||||||||||||||||
addDocString' ctorName ctorModifiers.docString? | ||||||||||||||||||
addAuxDeclarationRanges ctorName ref ref[3] | ||||||||||||||||||
|
||||||||||||||||||
return { ref, modifiers := ctorModifiers, declName := ctorName, binders := binders.getArgs.map (⟨·⟩), type? := type?.map (⟨·⟩) } | ||||||||||||||||||
|
||||||||||||||||||
def ofModifiersAndStx (modifiers : Modifiers) (decl : Syntax) : CommandElabM CoInductiveView := do | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring? |
||||||||||||||||||
let (binders, type) := expandDeclSig decl[2]! | ||||||||||||||||||
|
||||||||||||||||||
let binders := binders.getArgs.map (⟨·⟩) | ||||||||||||||||||
|
||||||||||||||||||
let declId := ⟨decl[1]⟩ | ||||||||||||||||||
let ⟨shortDeclName, declName, levelNames⟩ ← expandDeclId declId.raw modifiers | ||||||||||||||||||
|
||||||||||||||||||
let ctors ← decl[4].getArgs.mapM $ CtorView.ofStx declName modifiers | ||||||||||||||||||
|
||||||||||||||||||
addDeclarationRanges declName decl | ||||||||||||||||||
|
||||||||||||||||||
return { | ||||||||||||||||||
ref := ⟨decl⟩ | ||||||||||||||||||
|
||||||||||||||||||
declName | ||||||||||||||||||
shortDeclName | ||||||||||||||||||
|
||||||||||||||||||
levelNames | ||||||||||||||||||
type := ⟨type⟩ | ||||||||||||||||||
|
||||||||||||||||||
binders | ||||||||||||||||||
ctors | ||||||||||||||||||
|
||||||||||||||||||
declId | ||||||||||||||||||
modifiers | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
def ofStx (stx : Syntax) := elabModifiers stx[0] >>= (ofModifiersAndStx · stx[1]) | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring? And I would like to see the type annotation, even if it's trivial to infer |
||||||||||||||||||
|
||||||||||||||||||
def extractIds : TSyntax ``Parser.Term.bracketedBinder → Array Ident | ||||||||||||||||||
| ⟨.node _ _ #[.atom _ _, .node _ `null ids, _, _, .atom _ _]⟩ => ids.map (⟨·⟩) | ||||||||||||||||||
| _ => #[] | ||||||||||||||||||
Equilibris marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
def toBinderIds (c : CoInductiveView) : Array Ident := (c.binders.map extractIds).flatten | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically every |
||||||||||||||||||
|
||||||||||||||||||
def toRelType (c : CoInductiveView) : CommandElabM Term := | ||||||||||||||||||
c.binders.reverse.foldlM (fun curr acc => `($acc:bracketedBinder → $curr)) c.type | ||||||||||||||||||
|
||||||||||||||||||
end CoInductiveView | ||||||||||||||||||
|
||||||||||||||||||
open Parser.Term in | ||||||||||||||||||
section | ||||||||||||||||||
abbrev bb := bracketedBinder | ||||||||||||||||||
/- Since `bb` is an alias for `bracketedBinder`, we can safely coerce syntax of these categories -/ | ||||||||||||||||||
instance : Coe (TSyntax ``bb) (TSyntax ``bracketedBinder) where coe x := ⟨x.raw⟩ | ||||||||||||||||||
end | ||||||||||||||||||
|
||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might want to add a few newlines in between here. Also, add a docstring over I don't quite remember what the other alias was called (the one where we stole this trick from), but |
||||||||||||||||||
|
||||||||||||||||||
partial def typeToArgArr (type : Term) : Array Term × Term := Prod.map List.toArray id $ go type.raw | ||||||||||||||||||
where go | ||||||||||||||||||
| .node _ ``Parser.Term.arrow #[hd, _, tail] => Prod.map (⟨hd⟩ :: ·) id $ go tail | ||||||||||||||||||
| rest => ⟨[], ⟨rest⟩⟩ | ||||||||||||||||||
|
||||||||||||||||||
def appsToArgArr (type : Term) : Array Term × Term := match type.raw with | ||||||||||||||||||
| .node _ ``Parser.Term.app #[v, cont] => ⟨cont.getArgs.map (⟨·⟩), ⟨v⟩⟩ | ||||||||||||||||||
| rest => ⟨#[], ⟨rest⟩⟩ | ||||||||||||||||||
|
||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manipulating Syntax like this feels rather brittle. This was fine for QpfTypes, where I number one priority was to get something that at least kind of works, but if we want to PR to core, we should strongly consider going the proper route and elaborating into |
||||||||||||||||||
def extractName : Syntax → Name | ||||||||||||||||||
| .ident _ _ nm _ => nm | ||||||||||||||||||
| _ => .anonymous | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this exists already, |
||||||||||||||||||
|
||||||||||||||||||
def split : Nat → List α → (List α) × (List α) | ||||||||||||||||||
| _, [] => ⟨[], []⟩ | ||||||||||||||||||
| 0, arr => ⟨[], arr⟩ | ||||||||||||||||||
| n+1, hd :: tl => Prod.map (hd :: ·) id $ split n tl | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
-- Coming in these have the form of | name ... : ... Nm topBinders... args... | ||||||||||||||||||
-- But we want them to have the form | name ... : ... Nm.Shape topBinders... RecName args... | ||||||||||||||||||
def handleCtor (names : Array Ident) (topView : CoInductiveView) (isTy : Ident) (view : CoInductiveView.CtorView) : CommandElabM CtorView := do | ||||||||||||||||||
let nm := view.declName.replacePrefix topView.declName (topView.declName ++ `Shape) | ||||||||||||||||||
|
||||||||||||||||||
let type? ← view.type?.mapM fun type => do | ||||||||||||||||||
let ⟨args, out⟩ := typeToArgArr type | ||||||||||||||||||
let ⟨arr, _⟩ := appsToArgArr out | ||||||||||||||||||
|
||||||||||||||||||
let ⟨pre, post⟩ := (split topView.binders.size arr.toList).map (·.toArray) (·.toArray) | ||||||||||||||||||
|
||||||||||||||||||
let out ← `($isTy $pre* $names* $post*) | ||||||||||||||||||
|
||||||||||||||||||
args.reverse.foldlM (fun acc curr => `($curr → $acc)) out | ||||||||||||||||||
|
||||||||||||||||||
return { | ||||||||||||||||||
ref := .missing | ||||||||||||||||||
modifiers := view.modifiers | ||||||||||||||||||
declName := nm | ||||||||||||||||||
binders := .node .none `null (view.binders.map (·.raw)) | ||||||||||||||||||
type? := type? | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
def generateIs (vss : Array (CoInductiveView × Array Ident)) (rNameEntries : Array (Ident × Term)) : CommandElabM Unit := do | ||||||||||||||||||
-- It could be worth making this extract only the names that are required. | ||||||||||||||||||
let boundRNames ← rNameEntries.mapM (fun ⟨i, v⟩ => do `(bb| ( $i : $v) )) | ||||||||||||||||||
let coRecArgs ← vss.mapM (fun ⟨v, _⟩ => do `(bb| ( $(mkIdent $ v.shortDeclName) : $(←v.toRelType)))) | ||||||||||||||||||
let names := vss.map (mkIdent ·.fst.shortDeclName) | ||||||||||||||||||
|
||||||||||||||||||
for ⟨idx, topView, argArr⟩ in vss.toList.enum.toArray do | ||||||||||||||||||
let shortDeclName := topView.shortDeclName ++ `Shape | ||||||||||||||||||
|
||||||||||||||||||
let view := { | ||||||||||||||||||
ref := .missing | ||||||||||||||||||
declId := ←`(declId| $(mkIdent shortDeclName)) | ||||||||||||||||||
modifiers := topView.modifiers | ||||||||||||||||||
shortDeclName | ||||||||||||||||||
declName := topView.declName ++ `Shape | ||||||||||||||||||
levelNames := topView.levelNames | ||||||||||||||||||
binders := .node .none `null $ topView.binders.append coRecArgs | ||||||||||||||||||
type? := some topView.type | ||||||||||||||||||
ctors := ←topView.ctors.mapM $ handleCtor names topView $ mkIdent shortDeclName | ||||||||||||||||||
derivingClasses := #[] | ||||||||||||||||||
computedFields := #[] | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
trace[Elab.CoInductive] s!"{repr topView.binders}" | ||||||||||||||||||
trace[Elab.CoInductive] s!"{topView.toBinderIds}" | ||||||||||||||||||
|
||||||||||||||||||
let boundNames := rNameEntries.map Prod.fst | ||||||||||||||||||
let i := boundNames[idx]! -- OK since these come from the same source | ||||||||||||||||||
|
||||||||||||||||||
let stx ← `(command| | ||||||||||||||||||
abbrev $(mkIdent $ topView.shortDeclName ++ `Is) $(topView.binders)* $boundRNames* : Prop := | ||||||||||||||||||
∀ { $argArr* }, $i $(topView.toBinderIds)* $argArr* → $(mkIdent shortDeclName) $(topView.toBinderIds)* $boundNames* $argArr*) | ||||||||||||||||||
|
||||||||||||||||||
trace[Elab.CoInductive] "Generating Is check:" | ||||||||||||||||||
trace[Elab.CoInductive] stx | ||||||||||||||||||
|
||||||||||||||||||
elabInductiveViews #[view] | ||||||||||||||||||
elabCommand stx | ||||||||||||||||||
|
||||||||||||||||||
def elabCoInductiveViews (views : Array CoInductiveView) : CommandElabM Unit := do | ||||||||||||||||||
let viewCheck ← views.mapM fun view => do | ||||||||||||||||||
let ⟨tyArr, out⟩ := typeToArgArr view.type | ||||||||||||||||||
let argArr := (← tyArr.mapM (fun _ => mkFreshBinderName)).map mkIdent | ||||||||||||||||||
|
||||||||||||||||||
-- In theory we could make this handle types by simply changing the existential quantification but this would yield some pretty funny results | ||||||||||||||||||
let .node _ ``Parser.Term.prop _ := out.raw | throwErrorAt out "Expected return type to be a Prop" | ||||||||||||||||||
return Prod.mk view argArr | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would you change the existential? The reason this works is because If you meant "pull in QpfTypes", sure, but that is very explicitly not something that core wants
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
let rNameEntries ← viewCheck.mapM (fun ⟨v, _⟩ => return Prod.mk (mkIdent $ ←mkFreshBinderName) (←v.toRelType)) | ||||||||||||||||||
|
||||||||||||||||||
generateIs viewCheck rNameEntries | ||||||||||||||||||
for ⟨idx, view, argArr⟩ in viewCheck.toList.enum.toArray do | ||||||||||||||||||
let boundNames := rNameEntries.map Prod.fst | ||||||||||||||||||
let i := boundNames[idx]! -- OK since these come from the same source | ||||||||||||||||||
|
||||||||||||||||||
let stx ← `(def $(mkIdent view.shortDeclName) $(view.binders)* : $(view.type) := fun $argArr* => | ||||||||||||||||||
∃ $[$boundNames:ident]*, @$(mkIdent $ view.shortDeclName ++ `Is) $(view.toBinderIds)* $boundNames* ∧ $i $(view.toBinderIds)* $argArr*) | ||||||||||||||||||
|
||||||||||||||||||
trace[Elab.CoInductive] "Generating co-inductive:" | ||||||||||||||||||
trace[Elab.CoInductive] stx | ||||||||||||||||||
elabCommand stx | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
structure FSM where | ||
S : Type | ||
d : S → Nat → S | ||
A : S → Prop | ||
|
||
-- Example of a coinductive predicate defined over FSMs | ||
|
||
coinductive Bisim (fsm : FSM) : fsm.S → fsm.S → Prop := | ||
| step {s t : fsm.S} : | ||
(fsm.A s ↔ fsm.A t) | ||
→ (∀ c, Bisim fsm (fsm.d s c) (fsm.d t c)) | ||
→ Bisim fsm s t | ||
|
||
/-- | ||
info: inductive Bisim.Shape : (fsm : FSM) → ((fsm : FSM) → fsm.S → fsm.S → Prop) → fsm.S → fsm.S → Prop | ||
number of parameters: 4 | ||
constructors: | ||
Bisim.Shape.step : ∀ {fsm : FSM} {Bisim : (fsm : FSM) → fsm.S → fsm.S → Prop} {s t : fsm.S}, | ||
(fsm.A s ↔ fsm.A t) → (∀ (c : Nat), Bisim fsm (fsm.d s c) (fsm.d t c)) → Bisim.Shape fsm Bisim s t | ||
-/ | ||
Comment on lines
+15
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't feel right. |
||
#guard_msgs in | ||
#print Bisim.Shape | ||
|
||
/-- | ||
info: @[reducible] def Bisim.Is : FSM → ((fsm : FSM) → fsm.S → fsm.S → Prop) → Prop := | ||
fun fsm x => ∀ {x_1 x_2 : fsm.S}, x fsm x_1 x_2 → Bisim.Shape fsm x x_1 x_2 | ||
-/ | ||
#guard_msgs in | ||
#print Bisim.Is | ||
|
||
/-- | ||
info: def Bisim : (fsm : FSM) → fsm.S → fsm.S → Prop := | ||
fun fsm x x_1 => ∃ x_2, Bisim.Is fsm x_2 ∧ x_2 fsm x x_1 | ||
-/ | ||
#guard_msgs in | ||
#print Bisim | ||
|
||
/-- info: 'Bisim' does not depend on any axioms -/ | ||
#guard_msgs in | ||
#print axioms Bisim | ||
|
||
theorem bisim_refl : Bisim f a a := by | ||
exists fun _ a b => a = b | ||
simp only [and_true] | ||
intro s t seqt | ||
constructor <;> simp_all | ||
|
||
theorem bisim_symm (h : Bisim f a b): Bisim f b a := by | ||
rcases h with ⟨rel, relIsBisim, rab⟩ | ||
exists fun f a b => rel f b a | ||
simp_all | ||
intro a b holds | ||
specialize relIsBisim holds | ||
rcases relIsBisim with ⟨imp, z⟩ | ||
constructor <;> simp_all only [implies_true, and_self] | ||
|
||
theorem Bisim.unfold {f} : Bisim.Is f Bisim := by | ||
rintro s t ⟨R, h_is, h_Rst⟩ | ||
constructor | ||
· exact (h_is h_Rst).1 | ||
· intro c; exact ⟨R, h_is, (h_is h_Rst).2 c⟩ | ||
|
||
theorem bisim_trans (h_ab : Bisim f a b) (h_bc : Bisim f b c) : | ||
Bisim f a c := by | ||
exists (fun f s t => ∃ u, Bisim f s u ∧ Bisim f u t) | ||
constructor | ||
intro s t h_Rst | ||
· rcases h_Rst with ⟨u, h_su, h_ut⟩ | ||
have ⟨h_su₁, h_su₂⟩ := h_su.unfold | ||
have ⟨h_ut₁, h_ut₂⟩ := h_ut.unfold | ||
refine ⟨?_, ?_⟩ | ||
· rw [h_su₁, h_ut₁] | ||
· intro c; exact ⟨_, h_su₂ c, h_ut₂ c⟩ | ||
· exact ⟨b, h_ab, h_bc⟩ | ||
|
||
/-- info: 'bisim_refl' depends on axioms: [propext, Quot.sound] -/ | ||
#guard_msgs in | ||
#print axioms bisim_refl | ||
|
||
/-- info: 'bisim_symm' depends on axioms: [propext, Quot.sound] -/ | ||
#guard_msgs in | ||
#print axioms bisim_symm | ||
|
||
/-- info: 'bisim_trans' depends on axioms: [propext] -/ | ||
#guard_msgs in | ||
#print axioms bisim_trans | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
-- Coinductive currently does only work on Prop | ||
|
||
/-- error: Expected return type to be a Prop -/ | ||
#guard_msgs in | ||
coinductive S (α : Type) : Type := | ||
| cons (hd : α) (tl : S α) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a Lean-FRO copyright?