Zulip Chat Archive

Stream: lean4

Topic: Performance of Lists


Yves Jäckle (Jul 15 2024 at 14:12):

Hey!
In get a stack overflow for ( including on the web editor).

set_option maxRecDepth 9000000 in
#eval (List.range 175561).map (· +1)

I'm new to programming in general.
Is this because the code generated for lists is inefficient ? (though Array yields the same problem).
Is there a general way I allow lean to handle large computations without changing the implementation ?
Is my laptop just bad ?

Julian Berman (Jul 15 2024 at 14:24):

I'm not fully sure but it certainly seems like part of the answer is you're asking to print that giant thing. Are you really trying to do that, or just do some continued computation?

Julian Berman (Jul 15 2024 at 14:24):

E.g., with Array, this is fine:

def f := Array.range 175561
def g := f.map (· + 1)

#check g

#eval g[37]!

Eric Wieser (Jul 15 2024 at 14:28):

Julian Berman said:

I'm not fully sure but it certainly seems like part of the answer is you're asking to print that giant thing.

Worse, pretty-print as a Format object

Eric Wieser (Jul 15 2024 at 14:29):

This works fine:

set_option maxRecDepth 9000000 in
#eval toString <| (List.range 175561).map (· +1)

Kim Morrison (Jul 15 2024 at 14:31):

And even in this case you are benchmarking toString presumably! Throw in a Nat.sum before worrying about long lists.

Julian Berman (Jul 15 2024 at 14:32):

Unrelated but because I noticed it, does set_option maxRecDepth 0 PANIC for either/any of you?

Julian Berman (Jul 15 2024 at 14:32):

It does here (in nvim and VSCode, on 4.10.0-rc2) but seems not to in the web editor.

Eric Wieser (Jul 15 2024 at 14:33):

0 means "no stack!! only execute", as opposed to "as much stack as you like"

Julian Berman (Jul 15 2024 at 14:33):

Right I understand it's not what I wanted anyhow, but the PANIC was presumably unexpected

Yves Jäckle (Jul 15 2024 at 14:43):

Amazing, thanks :+1:

Notification Bot (Jul 15 2024 at 14:43):

Yves Jäckle has marked this topic as resolved.

Gareth Ma (Jul 17 2024 at 10:27):

Hi, this topic is quite interesting and I want to learn more about it. I looked at the implementation of Repr which I assume is where the Format comes from. Here are the relevant parts:

def Format.joinSep {α : Type*} [ToFormat α] : List α  Format  Format
  | [],    _   => nil
  | [a],   _   => format a
  | a::as, sep => as.foldl (· ++ sep ++ format ·) (format a)

protected def List.repr {α : Type*} [Repr α] (a : List α) : Format :=
  let _ : ToFormat α := repr
  match a with
  | [] => "[]"
  | as => Format.bracket "[" (Format.joinSep as ("," ++ Format.line)) "]"

From what I can tell, List.repr is not recursive (as in recurse to itself) and just calls Format.joinSep, and neither is Format.joinSep which calls List.foldl. So the stack overflow occurs at List.foldl. But... List.sum also calls List.foldl??

Notification Bot (Jul 17 2024 at 10:27):

Gareth Ma has marked this topic as unresolved.

Gareth Ma (Jul 17 2024 at 10:29):

And this doesn't stack overflow

import Mathlib.Tactic
open Std Format
def add_up : List Nat  Nat
  | [] => 0
  | a::as => as.foldl (· + ·) a

def add_up_list (a : List Nat) : Format :=
  match a with
  | [] => "[]"
  | as => Format.bracket "[" (toString <| add_up as) "]"

set_option linter.setOption false
set_option trace.profiler true

#eval add_up_list (List.range 50000)

So does this mean String is the place overflowing? Or am I missing something stupid

Mario Carneiro (Jul 17 2024 at 11:34):

you are secretly calling Format.pretty there because you are passing a Format to #eval to show it. Format.pretty is bad at deep recursion if you hand it a deeply nested Format object

Gareth Ma (Jul 17 2024 at 12:56):

Ohh I see now, thanks! I think I also mixed up Format with String (due to the ++), so I didn't realise it's a nested tree instead of a string


Last updated: May 02 2025 at 03:31 UTC