Monday, June 25, 2018

Open Sourcing NonEmpty

Point-Free (via Ole Begemann):

We believe that a compiler proven non-empty type is incredibly important for every developer to have at their disposal, and so that’s why today we are open sourcing our NonEmpty library.

[…]

The core of the library is just a single generic type, NonEmpty<C>, which allows you to transform any collection type C into a non-empty version of itself. The majority of the code in the library consists of conformances on NonEmpty to make it act as much like the collection that it wraps.

[…]

We encourage the reader to start looking critically at their own application code, their library API’s and their interactions with other API’s to see where non-empty types might be appropriate. By pushing the non-emptiness requirement to the type level you get to enforce this invariant in a single place rather than sprinkle if’s and guard’s into your code.

Previously: Why Dependent Types Matter.

Update (2018-06-26): Ole Begemann:

Important caveat: because NonEmpty stores its first element separate from the rest, it doesn’t necessarily capture all of the underlying collection’s semantics. E.g. NonEmpty<Set<Int>> doesn’t guarantee that all elements are unique unless you take special precautions.

3 Comments RSS · Twitter

> Important caveat: because NonEmpty stores its first element separate from the rest, it doesn’t necessarily capture all of the underlying collection’s semantics. E.g. NonEmpty<Set> doesn’t guarantee that all elements are unique unless you take special precautions.

I like the concept of NonEmpty - but it's these kind of edge cases which make using a wrapper like this dangerous. In fact this seems like a bit of a show-stopper to me.

When writing a function which should return a non-empty collection I would usually use an optional return value to indicate "no result". This is idiomatic and easily understood by clients of the code. Admittedly you don't get the compile time enforcement that NonEmpty provides. But is the added complexity, in terms of reading & understanding the code and handling edge-cases, worth it? I'm not sure. It's always a tradeoff when using 3rd party wrapper. like NonEmpty.

@JohnB: “because NonEmpty stores its first element separate from the rest”

Aside from that obvious smelliness (an immediate “reject” for me too), it completely fails to solve it for the general case (as if Swift doesn’t suffer enough special-case bloat already). Realistically, these sort of constraints come down to a combination of compile-time _and_ run-time checks, and that’s okay; what’s most valuable is they’re done in a totally consistent, automated way, where it matters most: the interfaces. e.g. Compare Eiffel’s Design by Contract.

But there’s no point trying to do any of this in Swift, because its type system, like the language, is just a poor dumb bag of spanners, completely non-extensible as much for lack of imagination as by design. That its own designers refuse to bootstrap it in itself says it all. The more I use it, the more I’m seriously tempted to fall back to C; irresponsible cowboy hackathon it may be, but at least it doesn’t bend over backwards to block you from saying what you need to say.

Poor dumb Algol-y language designers; they’re just so incredibly proud of the holes they dig.

It's important to click through that caveat! The library itself tries to provide this guarantee and handle the edge cases _for_ you, it's just not something the Swift compiler can guarantee. As a consumer you can use NonEmpty and the Set and Dictionary implementations will consider duplicate elements/keys.

Leave a Comment