Zulip Chat Archive

Stream: lean4

Topic: map Array while mutating state


Scott Morrison (Oct 12 2022 at 02:03):

I want to map a monadic function over an Array, while mutating some additional state. What's the idiomatic way to do this?

def F (f : α  β  MetaM (α × γ)) (a : α) (bs : Array β) : MetaM (α × Array γ) := do
  let mut z := a
  let gs  bs.mapM (fun b => do
    let (g, a')  f z b
    z  a'
    return g)
  return (z, gs)

Fails with

`z` cannot be mutated, only variables declared using `let mut` can be mutated. If you did not intent to mutate but define `z`, consider using `let z` instead.

Scott Morrison (Oct 12 2022 at 02:19):

I know how to do it "the old way" with StateT:

def G (f : α  β  MetaM (α × γ)) : β  StateT α MetaM γ := fun b => do
  let (a, g)  f (get) b
  set a
  return g

def F (f : α  β  MetaM (α × γ)) (a : α) (bs : Array β) : MetaM (Array γ × α) := do
  StateT.run (bs.mapM (G f)) a

#eval F (fun n m => return (n+1, n*m)) 0 #[1,2,3,4] -- prints (#[0, 2, 6, 12], 4)

(I changed α × Array γ to Array γ × α for convenience here.)

Adam Topaz (Oct 12 2022 at 02:20):

Is z \l g what's intended? (z has type \a)

Adam Topaz (Oct 12 2022 at 02:21):

Anyway, I got something to work with a for loop

def F (f : α  β  MetaM (α × γ)) (a : α) (bs : Array β) : MetaM (α × Array γ) := do
  let mut z := a
  let mut gs : Array γ := #[]
  for b in bs do
    let (a',g)  f z b
    z := a'
    gs := gs.push g
  return (z, gs)

#eval F (fun n m => return (n+1, n*m)) 0 #[1,2,3,4]

Tomas Skrivan (Oct 12 2022 at 05:26):

I encountered identical problem with a similar high order function like mapM but which cannot be easily replaced with a for loop.

Is it possible for let mut to introduce the required state monad?

Mario Carneiro (Oct 12 2022 at 08:25):

let mut does introduce the required state monad. The hard part is that do is basically a closed class, the elaborator has to know all the things that are going on in the block and in particular it can't lift its state over arbitrary monadic combinators. It has specific support for bundling and unbundling its state to do the try .. catch, try .. finally and for .. in .. do combinators, which all have specific syntax in the do block.

Mario Carneiro (Oct 12 2022 at 08:26):

the usual way you will see this being done in lean 4 is what Adam wrote, just don't use mapM at all and push into an array in a loop

Mario Carneiro (Oct 12 2022 at 08:29):

it's not that much worse than what mapM does already; the main optimization it has over pushing in a loop is that the initial array is not #[] but Array.mkEmpty bs.size


Last updated: Dec 20 2023 at 11:08 UTC