Stream: beginners

Topic: User-land Iter transformations


view this post on Zulip António Capela (Aug 31 2026 at 22:53):

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

view this post on Zulip Anton (Sep 01 2026 at 16:55):

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.

view this post on Zulip Anton (Sep 01 2026 at 16:56):

Shall we add Iter.with_index to the builtins @Richard Feldman?

view this post on Zulip Richard Feldman (Sep 01 2026 at 16:58):

sure!

view this post on Zulip Anton (Sep 01 2026 at 17:48):

I will make an issue to investigate this in depth.

#11047

view this post on Zulip Romain Lepert (Sep 01 2026 at 20:02):

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

view this post on Zulip Aurélien Geron (Sep 01 2026 at 21:21):

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