lean4-htt/src/Lean/Meta/Coe.lean
David Thrane Christiansen d8cbf1cefc
doc: docstring review for monads and transformers (#7548)
This PR adds missing monad transformer docstrings and makes their style
consistent.

---------

Co-authored-by: Bhavik Mehta <bm489@cam.ac.uk>
2025-03-20 12:18:46 +00:00

214 lines
9.4 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Transform
import Lean.Meta.SynthInstance
import Lean.Meta.AppBuilder
namespace Lean.Meta
builtin_initialize coeDeclAttr : TagAttribute ←
registerTagAttribute `coe_decl "auxiliary definition used to implement coercion (unfolded during elaboration)"
/--
Return true iff `declName` is one of the auxiliary definitions/projections
used to implement coercions.
-/
def isCoeDecl (env : Environment) (declName : Name) : Bool :=
coeDeclAttr.hasTag env declName
/-- Expand coercions occurring in `e` -/
partial def expandCoe (e : Expr) : MetaM Expr :=
withReducibleAndInstances do
transform e fun e => do
let f := e.getAppFn
if f.isConst then
let declName := f.constName!
if isCoeDecl (← getEnv) declName then
if let some e ← unfoldDefinition? e then
return .visit e.headBeta
return .continue
register_builtin_option autoLift : Bool := {
defValue := true
descr := "Insert monadic lifts (i.e., `liftM` and coercions) when needed."
}
/-- Coerces `expr` to `expectedType` using `CoeT`. -/
def coerceSimple? (expr expectedType : Expr) : MetaM (LOption Expr) := do
let eType ← inferType expr
let u ← getLevel eType
let v ← getLevel expectedType
let coeTInstType := mkAppN (mkConst ``CoeT [u, v]) #[eType, expr, expectedType]
match ← trySynthInstance coeTInstType with
| .some inst =>
let result ← expandCoe (mkAppN (mkConst ``CoeT.coe [u, v]) #[eType, expr, expectedType, inst])
unless ← isDefEq (← inferType result) expectedType do
throwError "could not coerce{indentExpr expr}\nto{indentExpr expectedType}\ncoerced expression has wrong type:{indentExpr result}"
return .some result
| .undef => return .undef
| .none => return .none
/-- Coerces `expr` to a function type. -/
def coerceToFunction? (expr : Expr) : MetaM (Option Expr) := do
-- constructing expression manually because mkAppM wouldn't assign universe mvars
let α ← inferType expr
let u ← getLevel α
let v ← mkFreshLevelMVar
let γ ← mkFreshExprMVar (← mkArrow α (mkSort v))
let .some inst ← trySynthInstance (mkApp2 (.const ``CoeFun [u,v]) α γ) | return none
let expanded ← expandCoe (mkApp4 (.const ``CoeFun.coe [u,v]) α γ inst expr)
unless (← whnf (← inferType expanded)).isForall do
throwError "failed to coerce{indentExpr expr}\nto a function, after applying `CoeFun.coe`, result is still not a function{indentExpr expanded}\nthis is often due to incorrect `CoeFun` instances, the synthesized instance was{indentExpr inst}"
return expanded
/-- Coerces `expr` to a type. -/
def coerceToSort? (expr : Expr) : MetaM (Option Expr) := do
-- constructing expression manually because mkAppM wouldn't assign universe mvars
let α ← inferType expr
let u ← getLevel α
let v ← mkFreshLevelMVar
let β ← mkFreshExprMVar (mkSort v)
let .some inst ← trySynthInstance (mkApp2 (.const ``CoeSort [u,v]) α β) | return none
let expanded ← expandCoe (mkApp4 (.const ``CoeSort.coe [u,v]) α β inst expr)
unless (← whnf (← inferType expanded)).isSort do
throwError "failed to coerce{indentExpr expr}\nto a type, after applying `CoeSort.coe`, result is still not a type{indentExpr expanded}\nthis is often due to incorrect `CoeSort` instances, the synthesized instance was{indentExpr inst}"
return expanded
/-- Return `some (m, α)` if `type` can be reduced to an application of the form `m α` using `[reducible]` transparency. -/
def isTypeApp? (type : Expr) : MetaM (Option (Expr × Expr)) := do
let type ← withReducible <| whnf type
match type with
| .app m α => return some ((← instantiateMVars m), (← instantiateMVars α))
| _ => return none
/--
Return `true` if `type` is of the form `m α` where `m` is a `Monad`.
Note that we reduce `type` using transparency `[reducible]`.
-/
def isMonadApp (type : Expr) : MetaM Bool := do
let some (m, _) ← isTypeApp? type | return false
return (← isMonad? m).isSome
/--
Try coercions and monad lifts to make sure `e` has type `expectedType`.
If `expectedType` is of the form `n β`, we try monad lifts and other extensions.
Extensions for monads.
1. Try to unify `n` and `m`. If it succeeds, then we use
```
coeM {m : Type u → Type v} {α β : Type u} [∀ a, CoeT α a β] [Monad m] (x : m α) : m β
```
`n` must be a `Monad` to use this one.
2. If there is monad lift from `m` to `n` and we can unify `α` and `β`, we use
```
liftM : ∀ {m : Type u_1 → Type u_2} {n : Type u_1 → Type u_3} [self : MonadLiftT m n] {α : Type u_1}, m α → n α
```
Note that `n` may not be a `Monad` in this case. This happens quite a bit in code such as
```
def g (x : Nat) : IO Nat := do
IO.println x
pure x
def f {m} [MonadLiftT IO m] : m Nat :=
g 10
```
3. If there is a monad lift from `m` to `n` and a coercion from `α` to `β`, we use
```
liftCoeM {m : Type u → Type v} {n : Type u → Type w} {α β : Type u} [MonadLiftT m n] [∀ a, CoeT α a β] [Monad n] (x : m α) : n β
```
Note that approach 3 does not subsume 1 because it is only applicable if there is a coercion from `α` to `β` for all values in `α`.
This is not the case for example for `pure $ x > 0` when the expected type is `IO Bool`. The given type is `IO Prop`, and
we only have a coercion from decidable propositions. Approach 1 works because it constructs the coercion `CoeT (m Prop) (pure $ x > 0) (m Bool)`
using the instance `pureCoeDepProp`.
Note that, approach 2 is more powerful than `tryCoe`.
Recall that type class resolution never assigns metavariables created by other modules.
Now, consider the following scenario
```lean
def g (x : Nat) : IO Nat := ...
deg h (x : Nat) : StateT Nat IO Nat := do
v ← g x;
IO.Println v;
...
```
Let's assume there is no other occurrence of `v` in `h`.
Thus, we have that the expected of `g x` is `StateT Nat IO ?α`,
and the given type is `IO Nat`. So, even if we add a coercion.
```
instance {α m n} [MonadLiftT m n] {α} : Coe (m α) (n α) := ...
```
It is not applicable because TC would have to assign `?α := Nat`.
On the other hand, TC can easily solve `[MonadLiftT IO (StateT Nat IO)]`
since this goal does not contain any metavariables. And then, we
convert `g x` into `liftM $ g x`.
-/
def coerceMonadLift? (e expectedType : Expr) : MetaM (Option Expr) := do
let expectedType ← instantiateMVars expectedType
let eType ← instantiateMVars (← inferType e)
let some (n, β) ← isTypeApp? expectedType | return none
let some (m, α) ← isTypeApp? eType | return none
-- Need to save and restore the state in case `m` and `n` are defeq but not monads to prevent this procedure from having side effects.
let saved ← saveState
if (← isDefEq m n) then
let some monadInst ← isMonad? n | restoreState saved; return none
try expandCoe (← mkAppOptM ``Lean.Internal.coeM #[m, α, β, none, monadInst, e]) catch _ => restoreState saved; return none
else if autoLift.get (← getOptions) then
try
-- Construct lift from `m` to `n`
-- Note: we cannot use mkAppM here because mkAppM does not assign universe metavariables,
-- but we need to make sure that the domains of `m` and `n` have the same level.
let .forallE _ (.sort um₁) (.sort um₂) _ ← whnf (← inferType m) | return none
let .forallE _ (.sort un₁) (.sort un₂) _ ← whnf (← inferType n) | return none
let u ← decLevel um₁
let .true ← isLevelDefEq u (← decLevel un₁) | return none
let v ← decLevel um₂
let w ← decLevel un₂
let monadLiftType := mkAppN (.const ``MonadLiftT [u, v, w]) #[m, n]
let .some monadLiftVal ← trySynthInstance monadLiftType | return none
let u_1 ← getDecLevel α
let u_2 ← getDecLevel eType
let u_3 ← getDecLevel expectedType
let eNew := mkAppN (Lean.mkConst ``liftM [u_1, u_2, u_3]) #[m, n, monadLiftVal, α, e]
let eNewType ← inferType eNew
if (← isDefEq expectedType eNewType) then
return some eNew -- approach 2 worked
else
let some monadInst ← isMonad? n | return none
let u ← getLevel α
let v ← getLevel β
let coeTInstType := Lean.mkForall `a BinderInfo.default α <| mkAppN (mkConst ``CoeT [u, v]) #[α, mkBVar 0, β]
let .some coeTInstVal ← trySynthInstance coeTInstType | return none
let eNew ← expandCoe (mkAppN (Lean.mkConst ``Lean.Internal.liftCoeM [u_1, u_2, u_3]) #[m, n, α, β, monadLiftVal, coeTInstVal, monadInst, e])
let eNewType ← inferType eNew
unless (← isDefEq expectedType eNewType) do return none
return some eNew -- approach 3 worked
catch _ =>
/- If `m` is not a monad, then we try to use `tryCoe?`. -/
return none
else
return none
/-- Coerces `expr` to the type `expectedType`.
Returns `.some coerced` on successful coercion,
`.none` if the expression cannot by coerced to that type,
or `.undef` if we need more metavariable assignments. -/
def coerce? (expr expectedType : Expr) : MetaM (LOption Expr) := do
if let some lifted ← coerceMonadLift? expr expectedType then
return .some lifted
if (← whnfR expectedType).isForall then
if let some fn ← coerceToFunction? expr then
if ← isDefEq (← inferType fn) expectedType then
return .some fn
coerceSimple? expr expectedType
end Lean.Meta