Zulip Chat Archive

Stream: lean4

Topic: For loop over Fin range


James Sully (Feb 28 2024 at 05:58):

Is it possible to loop over a range of Fin? I have:

import Std
open Std

def twoSum (nums : Array Int) (target : Int) : Option (Fin nums.size × Fin nums.size) := do
  let mut seen : HashMap Int (Fin nums.size) := HashMap.empty

  for x in nums, index_ in [0:nums.size] do
    let index : Fin nums.size := index_, sorry
    let complement := target - x
    match seen.find? complement with
    | some complementIndex => return (complementIndex, index)
    | none => seen := seen.insert x index

  none

#eval twoSum #[2, 7, 11, 15] 9 == some (⟨0, by simp⟩, 1, by simp⟩)
#eval twoSum #[3, 2, 4] 6 == some (⟨1, by simp⟩, 2, by simp⟩)
#eval twoSum #[3, 3] 6 == some (⟨0, by simp⟩, 1, by simp⟩)

I'd like index_ to somehow be a Fin nums.size instead of a Nat, so that I don't have to fill in the sorry.

Thomas Murrills (Feb 28 2024 at 06:19):

(It's not what you've asked for on the nose, but you can use the syntax for ... h : index_ in [0:nums.size] do to include the hypothesis that index_ is in [0:nums.size] in the local context, which you can use to prove that sorry! You might be aware of this, but I wanted to make sure it was mentioned just in case.)

James Sully (Feb 28 2024 at 06:21):

I actually tried that, with

  for x in nums, h : index_ in [0:nums.size] do

I'm getting

 7:18-7:21: error:
the proof annotation here has not been implemented yet

In the infoview. Presumably the parallel looping syntax is incomplete

James Sully (Feb 28 2024 at 06:22):

I'm sure there's some workaround. But yeah, ofc I would prefer not to have to prove it

James Sully (Feb 28 2024 at 06:26):

Ok, having searched some more, it seems like @David Thrane Christiansen has implemented exactly what I'm wanting here
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/.E2.9C.94.20Iterating.20over.20array.20indices/near/421224993

Would be cool to see this in the standard library

James Sully (Feb 28 2024 at 06:30):

Unfortunately when I copy it in, I'm getting

 15:1-15:43: error:
Too many extra parameters bound; the function definition only has 0 extra parameters.

On the line

termination_by _ ff action => n - ff.start

Thomas Murrills (Feb 28 2024 at 06:32):

Ah, gotcha, oops! I think the conventional way to do this at the moment would be for h : index in [:nums.size] do, where we simply access nums via nums[index] and construct the Fin element in the loop. Being able to iterate through Fin n with for doesn't seem like a bad idea, though—I wonder if it exists in mathlib/std anywhere yet, in some form, at least.

James Sully (Feb 28 2024 at 06:36):

Yeah, that's what I was doing before. In this particular case it would be slightly nicer to be iterating Fin because I'm returning the indices as Fins, so I have to get the proof of index < nums.size somehow anyway.

James Sully (Feb 28 2024 at 06:48):

James Sully said:

Unfortunately when I copy it in, I'm getting

I don't really understand termination_by, but after some programming-by-permutation, changing it to termination_by n - ff.start seems to have made the typechecker happy

James Sully (Feb 28 2024 at 06:51):

the next thing is that for parallel for loops, apparently I need to implement ToStream for the thing I'm looping over

James Sully (Feb 28 2024 at 06:51):

Actually maybe I can get away without it if I commute them

James Sully (Feb 28 2024 at 06:53):

yep, that worked. Full code, using David's FinsFrom

import Std
open Std

structure FinsFrom (n : Nat) where
  start : Nat
  ok : start  n

def fins (n : Nat) : FinsFrom n := 0, by simp

def FinsFrom.forM [Monad m] (ff : FinsFrom n) (action : Fin n  m Unit) : m Unit := do
  if h : ff.start  n then pure ()
  else
    action ff.start, by omega
    forM ff.start + 1, by omega action
termination_by n - ff.start
decreasing_by simp_wf; omega

instance : ForM m (FinsFrom n) (Fin n) where
  forM := FinsFrom.forM

instance : ForIn m (FinsFrom n) (Fin n) where forIn := ForM.forIn

def twoSum (nums : Array Int) (target : Int) : Option (Fin nums.size × Fin nums.size) := do
  let mut seen : HashMap Int (Fin nums.size) := HashMap.empty

  for index in fins nums.size, x in nums do
    let complement := target - x
    match seen.find? complement with
    | some complementIndex => return (complementIndex, index)
    | none => seen := seen.insert x index

  none

#eval twoSum #[2, 7, 11, 15] 9 == some (⟨0, by simp⟩, 1, by simp⟩)
#eval twoSum #[3, 2, 4] 6 == some (⟨1, by simp⟩, 2, by simp⟩)
#eval twoSum #[3, 3] 6 == some (⟨0, by simp⟩, 1, by simp⟩)

James Sully (Feb 28 2024 at 10:05):

Proposal to overload [i:j] syntax via typeclass is relevant
https://github.com/leanprover/lean4/issues/1962

Thomas Murrills (Feb 28 2024 at 20:43):

Honestly it would be nice to be able to simply write for x in Fin n do, but the current nature of ForIn makes using types A in that position awkward, since you would need an instance of ForIn m (Type u) A.

I wonder what the best way to solve this is: you could either have some "formalizing" map inductive F (α : Type v) where | formal (plus nice syntax) to force it to fit the current shape of the instance (this is kind of a hack), or you could overload the for ... in syntax to also try to use an instance of a new class ForInType m A, for example. (Or something else?)

James Gallicchio (Feb 28 2024 at 20:59):

LeanColls lets you write for x in IndexType.univ (Fin n) do for example, using a trivial wrapper structure

James Gallicchio (Feb 28 2024 at 21:01):

you might be able to write the similar Finset.univ (Fin n) if you have Mathlib, but that is not made for computation

Thomas Murrills (Feb 28 2024 at 21:49):

Nice! I wonder if IndexType.univ could somehow be a coercion from Types to IndexTypes, and if that could be picked up by for. (Or if that would mess with other things.)

James Gallicchio (Feb 28 2024 at 22:31):

Probably doable. I'd happily accept a PR if it works :-)

James Sully (Jul 13 2024 at 04:59):

Sorry to necro, posting here for my own reference
Here's a version of david's FinsFrom with Stream and ToStream instances, which gets you the parallel looping syntax. The ForIn instance comes via instForInOfStream.

structure FinRange (n : Nat) where
  curr : Nat
  ok : curr  n

def fins
  (start : Nat)
  (to : Nat)
  (ok : start  to := by omega)
  : FinRange to :=
  { curr := start, ok }

def allFins (n : Nat) : FinRange n := fins 0 n

instance : Stream (FinRange n) (Fin n) where
  next? (fs : FinRange n) : Option (Fin n × FinRange n) :=
    if h : fs.curr < n then
      let i : Fin n := fs.curr, h
      some (i, { curr := fs.curr + 1, ok := Nat.succ_le_of_lt h })
    else
      none

instance : ToStream (FinRange n) (FinRange n) where
  toStream := id

def main : IO Unit :=
  for i in allFins 10, j in fins 10 20 do
    IO.println s!"{i.val} {j.val}"

#eval main
#synth ForIn Id (FinRange 10) (Fin 10)

Last updated: May 02 2025 at 03:31 UTC