Zulip Chat Archive

Stream: lean4

Topic: Extracting entries from a literal list


Bhavik Mehta (Mar 29 2025 at 21:57):

I have an expression of type List Nat, and I want to get the underlying actual List Nat, failing if the expression is not a literal list of literal naturals. What I have so far works iff the list has <= 32 elements:

import Lean

open Lean

def myList : List Nat :=
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0,
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

#eval myList.length

open Elab Command

def getEntries (e : Expr) : CommandElabM (List Nat) := do
  let some (ty, entries) := e.listLit? | throwError "not a list"
  unless ty == (mkConst `Nat []) do throwError "expected List ℕ but got List {ty}"
  entries.mapM fun e  do
  let some i := e.nat? | throwError "expected natural literal but got {e}"
  pure i

elab "test" n:ident : command => do
  let env  getEnv
  let some valuesC := env.find? n.getId | throwError "declaration {n} not found"
  let some valuesE := valuesC.value? | throwError "declaration {n} not defined"
  let l  getEntries valuesE
  logInfo m!"values: {l}"

test myList

But because of docs#«term%[_|_]», this doesn't work if the list has at least 33 elements. (The implementation seems to cut off at 64, not sure where the discrepancy here is...).
Is there a nice way I can make this work through that notation? Note I need to be careful reducing the expression, since I want to fail if the entries are not natural literals. But maybe there's a particular choice of reduction I can do that's safe?

Bhavik Mehta (Mar 29 2025 at 22:05):

Ah, doing

let some (ty, entries) := ( liftTermElabM (Meta.zetaReduce e)).listLit? | throwError "not a list"

seems to work. (I'm reminded of why making MWEs helps so much, when writing the last question of the last message I realised that picking the right reduction is what I actually needed to do, and that was the key I missed!)

Eric Wieser (Mar 29 2025 at 23:34):

A very different approach would be to reimeplement docs#Lean.Expr.listLit? with reducible-defeq matching (possibly with Qq), otherwise you won't see through abbrev

Bhavik Mehta (Mar 29 2025 at 23:36):

Eric Wieser said:

A very different approach would be to reimeplement docs#Expr.listLit? with reducible-defeq matching (possibly with Qq), otherwise you won't see through abbrev

I think that would go too far, since it would permit 1+0 as an entry, right? If I'm understanding correctly, 1+0 is reducible-defeq to a natural lit

Eric Wieser (Mar 29 2025 at 23:37):

I mean the list constructor matching not the nat constructor

Eric Wieser (Mar 29 2025 at 23:38):

Either way, I think this should be a bug report as Expr.listLit?

Bhavik Mehta (Mar 29 2025 at 23:39):

Oh, okay! I'm happy to make this bug report, but my example above feels a little silly since I'm querying the environment to find the expression - can you point me to a slightly more sensible way of illustrating the problem?

Eric Wieser (Mar 29 2025 at 23:41):

import Lean
import Qq
open Qq

-- some _
#eval Lean.Expr.listLit? q([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] : List Nat)
-- none
#eval Lean.Expr.listLit? q([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] : List Nat)

Eric Wieser (Mar 29 2025 at 23:41):

But Qq doesn't exist in lean core of course, so I think your approach is what a unit test there would have to use

Eric Wieser (Mar 29 2025 at 23:43):

Oh, actually there is a clean enough way...

Eric Wieser (Mar 29 2025 at 23:44):

import Lean

elab "#test" t:term : command => do
  let e  Lean.Elab.Command.liftTermElabM do
    Lean.Elab.Term.elabTerm t none
  if let .none := e.listLit? then
    throwError "Not a list literal"

#test [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -- ok
#test [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] -- fails
#test (by_elab
  return Lean.toExpr
      [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) -- ok

Bhavik Mehta (Mar 29 2025 at 23:46):

Thank you!

Mario Carneiro (Mar 29 2025 at 23:49):

Bhavik Mehta said:

But because of docs#«term%[_|_]», this doesn't work if the list has at least 33 elements. (The implementation seems to cut off at 64, not sure where the discrepancy here is...).

The discrepancy is because it's counting the length of the syntax array, which alternates between elements and commas

Eric Wieser (Mar 29 2025 at 23:53):

Edited above; interestingly the result of toExpr on list returns something that is considered a literal, even though an actual literal is not considered a literal

Bhavik Mehta (Mar 29 2025 at 23:56):

That's because the toExpr means the macro_rules (which is the one making the let expressions) don't get used, right?

Bhavik Mehta (Mar 29 2025 at 23:56):

lean4#7730

Eric Wieser (Mar 29 2025 at 23:59):

More precisely its because the ToExpr instance isn't following the same rules that the macro is

Eric Wieser (Mar 30 2025 at 00:01):

Maybe this is working as intended though, as listLit? does seem to agree with the infoview

Bhavik Mehta (Mar 30 2025 at 00:02):

Do you mean getListLit?, I can't find anything called toListLit?

Eric Wieser (Mar 31 2025 at 11:32):

That was a typo, I hadn't even realized the identical getListLit? existed


Last updated: May 02 2025 at 03:31 UTC