-
Notifications
You must be signed in to change notification settings - Fork 454
wrapAsNonEmpty
functions
#3611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
wrapAsNonEmpty
functions
#3611
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth including a @DelicateApi
-style annotation for these to indicate the unsafe nature (especially given the names don't indicate they're potentially "unsafe" - footguns should ideally be hard to use accidentally)
Side note - any reason not to make the public NonEmptyList
constructor take (E, Iterable<E>)
rather than (E, List<E>)
?
arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/NonEmptyList.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe an immutable-collections integration module next?
Kover Report
|
I've added special variants which require opt-in for mutable versions.
We wanted the constructor to be minimally expensive. With an |
I've actually been thinking about this, but I could not find anything specific that we can bring to the table in this case. With the new APIs in this PR you'll be able to do: persistentListOf(1, 2).wrapAsNotEmptyListOrNull() and use it as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice🙏.
I think the unsafe nature extends to any
Makes sense, at least when it wasn't a value class. Now we end up copying from tail as a collection anyway with |
If we ended up with all |
There's actually a design which would allow us to have nicer integration with any value class NonEmpty<A, C : Collection<A>> internal constructor(
public val elements: C
): Collection<A> by elements
typealias NonEmptyList<A> = NonEmpty<A, List<A>>
typealias NonEmptySet<A> = NonEmpty<A, Set<A>>
typealias NonEmptyPersistentList<A> = NonEmpty<A, PersistentList<A>>
fun <A, C: Collection<A>> C.wrapAsNonEmpty(): NonEmpty<A, C> {
require(isNotEmpty())
return NonEmpty(this)
} With this approach you can always use The other problem with that approach is that value class NonEmpty<A, C : List<A>>(val elements: C): List<A> by elements
typealias NonEmptyList<A> = NonEmpty<A, List<A>>
typealias NonEmptyPersistentList<A> = NonEmpty<A, PersistentList<A>> Maybe we can introduce it as a new (experimental) module? |
It's not too bad with |
I think my main concern is we have no way to control if the underlying Unwrapping shouldn't really be needed other than for optimisations such as PersistentList "mutations" referencing the original data structure - which is why pushing it behind an OptIn to mitigate casual use and providing an integration module that knows to convert a I think allowing break-glass access to the underlying list is enough for these cases, and carrying the underlying list type via the A (poor) example where the OptIn only for fun handle(messages: List<String>) {
dispatch(messages.wrapAsNonEmptyListOrNull() ?: return)
}
val collected = mutableListOf<String>("hello")
handle(collected)
collected.clear()
|
The current behavior of the
toNonEmpty
functions is to create a copy of the givenIterable
. This is the safest option, but sometimes brings additional useless copying. For example, if you just want to signal that a given immutableList
is non-empty, you don't need such copy.This PR introduces a family of
wrapAsNonEmpty
functions which avoid that copy. As the documentation states, the developer is then responsible from keeping the non-emptiness invariant. For example, if you wrap aMutableList
and thenclearAll
, you'll end up withNonEmptyList
with no elements. Unfortunately, this is a trade-off we need to make here, since the fact thatMutableList
extendsList
means that we cannot prohibit this scenario.