Zulip Chat Archive
Stream: batteries
Topic: Generalize `Fin.fold`?
Quang Dao (Dec 01 2024 at 15:15):
Lean currently defines docs#Fin.foldl and docs#Fin.foldr as special cases of the most general pattern possible. Something like:
def foldl' {n : ℕ} {α : Fin (n + 1) → Sort*} {β : Fin n → Sort*}
(f : ∀ i, α i.castSucc → β i → α i.succ) (init : α 0)
(v : (i : Fin n) → β i) : α (Fin.last n) := by
induction n with
| zero => exact init
| succ n ih =>
exact @ih (α ∘ Fin.succ) (β ∘ Fin.succ) (fun i a => f i.succ a)
(f 0 init (v ⟨0, by omega⟩)) (fun i => v i.succ)
One can recover docs#Fin.foldl as the special case where α
is constant, β := Fin n
, and v = id
.
I happen to need this generalization for my project. Is there any wider interest in replacing Fin.foldl
with this general version? For type-checking convenience, one could then recover the current version as a non-dependent case (like Fin.append
and Fin.addCases
).
I don't see this used anywhere in Mathlib and only in Batteries, hence the choice of channel.
François G. Dorais (Dec 01 2024 at 19:03):
Is there an issue with using Sigma
(or PSigma
) instead of a dependent type?
Edward van de Meent (Dec 01 2024 at 19:07):
from the looks of it, the fold you're describing is not a fold over Fin n
but a fold over (i:Fin n) -> b i
.
François G. Dorais (Dec 01 2024 at 19:43):
Actually, the β
is easily eliminated with fv : (i : Fin n) → α i.castSucc → α i.succ
defined by fv i x := f i x (v i)
.
François G. Dorais (Dec 01 2024 at 19:47):
In other words, this is sufficiently general for the OP's purposes:
def foldl' {n : Nat} {α : Fin (n + 1) → Sort _}
(f : ∀ i, α i.castSucc → α i.succ) (init : α 0) : α (Fin.last n)
François G. Dorais (Dec 01 2024 at 19:52):
The main issue, in my opinion, is whether replacing the dependent type α : Fin (n + 1) → Sort _
by a sigma type (i : Fin (n + 1)) × α i
is a burden for the OP's use case.
Edward van de Meent (Dec 01 2024 at 20:09):
i imagine having OP's fold allows for compatibility with stuff like mathlibs' docs#Matrix.vecCons ecosystem
Edward van de Meent (Dec 01 2024 at 20:10):
btw, a sigma and pi type are not the same at all
Edward van de Meent (Dec 01 2024 at 20:13):
so i don't get what you want to replace with (i : Fin n) × β i
or (i : Fin (n + 1)) × α i
Quang Dao (Dec 01 2024 at 20:52):
I'm not sure what the solution would look like using Sigma types. For my use case it's more convenient to think of objects to fold over as tuples (i : Fin n) → α i
.
François G. Dorais (Dec 01 2024 at 21:32):
I'm hinting at the usual currying trick.
I'm thinking how I would implement foldl'
. I would define f' : (i : Fin (n + 1)) × α i → (i : Fin (n + 1)) × α i
as f' ⟨i, x⟩ := ⟨i+1, f i x⟩
when i < n
(and rolling back to ⟨0, init⟩
otherwise, say). Then iterate n
times starting with ⟨0, init⟩
. Perhaps using Nat.fold
or with a tailored tail-recursive loop.
François G. Dorais (Dec 01 2024 at 21:33):
I'm wondering if this alternative is easily implementable by the OP or whether we really need a library function to encapsulate this.
Eric Wieser (Dec 01 2024 at 22:06):
François G. Dorais said:
In other words, this is sufficiently general for the OP's purposes:
def foldl' {n : Nat} {α : Fin (n + 1) → Sort _} (f : ∀ i, α i.castSucc → α i.succ) (init : α 0) : α (Fin.last n)
What happens if we just replace docs#Fin.foldl with this?
Quang Dao (Dec 01 2024 at 22:10):
I think docs#Fin.foldl has a nice tail-recursive implementation right now. I'm trying to generalize that, but the final function may be less nice.
Also, note that Fin.foldl'
is highly similar to Fin.hIterate
, so that's where I'm trying to port the tail-recursive definition from. It's not similar enough to define the former in terms of the latter though (lots of type casting involved).
Quang Dao (Dec 01 2024 at 22:31):
Not very nice right now, but here's the adaptation:
import Mathlib
def foldCasesFrom {n} (P : Fin (n + 1) → Sort*)
(f : ∀ (i : Fin n), P i.castSucc → P i.succ)
(i : Fin (n + 1)) (a : P i) : P (last n) :=
if h : i ≠ last n then
haveI this : P (i + 1) := by simpa [succ_castPred_eq_add_one] using (f (i.castPred h) a)
foldCasesFrom P f (i+1) this
else
_root_.cast (congrArg P (not_not.mp h)) a
termination_by n - i
decreasing_by
have h0 : i < last n := lt_last_iff_ne_last.mpr h
have h1 : i.val < n := by omega
have h2 : (i + 1).val = i.val + 1 := by simp [val_add, h1]
rw [h2]
exact Nat.sub_add_lt_sub h1 (by omega)
def foldCases {n} (P : Fin (n + 1) → Sort*)
(f : ∀(i : Fin n), P i.castSucc → P i.succ)
(init : P 0) : P (last n) :=
foldCasesFrom P f 0 init
François G. Dorais (Dec 01 2024 at 22:40):
Eric Wieser said:
François G. Dorais said:
In other words, this is sufficiently general for the OP's purposes:
def foldl' {n : Nat} {α : Fin (n + 1) → Sort _} (f : ∀ i, α i.castSucc → α i.succ) (init : α 0) : α (Fin.last n)
What happens if we just replace docs#Fin.foldl with this?
It's not a drop-in replacement. It would have to be Fin.depFoldl
or something.
François G. Dorais (Dec 01 2024 at 22:45):
@Quang Dao Please make a draft PR to Batteries and we will work together on refining the implementation.
Quang Dao (Dec 02 2024 at 01:49):
It is now batteries#1071.
Kim Morrison (Dec 02 2024 at 02:31):
I've left some comments.
François G. Dorais (Dec 06 2024 at 00:04):
@Quang Dao I didn't think about this while reviewing batteries#1071 when you said you needed dfold{l/r}
to use Sort _
and not Type _
: is there a reason why you couldn't use Fin.induction
, for example, to handle the Prop
case in your application? I'm asking because there is no reason for induction principles to be efficient and we would save a bunch of code by defining dfoldl{l/r}
in terms of their monadic versions.
Quang Dao (Dec 06 2024 at 21:16):
Yes, I think you're right. I haven't gotten to the part where I need to use Sort _
yet, but you can go ahead and make the change.
François G. Dorais (Dec 06 2024 at 23:13):
Great! If you have time, I would appreciate your review for batteries#1074
Last updated: May 02 2025 at 03:31 UTC