Open joffrey-bion opened 2 years ago
Let's dissect it. I like the idea to use the complete
verb to focus attention on the key purpose of this function. However, the After
suffix might be somewhat confusing, since all the other xxxAfter
functions (substringAfter
and replaceAfter
) take the element value as a parameter as opposed to the predicate. It is not a showstopper, since that xxxAfter
suffix is used only in the strings and has not been used in collections, but still an annoying inconsistency. From the standpoint of the English language, After
looks like the best choice, indeed. I cannot find any better way to express this intent.
However, Kotlin stdlib naming does not always clearly follow the predicate/element distinction and sometimes the same naming pattern is overloaded for both. There is a precedent that might yield a name that is more aligned with the rest of the Kotlin collections. Notice that there is indexOf(element)
, but indexOfFirst(predicate)
and indexOfLast(predicate)
. Even for strings, xxxFirst
and xxxLast
functions always take a predicate as a parameter.
Following this precedent, I can suggest to consider an option of using a longer, but more explicit name for this function: completeAfterFirst
@elizarov thanks for the prompt consideration.
This is a very fair point, I hadn't payed enough attention to the "element vs predicate" distinction here. We could even consider having completeAfter(element)
and completeAfterFirst(predicate)
, so we can really complete on a sentinel value too. Although as you mentioned in the transformWhile
issue, you probably want to avoid having too many operators.
The downside of completeAfter[First]
is that it cannot be backported to collections, as opposed to dropAfter[First]
. But that might not be too much of a problem, given the main use case motivating this operator is making flows finite, and that would work at least for sequences as well.
Let's look at how transformWhile
is used today (https://grep.app/search?q=transformWhile%20%7B).
Here are the usages where transformWhile
is used exactly for completeAfter
and the predicate is negated:
Here are the usages where transformWhile
is used for completeAfter
and the predicate is stated positively:
The examples where transformWhile
is used for some other purpose:
transform
.permissionStateFlow.takeWhile { isActiveStateFlow.value }
.takeWhile { it.hasValue() }.onCompletion { emit(startedEvent) }
.These stats (although for a very small n
) suggest that introducing a shorthand to transformWhile { emit(it); !predicate(it) }
and naming it properly would reduce the number of boolean negations in the user code, as well as the number of errors: some people did name their copies takeUntil
or something else that suggests that the element on which everything stops would not be included, making the name not quite right.
I have noticed the answer on https://github.com/Kotlin/kotlinx.coroutines/issues/2042 and read the concerns expressed in https://github.com/Kotlin/kotlinx.coroutines/issues/2065 which is presented as a solution to this problem.
While I concur with the naming challenges and the fact that we cannot use the name
takeUntil
in Kotlin for this purpose, I do believe the use case of ensuring a flow is finite / completes is quite important and prevalent especially since the addition ofStateFlow
andSharedFlow
, and is worth a dedicated operator.transformWhile
indeed does the job, but it really doesn't convey the message cleanly, which after all is why I personally love Kotlin's collection and flow operators.takeWhile
,drop(n)
, etc. really read well, buttransformWhile { emit(it); it.isNotTheLastOne() }
honestly doesn't. In particular it's unclear whether the element will be emitted if we return false (of course it is, since we emit before reaching the condition, but what I mean is that it's not obvious). The point is that the goal of this snippet doesn't pop right away at first glance. The code seems too complex to express a single idea - we're doing multiple things.Since the use case at hand is about ensuring flow completion on a certain condition, I would like to suggest
completeAfter { it.isTheLastOne() }
as a possible name for this function. The name would suggest that we make the flow "complete" when a given condition is met, while still suggesting the element matching the condition will be included.I understand that it doesn't follow the
take
convention, but it's actually not what we want to express here. The focus is not on the fact that all elements match a condition and we want to take them. The focus is about completing the flow after we see a particular sentinel value.Another possible option would be
dropAfter
, which would match other helpers likesubstringAfter
, and follow thedrop
naming convention for cutting off the end of a flow or collection. It would be slightly more general-sounding thancompleteAfter
, but with less emphasis on the "completion" aspect - making the flow finite.If such an operator is not included in the coroutines lib, I believe a lot of projects will add their own and we'll end up with no common name for this operation, which would be sad for the ecosystem IMO.