Zulip Chat Archive
Stream: lean4
Topic: mkLetFVars with nondependent let
𝚠𝚘𝚓𝚌𝚒𝚎𝚌𝚑 𝚗𝚊𝚠𝚛𝚘𝚌𝚔𝚒 (Jul 26 2025 at 20:47):
When a let-binding is introduced via withLetDecl (nondep := true), abstracting it away later via mkLetFVars does not produce a have, but rather a lambda. It makes sense in terms of the implementation - nondependent bindings are stored as Should this be considered a bug or intended behavior?cdecls - but is certainly quite unintuitive.
import Lean
open Lean Meta Elab Term
#version -- Lean 4.23.0-nightly-2025-07-26
-- let x := True.intro; x
run_meta do
let t ← withLetDecl `x (.const ``True []) (.const ``True.intro []) (nondep := False) fun x =>
mkLetFVars #[x] x
logInfo t
-- fun x => x
run_meta do
let t ← withLetDecl `x (.const ``True []) (.const ``True.intro []) (nondep := True) fun x =>
mkLetFVars #[x] x
logInfo t
𝚠𝚘𝚓𝚌𝚒𝚎𝚌𝚑 𝚗𝚊𝚠𝚛𝚘𝚌𝚔𝚒 (Jul 26 2025 at 20:53):
Oh, and it works as expected with generalizeNondepLet := false:
-- have x := True.intro; x
run_meta do
let t ← withLetDecl `x (.const ``True []) (.const ``True.intro []) (nondep := true) fun x =>
mkLetFVars (generalizeNondepLet := false) #[x] x
logInfo t
The documentation for it is buried far down, but it seems to be what I want here?
Kyle Miller (Jul 26 2025 at 20:55):
Yes, this is intended behavior. https://github.com/leanprover/lean4/blob/2be6c75c2baba5764a2dac57cabfcb69d273d4c9/src/Lean/LocalContext.lean#L63
If you are wanting to use withLetDecl and mkLetFVars in a pair like this, it's better to use mapLetDecl
Kyle Miller (Jul 26 2025 at 20:58):
There's some more documentation at docs#Lean.MetavarContext.MkBinding.mkBinding too
𝚠𝚘𝚓𝚌𝚒𝚎𝚌𝚑 𝚗𝚊𝚠𝚛𝚘𝚌𝚔𝚒 (Jul 26 2025 at 21:08):
Thanks! I can't use mapLetDecl because I am producing several Exprs whereas that only supports mapping one. I am basically inlining its implementation, though. The docstring on ldecl contains a mysterious remark:
Rule: never use (generalizeNondepLet := false) in mkBinding-family functions within a local context you do not own. See LocalDecl.setNondep for some additional discussion.
What does it mean to 'own' a local context?
Kyle Miller (Jul 26 2025 at 21:09):
𝚠𝚘𝚓𝚌𝚒𝚎𝚌𝚑 𝚗𝚊𝚠𝚛𝚘𝚌𝚔𝚒 said:
nondependent bindings are stored as
cdecls
They're being stored as ldecls.
I linked to docstrings with some explanation, but the short version is that there's an "inside" and an "outside" to nondependent lets. From "inside" the value is opaque, but from "outside" you can see what the value is. It's unfortunately more complex than you'd hope...
The API is designed so that you can create a nondep let and call whatever you want (it will appear cdecl-like), and then you're able to abstract it as a have still.
Kyle Miller (Jul 26 2025 at 21:10):
"own" is supposed to mean "I extended this local context with my own bindings, and I will only abstract those bindings"
Kyle Miller (Jul 26 2025 at 21:11):
the point is that you shouldn't do mk*FVars with (generalizeNondepLet := false) with fvars that you don't know exactly where they came from.
𝚠𝚘𝚓𝚌𝚒𝚎𝚌𝚑 𝚗𝚊𝚠𝚛𝚘𝚌𝚔𝚒 (Jul 26 2025 at 21:23):
Ah ok, maybe it would be clearer to say 'never use ... on a local context entry you did not create'? I can make a small doc PR. UPDATE: It's lean4#9570.
Last updated: Dec 20 2025 at 21:32 UTC