Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Idiomatic way to add declarations to a LocalContext


Ernest Ng (Jun 27 2025 at 19:05):

Hi! I was wondering what is the idiomatic way to add extra declarations to a LocalContext?

For context, I am inheriting a codebase in which the following function is used to add extra decls to the LocalContext, and I am unsure if this implementation is the correct way of doing things:

-- For each `(arg, ty)` pair in `Array.zip argNames input_types`,
-- `mkInputLocalContext` adds a new declaration `arg : ty` is added to the local
-- context. The resultant LocalContext is returned by this function.
-- Invariant: `input_types.size == argNames.size`
def mkInputLocalContext (input_types : Array Expr) (argNames: Array String)
: MetaM LocalContext
:= do
   let mut lctx  getLCtx
   for (arg, ty) in argNames.zip input_types do
    let argname := Name.mkStr1 arg
    let fvarid   mkFreshFVarId
    input_args := input_args.push fvarid
    lctx :=  mkLocalDecl lctx fvarid argname ty
  return lctx

Specifically, I am unsure whether the technique of first calling mkFreshFVarId and then calling mkLocalDecl is the recommended way of adding decls, since the docs for mkLocalDecl that mkLocalDecl should not be called directly.

I was wondering if the idiomatic solution is instead to use Meta.withLocalDecl instead? However, I see that Meta.withLocalDecl reverts the context after it is called, and I was wondering if there is an function analogous to withLocalDecl which provides the caller with the updated local context (after the extra decls have been added)?

Thanks!

Kyle Miller (Jun 27 2025 at 19:06):

withLocalDecl is idiomatic, and you can get the local context from inside it with getLCtx

Kyle Miller (Jun 27 2025 at 19:07):

withLocalDecls lets you add many at once

Ernest Ng (Jun 27 2025 at 19:09):

Thanks for the quick reply! I saw in the docs for withLocalDecl that it "reverts the context" after it is called, and I was wondering if there is a variant of withLocalDecl that doesn't revert the context?

Kyle Miller (Jun 27 2025 at 19:10):

No, that's not possible, since the local context is part of the monad's reader state. It's not actively being reverted, but rather you leave the scope where it's temporarily been changed.

Kyle Miller (Jun 27 2025 at 19:11):

In any case, that doesn't matter, because you can use getLCtx from within that callback to obtain the local context. You can do whatever you want with it.

Kyle Miller (Jun 27 2025 at 19:11):

import Lean

open Lean Meta

def mkInputLocalContext (inputTypes : Array Expr) (argNames: Array String) :
    MetaM LocalContext := do
  let spec := inputTypes.zip argNames |>.map fun (ty, name) => (Name.mkSimple name, .default, fun _ => return ty)
  withLocalDecls spec fun _ => getLCtx

Ernest Ng (Jun 27 2025 at 19:12):

Ah I see that makes sense, thanks! It seems like calling getLCtx within the callback (continuation) that is passed to withLocalDecls is the preferred way of "explicitly threading" the LocalContext between different function calls.

Kyle Miller (Jun 27 2025 at 19:15):

That's not necessarily the case; it's often better to run your code from within withLocalDecls rather than working with the local context itself, threading it around.

That said, it does happen, and for that there's withLCtx. Notice that it also takes a list that caches the local instances. That's one of the things that withLocalDecl properly manages for you.

Kyle Miller (Jun 27 2025 at 19:17):

(I should say that there's a good amount of code out there that uses the mkLocalDecl interface, but it's pretty low level and it's usually done for some specialized low-level optimization. It's best to avoid it unless you know you really need it.)

Ernest Ng (Jun 27 2025 at 19:18):

Ah that's fair, good point! It looks like withLocalDecl also properly handles the low-level operations associated with managing FVarIds (since it takes in a Name and a type and adds it to the context for you), and using withLocalDeclsto add a bunch of declarations to the LocalContext is to be preferred over managing FVarIds yourself.


Last updated: Dec 20 2025 at 21:32 UTC