Zulip Chat Archive
Stream: general
Topic: Persistent interactive data?
Niels Voss (Feb 23 2026 at 08:29):
Python has an interactive mode where you can do something like
>>> name = input("Enter your name: ")
Enter your name: Bob
>>> print(name)
Bob
>>> n = len(name)
>>> n
3
I'm wondering if such a thing would be possible for Lean (e.g. if we were to attach a proper non-JSON-based CLI to the Lean REPL). Suppose I wanted to create a random list and refer to it in several different #eval commands. I was wondering if there was some way to store the results of #eval in a variable.
def n : IO Nat := IO.rand 0 100
def myList : IO (List Nat) := do
return [← IO.rand 0 100, ← IO.rand 0 100, ← IO.rand 0 100]
-- How do I store the result of the following two commands?
#eval n -- 54
#eval myList -- [18, 66, 41]
#eval /- What do I put here to refer to 54 or 66? -/
For n I can do something like
import Lean
open Lean
def n : IO Nat := IO.rand 0 100
#eval show CoreM Unit from do
let realizationOfN ← n
addAndCompile (.defnDecl {
name := `m, levelParams := [], type := mkConst ``Nat,
value := mkNatLit realizationOfN, hints := .regular 0, safety := .safe
})
-- m is a fixed value, so it will always be the same
#eval m -- 54
#eval m -- 54
but this is not easy to do for lists. (I managed to do the same thing by using toExpr, but this seems very inefficient). And in any case, I want to be storing the actual value, not some Expr which represents it.
My mental model is that the Lean environment is basically a giant hashmap of names to Exprs + a bunch of environment extensions. So if I understand correctly, what I propose would require the realization of myList to be stored in an environment extension, not in the expression hashmap. Is this possible, and how do I do this?
My motivation for this question is just to get a better understanding of Lean as a system because I am trying to learn metaprogramming and might have to do something using this later. This is also relevant to #Computer algebra > What would interactive use of a simplifier look like?
Malvin Gattinger (Feb 23 2026 at 11:08):
Just to check - have you seen https://github.com/leanprover-community/repl before?
Niels Voss (Feb 23 2026 at 17:20):
Yes I have, but for the reasons stated above, this doesn't actually let me have persistent interactive data.
I'm essentially looking for a way to
- Create a random natural number
n : Nat(or anIO.Ref) - Define two functions
f : Nat -> Natandg : Nat -> Nat(or two functions that consume anIO.Ref) - Call
fandgon the same random natural number, in two different#evalcommands
Robin Arnez (Feb 23 2026 at 19:49):
I have a little prototype with a special #eval command:
def f (n : Nat) : Nat :=
n * n + 3
def g (n : Nat) : Nat :=
n + 7
#eval_set n : Nat ← IO.rand 3 10
#eval_set a : Nat ← pure (f n)
#eval_set b : Nat ← pure (g n)
#eval_set x : Fin (n + 1) ← do
return Fin.ofNat _ (← IO.rand 0 n)
#eval_set _a : Unit ←
IO.println s!"{n}, {a}, {b}, {x}"
Here's the prototype (which will need to be imported as a separate file):
Prototype for #eval_set
Niels Voss (Feb 24 2026 at 07:46):
Woah, this is pretty cool. I'm having a bit of difficulty understanding what it actually does though. Is my understanding correct that:
- It registers an environment extension, which contains a list of definitions and associated values. (Don't we want this to only contain cdecls and not ldecls?) I'm not familiar with the
initializecommand; do you know where it is documented? - It uses
withExistingLocalDeclsto add the types of everything registered previously using#eval_setto the local context. Then it elaborates the type and term within this context. - It then abstracts away all the custom variables, so e.g. in this example
abecomesfun n => pure (f n)of typeforall n, Nat, andbbecomesfun n a => pure (g n)of typeforall n a, Nat. fun n => pure (f n)is then replaced with(fun n => pure (f n)) (getNthDataValue data 0)andfun n a => pure (g n)is replaced with(fun n a => pure (g n)) (getNthDataValue data 0) (getNthDataValue data 1), wheredatais a free variable of typeArray DataEntry. During this process, one level of forall is also peeled off of the type and substituted like a beta-reduction.- A new unsafe auxillary declaration is created and compiled, with e.g. value
fun data : Array DataEntry => (fun n => pure (f n)) (getNthDataValue data 0) - This is evaluated somehow? I don't really understand the
let resline. - Somehow the new value is appended to the environment extension?
Chris Henson (Feb 24 2026 at 09:39):
The reference manual has some docs about initialization.
Robin Arnez (Feb 24 2026 at 10:03):
Yes, pretty much.
For 1.: Yes, we only want cdecls but LocalDecl is the most convenient way to store them.
For 3.: Of type forall n, TermElabM Nat but otherwise yes
For 6.: We end up with a declaration Array DataEntry → TermElabM ... after all these steps. Using the interpreter we can evaluate this into an actual function Array DataEntry → TermElabM .... The ... bothers us here but we can sneakily cast into NonScalar without anyone noticing!
For 7.: After we evaluate the constant and run the code with res dataEntries we end up with the result of the computation in val (here as a NonScalar but that's just a placeholder for "any type"). Then we create our cdecl and add it together with the value to the list of entries in the environment extension.
Last updated: Feb 28 2026 at 14:05 UTC