I was looking to implement a with_index function for iterators with the signature:
with_index : Iter(a) -> Iter((U64, a))
As far as I can tell, the way to do this would be to use Iter.custom to create a new iterator, and then advance the existing iterator using Iter.next
custom : state, [Known(U64), Unknown], (state -> Try((item, state), [NoMore])) -> Iter(item)
next : Iter(item) -> [One({ item : item, rest : Iter(item) }), Skip({ rest : Iter(item) }), Done]
I didn't understand why next's signature was different from custom ([One(_), Skip(_), Done] vs Try(_, [NoMore])), but after a conversation with claude I now have some idea for why that is. But it raised a question, my implementation of with_index does internal looping, does that mean it is going to have worse performance than what could be done in the standard library?
Here is how I implemented with_index:
with_index : Iter(a) -> Iter((U64, a))
with_index = |source|
Iter.custom(
(0, source),
Iter.size_hint(source),
|(i, src)| advance_indexed(i, src),
)
advance_indexed : U64, Iter(a) -> Try(((U64, a), (U64, Iter(a))), [NoMore])
advance_indexed = |i, src|
match Iter.next(src) {
Done => Err(NoMore)
Skip({ rest }) => advance_indexed(i, rest)
One({ item, rest }) => Ok(((i, item), (i + 1, rest)))
}
Thank you for your patience, roc is awesome
my implementation of with_index does internal looping, does that mean it is going to have worse performance than what could be done in the standard library?
Yes although currently the looping does not seem to be the problem, it's currently ~50x slower because of per-step allocation/refcount churn. I will make an issue to investigate this in depth.
Shall we add Iter.with_index to the builtins @Richard Feldman?
sure!
I will make an issue to investigate this in depth.
I believe Iter.enumerate would be more conventional with the exception of Scala/Elixir/Kotlin/Ruby
| Language | Idiom / API | Result |
|---|---|---|
| Python | enumerate(xs) |
(index, value) |
| Rust | xs.iter().enumerate() |
(index, value) |
| Swift | xs.enumerated() |
(offset, element) |
| C# / LINQ | xs.Select((x, i) => ...) |
selector receives value + index |
| JavaScript | xs.entries() |
[index, value] |
| Go | for i, x := range xs |
index + value, but language syntax |
| C++ ranges | views::enumerate C++23 |
tuple-like (index, value) |
| Haskell | zip [0..] xs |
(index, value) |
| Scala | xs.zipWithIndex |
(value, index) |
| Elixir | Enum.with_index(xs) |
{value, index} |
| Kotlin | xs.withIndex() |
IndexedValue(index, value) |
| Ruby | xs.each_with_index |
value + index |
| Dart | no core enumerate; commonly indexed extensions/packages |
varies |
| Julia | enumerate(xs) |
(index, value), 1-based |
| Zig | typically for (xs, 0..) \|x, i\| |
language syntax |
| Gleam | list.index_map / iterator equivalents depending on abstraction |
index supplied to function |
Coming from Python, I like enumerate, but since we have List.map_with_index, List.fold_with_index, and several with_... functions, I think I prefer with_index for consistency. Also, enumerate is only used in one more language than with_index.
Last updated: Sep 03 2026 at 15:16 UTC