Should `NonEmptyList`s be assumed immutable? Or is...
# arrow
y
Should `NonEmptyList`s be assumed immutable? Or is the only assumption that the list is never empty? I ask because the `wrap`ing APIs say that you need to ensure the list stays nonempty, but it doesn't clearly say whether you can mutate the underlying list or not
a
It should only assume it's not empty (and stays not empty). Wrapping a mutable list should be perfectly OK
thank you color 1
u
For consistency, wouldn't that be a
NonEmptyMutableList
? A Kotlin
List
is immutable, so I would expect a
NonEmptyList
to be the same (immutability is the default).
y
Nope! A Kotlin
List
is not immutable, it's read only. Case in point:
val list: List<Int> = mutableListOf()
u
Sorry for being sloppy – that's what I meant. In my opinion, The NonEmpty wrapper should adhere to the Kotlin naming convention for collections.
y
It does, though. All that we're saying is that the following is completely valid:
Copy code
val mutable = mutableListOf(1)
val nonEmpty = mutable.wrapAsNonEmptyListOrThrow()
println(nonEmpty) // [1]
mutable.add(2)
println(nonEmpty) // [1, 2]
(note that
wrapAsNonEmptyListOrThrow
has an opt-in because you must ensure that the list you're wrapping never becomes empty)
u
As a simple user of the arrow libraries, that looks disturbing. You put a guarantee into the type that the type cannot deliver.
y
I didn't, the user who calls
wrapAsNonEmptyListOrThrow
and opts-in (to the aptly named
PotentiallyUnsafeNonEmptyOperation
) does. These functions are provided firstly for performance, but it's also perfectly legitimate to wrap a mutable list, as long as you, the person who explicitly opts-in, decides that your list will never become empty in the future
FWIW, I'm working on a set of safe (so no opt-in) APIs that'll allow for
NonEmptyMutableList
to be a thing, and truly ensuring that the list cannot ever get empty
👍 1
u
hmmm, as a user of the non-mutable `List`` I would not like to be bothered by an opt-in that doesn't apply to me.
y
If none of your code uses that opt-in, you're fine. The regular
toNonEmptyList()
functions do a defensive copy.
👍 1
Let me make it very clear, for future readers as well: If you never opt-in to
PotentiallyUnsafeNonEmptyOperation
, all your
NonEmptyList
-related code will never encounter empty lists (modulo bugs, of course). If you opt-in to
PotentiallyUnsafeNonEmptyOperation
, it's your responsibility to make sure that the list passed to
wrapAsNonEmptyXXX
never goes empty. If you're a library, be dang sure that it doesn't, or your users will be very unhappy! Within Arrow itself, we use that method sparingly, and (AFAIK) never to wrap a mutable list (I just remembered one exception, but it's in experimental Accumulate code, and it only calls
add
and
addAll
on the mutable list, so it's safe) In fact, using the
wrapAsNonEmptyXXX
methods with a guaranteed-immutable list as the receiver is completely safe (of course, the
orThrow
variants will throw in such cases, which is to be expected).
👍 1
FWIW, here's the PR that adds
buildNonEmptyList/Set
👍 1