Zulip Chat Archive

Stream: lean4

Topic: Reduction of types in lambda and forall bindings


Yann Herklotz (Aug 02 2024 at 15:47):

reduce does not reduce the types of lambda and forall bindings, only the final expressions. For example:

#reduce (proofs := true) (types := true) 1 + 1 = 2  True
-- ==> 1 + 1= 2 → True

This seems to be a design decision, because the implementation for reduction of foralls and lambdas first abstracts these away, reduces, and then reconstructs the forall (GitHub link to implementation: src/Lean/Meta/Reduce.lean#L40):

| Expr.forallE ..    => forallTelescope e fun xs b => do mkForallFVars xs ( visit b)

I was wondering what the main downsides would be if one allowed reduction of types in the binders, maybe with an optional flag that enables it, like the reduction of proofs and types?

I do agree that it could be quite expensive, but are there other reasons one may never actually want this?

We sometimes have hypotheses that quantify over quite large types because they are generated by a function, but they usually can be reduced into a small concrete type if this function is given concrete values. So to make it easier to look at the context, we would like to reduce all of these expressions, which might appear in binders in hypotheses.

I have implemented a version of reduce which does exactly this, adding an option called skipArgs which will skip the reduction of binders, I'll post the implementation below.

Yann Herklotz (Aug 02 2024 at 15:48):

import Lean

open Lean Expr Meta

partial def reallyReduce (e : Expr) (explicitOnly skipTypes skipProofs skipArgs := true) : MetaM Expr :=
  let rec visit (e : Expr) : MonadCacheT Expr Expr MetaM Expr :=
    checkCache e fun _ => Core.withIncRecDepth do
      if ( (pure skipTypes <&&> isType e)) then
        return e
      else if ( (pure skipProofs <&&> isProof e)) then
        return e
      else
        let e  whnf e
        match e with
        | Expr.app .. =>
          let f      visit e.getAppFn
          let nargs := e.getAppNumArgs
          let finfo  getFunInfoNArgs f nargs
          let mut args  := e.getAppArgs
          for i in [:args.size] do
            if i < finfo.paramInfo.size then
              let info := finfo.paramInfo[i]!
              if !explicitOnly || info.isExplicit then
                args  args.modifyM i visit
            else
              args  args.modifyM i visit
          if f.isConstOf ``Nat.succ && args.size == 1 && args[0]!.isRawNatLit then
            return mkRawNatLit (args[0]!.rawNatLit?.get! + 1)
          else
            return mkAppN f args
        | Expr.lam ..        => lambdaTelescope e fun xs b => do
          if skipArgs then mkLambdaFVars xs ( visit b)
          else
            let ctx  getLCtx
            let ctx'  xs.foldlM (fun ctx' e => do
              let e'  visit (ctx'.getFVar! e).type
              return ctx'.modifyLocalDecl e.fvarId! fun l => l.setType e') ctx
            withLCtx ctx' #[] do mkLambdaFVars xs ( visit b)
        | Expr.forallE ..    => forallTelescope e fun xs b => do
          if skipArgs then mkForallFVars xs ( visit b)
          else
            let ctx  getLCtx
            let ctx'  xs.foldlM (fun ctx' e => do
              let e'  visit (ctx'.getFVar! e).type
              return ctx'.modifyLocalDecl e.fvarId! fun l => l.setType e') ctx
            withLCtx ctx' #[] do mkForallFVars xs ( visit b)
        | Expr.proj n i s .. => return mkProj n i ( visit s)
        | _                  => return e
  visit e |>.run

def reallyReduceAll (e : Expr) : MetaM Expr :=
  reallyReduce e false false false

syntax (name := reallyReduceCmd) "#reallyReduce " term : command

open Elab Command in
@[command_elab reallyReduceCmd] def elabReallyReduce : CommandElab
  | `(#reallyReduce%$tk $term) => go tk term
  | _ => throwUnsupportedSyntax
where
  go (tk : Syntax) (term : Syntax) : CommandElabM Unit :=
    withoutModifyingEnv <| runTermElabM fun _ => Term.withDeclName `_reduce do
      let e  Term.elabTerm term none
      Term.synthesizeSyntheticMVarsNoPostponing
      let e  Term.levelMVarToParam ( instantiateMVars e)
      -- TODO: add options or notation for setting the following parameters
      withTheReader Core.Context (fun ctx => { ctx with options := ctx.options.setBool `smartUnfolding false }) do
        let e  withTransparency (mode := TransparencyMode.all) <| reallyReduce e false false false false
        logInfoAt tk e

#reduce (proofs := true) (types := true) 1 + 1 = 2  True
#reallyReduce 1 + 1 = 2  True

Last updated: May 02 2025 at 03:31 UTC