88public import RandomDo.Tactic.Computable.Counterparts
99public import RandomDo.Monad.MeasurableSpace
1010public meta import Lean.Elab.Tactic.Basic
11- public meta import Batteries.Tactic.Lint.Basic
1211
1312/-!
1413# The `@[computable]` attribute
1514
16- Writing an `rdo` program and writing the program that samples from it are two separate steps, and
17- the second is mechanical: every construct of the first has a counterpart in the second. This
18- attribute walks the program and writes that counterpart down, so a program carries the sampler it
19- denotes:
15+ `@[computable]` reads an `rdo` program and adds the program that samples from it:
2016
2117```
2218@[computable]
@@ -25,19 +21,19 @@ noncomputable def shifted : Measure ℝ := rdo
2521 return x + 1
2622```
2723
28- adds `shifted_computable : RandPCG IO Float`, which draws from `NumLean.normal 0 1` and adds one.
24+ adds `shiftedComputable : RandPCG IO Float`, which draws from `NumLean.normal' 0 1` and adds one.
2925
30- ## How the program is read
26+ The Giry monad and its two operations become `RandPCG IO`, `pure` and `bind`. Anything else is
27+ rebuilt from the counterpart `@[computable_as]` records for its head, with its arguments translated
28+ in turn and its instances synthesized anew. A term translates into a term of the translation of its
29+ type; where the rebuilt one does not, its head is a definition nothing is known about, and its body
30+ is read in its place. `@[computable]` records the program it writes, so a program drawing from
31+ another translates into one calling that other's translation.
3132
32- The two constructs `rdo` is made of become the two of `do`: `return e` becomes `pure e`, and
33- `let x ← p; q` becomes `p >>= q`, at `RandPCG IO`. Everything else — a distribution, a numeral, an
34- operation on the values the program computes with — is rebuilt from the counterpart
35- `@[computable_as]` records for its head, applied to the translation of its arguments; the
36- instances it asks for are synthesized anew, for the translated types.
33+ Two things extend the attribute: an `@[computable_as]` entry, and an alternative of `translate` for
34+ a construct of `rdo` it has not been taught.
3735
38- Setting `set_option trace.computable true` prints the tree of pieces the attribute walked through,
39- each with what it became, the instances synthesized along the way, and the declaration written at
40- the end.
36+ `set_option trace.computable true` prints what each piece became.
4137-/
4238
4339public meta section
@@ -48,77 +44,104 @@ namespace RDo.Tactic
4844
4945initialize registerTraceClass `computable
5046
51- /-- The monad a translated program lives in, `RandPCG IO`: the counterpart of the Giry monad an
52- `rdo` program is written over. -/
47+ /-- The monad the translated programs live in. -/
5348def computableMonad : MetaM Expr := mkAppM ``RandPCG #[mkConst ``IO]
5449
55- /-- Translate `e`, a piece of an `rdo` program, into its computable counterpart. `σ` sends the
56- variables the program binds to the ones the translated program binds in their place, which the
57- change of types makes necessary. -/
50+ mutual
51+
52+ /-- Translate a piece of an `rdo` program. `σ` maps the variables the program binds to the ones the
53+ translated program binds in their place. -/
5854partial def translate (σ : FVarSubst) (e : Expr) : MetaM Expr :=
59- withTraceNode `computable
60- (fun
61- | .ok e' => return m! "{ e} ↦ { e'} "
62- | .error _ => return m! "{ e} : not translated" ) do
63- -- `MeasurableSpacePure.mPure` takes five arguments, the last of which is the value returned.
64- if e.isAppOfArity ``MeasurableSpacePure.mPure 5 then
65- mkAppOptM ``Pure.pure #[← computableMonad, none, none, ← translate σ (e.getArg! 4 )]
66- -- `MeasurableSpaceBind.mBind` takes eight, the last two the program and the continuation.
67- else if e.isAppOfArity ``MeasurableSpaceBind.mBind 8 then
55+ withTraceNode `computable (fun
56+ | .ok e' => return m! "{ e} ↦ { e'} "
57+ | .error _ => return m! "{ e} : not translated" ) do
58+ match_expr e with
59+ | MeasurableSpacePure.mPure _ _ _ _ a =>
60+ mkAppOptM ``Pure.pure #[← computableMonad, none, none, ← translate σ a]
61+ | MeasurableSpaceBind.mBind _ _ _ _ _ _ p k =>
6862 mkAppOptM ``Bind.bind #[← computableMonad, none, none, none,
69- ← translate σ (e.getArg! 6 ), ← translate σ (e.getArg! 7 )]
70- else match e with
71- | .fvar fvarId => return σ.get fvarId
72- | .sort .. | .lit .. => return e
73- | .mdata _ b => translate σ b
74- | .lam .. =>
75- lambdaBoundedTelescope e 1 fun xs body ↦ do
76- let x := xs[0 ]!
77- let t ← translate σ (← x.fvarId!.getType)
78- withLocalDeclD (← x.fvarId!.getUserName) t fun y ↦ do
79- mkLambdaFVars #[y] (← translate (σ.insert x.fvarId! y) body)
80- | _ =>
81- let .const declName _ := e.getAppFn
82- | throwError "`computable`: cannot translate{indentExpr e}"
83- let counterpart := (← computableAs? declName).getD declName
84- let mut f ← mkConstWithFreshMVarLevels counterpart
85- for arg in e.getAppArgs do
86- let .forallE _ t _ bi ← whnf (← inferType f)
87- | throwError "`computable`: {f} does not take the argument{indentExpr arg}"
88- /- An instance is the one argument that is not translated but synthesized anew, so the
89- trace above it says nothing: it is reported here instead. -/
90- let arg ←
91- if bi.isInstImplicit then do
92- let inst ← synthInstance t
93- trace[computable] "instance: { inst} "
94- pure inst
95- else
96- translate σ arg
97- unless ← isDefEq (← inferType arg) t do
98- throwError "`computable`: {arg} does not fit the argument of {f}, of type{indentExpr t}"
99- f := mkApp f arg
100- return f
63+ ← translate σ p, ← translate σ k]
64+ | MeasureTheory.Measure α _ => return mkApp (← computableMonad) (← translate σ α)
65+ | _ => match e with
66+ | .fvar x => return σ.get x
67+ | .sort .. | .lit .. => return e
68+ | .mdata _ b => translate σ b
69+ | .lam .. => lambdaBoundedTelescope e 1 fun xs body ↦ do
70+ let x := xs[0 ]!.fvarId!
71+ withLocalDeclD (← x.getUserName) (← translate σ (← x.getType)) fun y ↦ do
72+ mkLambdaFVars #[y] (← translate (σ.insert x y) body)
73+ | _ => translateApp σ e
74+
75+ /-- Rebuild an application from the counterpart of its head; where nothing known about that head
76+ fits, look through it and read its body in its place. -/
77+ partial def translateApp (σ : FVarSubst) (e : Expr) : MetaM Expr := do
78+ if e.getAppFn.isLambda then return ← translate σ e.headBeta
79+ let .const declName _ := e.getAppFn | throwError "`computable`: cannot translate{indentExpr e}"
80+ let counterpart? ← computableAs? declName
81+ let head := counterpart?.getD declName
82+ /- We save the state of metavariables, so that a failed rebuild does not leave them in a
83+ half-built state. -/
84+ let s ← saveState
85+ try
86+ /- A term translates into a term of the translation of its type. A head nothing is known about
87+ rebuilds into itself, of the type it had, and might fail. We check that the rebuilt term has
88+ the translation of the type, and if not, we look through it. -/
89+ let f ← rebuild σ head e
90+ -- Useless for a type, whose type is a sort either way.
91+ if (← inferType f).isSort then return f
92+ let expected ← translate σ (← inferType e)
93+ unless ← isDefEq (← inferType f) expected do
94+ throwError "`computable`: {f} is of type{indentExpr (← inferType f)}\n \
95+ where the translation asks for{indentExpr expected}"
96+ return f
97+ catch ex =>
98+ restoreState s
99+ /- The rebuild failed, we try to look through the head, and read its body in its place. -/
100+ if counterpart?.isNone then
101+ if let some e' ← unfoldDefinition? e then
102+ trace[computable] "nothing known about { declName} , looking through it"
103+ return ← translate σ e'
104+ throw ex
105+
106+ /-- Apply `head` to the arguments of `e`, translated in turn, and check that the result has the
107+ translation of the type of `e`. -/
108+ partial def rebuild (σ : FVarSubst) (head : Name) (e : Expr) : MetaM Expr := do
109+ let mut f ← mkConstWithFreshMVarLevels head
110+ for arg in e.getAppArgs do
111+ let .forallE _ t _ bi ← whnf (← inferType f)
112+ | throwError "`computable`: {f} does not take the argument{indentExpr arg}"
113+ let arg ←
114+ if bi.isInstImplicit then do
115+ -- An instance is not translated: it is asked for anew, at the translated types.
116+ let inst ← synthInstance t
117+ trace[computable] "instance: { inst} "
118+ pure inst
119+ else
120+ translate σ arg
121+ unless ← isDefEq (← inferType arg) t do
122+ throwError "`computable`: {arg} does not fit the argument of {f}, of type{indentExpr t}"
123+ f := mkApp f arg
124+ return f
125+
126+ end
101127
102128/-- Translate the `rdo` program `declName` and add the translation to the environment, under the
103- name `declName` followed by `_computable `. -/
129+ name `declName` followed by `Computable `. -/
104130def addComputableDecl (declName : Name) : MetaM Unit := do
105131 let info ← getConstInfo declName
106132 let some value := info.value?
107133 | throwError "`computable` can only be derived for a definition, but {declName} has no value"
108134 let value ← instantiateMVars (← translate {} value)
109135 let type ← instantiateMVars (← inferType value)
110- let translated := declName.appendAfter "_computable "
136+ let translated := declName.appendAfter "Computable "
111137 addAndCompile <| .defnDecl <| ← mkDefinitionValInferringUnsafe translated info.levelParams type
112138 value (.regular (getMaxHeight (← getEnv) value + 1 ))
113- trace[computable] "wrote { translated} :{ indentExpr type} "
114- addDocStringCore translated s! "The program that samples from `{ declName} `, written by the \
115- `@[computable]` attribute."
116- /- The name is one the attribute picks and not one the user wrote, so the underscore in it is
117- reported for every program translated unless it is exempted here. -/
118- setEnv (← ofExcept (Batteries.Tactic.Lint.nolintAttr.setParam (← getEnv) translated
119- #[`defsWithUnderscore]))
120-
121- /-- The `@[computable]` attribute. -/
139+ addDocStringCore translated s! "The computable program that samples from `{ declName} ` \
140+ (automatically generated by the `@[computable]` attribute)."
141+ computableAsExt.add declName translated
142+ trace[computable] "wrote { translated} :{ indentExpr type} "
143+
144+ @ [inherit_doc addComputableDecl]
122145initialize registerBuiltinAttribute {
123146 name := `computable
124147 descr := "translate this `rdo` program into the program that samples from it"
0 commit comments