In this previous post, I discussed Kotlin’s Iterables and Sequences, focusing on the eager versus deferred and lazy behavior of their extension functions (see this helpful this StackOverflow discussion). I came across a statement that “Collections (classes implementing Iterable) contain items, while Sequences produce items,” which resonated with me. However, I didn’t address the peculiar fact that Sequences in Kotlin aren’t Iterables (I don’t see why the Sequence<T>
interface couldn’t implement the Iterable<T>
interface – it wouldn’t interfere with utilizing the correct extension function).
In languages like Python and JavaScript (which lack an explicit Iterable class or interface), an iterable is essentially a “protocol.” If you can obtain an iterator from it, it’s iterable. Kotlin has an Iterable interface, yet some things, like Sequences, are iterable (both Iterable and Sequence interfaces have an iterator()
method) without implementing that interface. The confusion stems from saying “X is iterable,” which sounds deceptively similar to “X is (implements) an Iterable.” “X can be iterated” seems more accurate – a Sequence can be iterated but isn’t an Iterable.
Semantics aside, let’s get practical. We can derive a Sequence from an Iterable using the asSequence() extension function or the Sequence() function. This is beneficial when we want to leverage lazy extension functions for operations like mapping and filtering. Conversely, we can “materialize” a Sequence into a Collection using the toList() extension function, forcing the Sequence to generate all its items and store them within a collection. We also have the Sequence[T].asIterable() extension function at our disposal. When and why would we opt for asIterable()
over toList()
?
Imagine a scenario where we intend to apply filtering/mapping to a Sequence, and we desire these operations to be executed eagerly in one go as soon as an item is requested during iteration. However, we don’t need this to happen immediately. asIterable()
comes in handy here – it provides an object adhering to the Iterable interface but without any elements (deferring execution). When we first invoke a map/filter extension function (on Iterable), the entire original Sequence is iterated and processed eagerly in one shot. Conversely, toList()
triggers the complete iteration of the Sequence upfront, even before we can apply map/filter to process the items. I’m using deferred/immediate execution and lazy/eager evaluation as defined in this excellent post.
|
|
One final observation: The iterator()
method within Iterable or Sequence must be designated as an operator. Why? Documentation and discussions provide this explanation:
The for-loop relies on the iterator()
, next()
, and hasNext()
methods to be marked with operator
. This goes beyond operator overloading and constitutes another use case for the operator
keyword.
The crux is that specific syntax leverages these functions without a visible syntactic call. Without the operator
marking, this special syntax wouldn’t be accessible.
Keep in mind that Kotlin (similar to Python) doesn’t allow defining custom operators – we can only overload existing ones.