`isNotEmpty` on Array

You're right in every bits of your message, Jordan! And thinking about parenthesis was one trigger for the follow-up message. In which I somewhat missed my point in one sentence, because parenthesis are the real culprit (not the confusion I talk about):

Python: if we provide English-like in operator for containment test, then we'd rather also provide the not in negated operator in order to avoid confusing explanations about expressions, operator precedence, parenthesis, etc

I think isNotEmpty could be a huge readability improvement in case of long expressions – it often makes more sense logically for the negation part to be at the end of the expression than at the start. For this reason I've seen people write .isEmpty == false or even .isEmpty.not instead.

So I'm in favor of isNotEmpty (and other similarly properties such as UIView.isVisible) but it would be a pain maintainability-wise to implement the negations of all boolean properties in a library's public interface. What about an attribute?

@inverse(isNotEmpty)
var isEmpty: Bool { ... }

This seems to be consistent with the @objc(...) attribute, and it has the additional benefit for consumers of an API that they don't have to guess from the names of two boolean properties that they're each other's inverse.

9 Likes

Thanks for bringing this up. I've been thinking about this recently, especially in the light of a large number of "trivially composable" proposals that have been brought up recently. The problem is mainly the relative weighting of the criteria we've discussed. Readability improvements and minor performance and correctness optimizations are being used as the main criteria, whereas I feel like major performance wins and really hard (but still relatively common) problems to solve correctly are being underweighted.

I think there's a real risk with the current pitches that we end up with a standard library populated with huge quantities of sugar and not enough protein. I feel like we need to refocus on key algorithms that are important and hard to write correctly, rather than minor nice-to-haves which are (sometimes) more common, but also very easily written. A standard library that is missing stable sorting and partitioning, rotation, permutation, and binary search, but does have notEmpty, id, isEven, would be pretty unfortunate.

I see this as guidance when adding something about what to call it, and not guidance as to what to add. That is, once you have decided to add an allSatisfies method, that naming guidance is how you decide to call it that and not all or contains(only:). It isn't an encouragement to add synonyms or trivially composable methods to the standard library. Those can sometimes aid readability – but they harm the usability of the library by bloating it, making it hard to use and navigate.

I think there's an important difference. The motivation for toggle was the need to duplicate long expressions (path.to.something.over.here = !path.to.something.over.here) as well as avoid the risk of typos in that long expression. Neither of these arguments materially apply to !isEmpty over notEmpty.

15 Likes

I think part of the issue is that, after toggle() was approved and the criteria for trivial changes were posted, it became very easy to come up with trivial additions that meet the criteria. Whereas the larger, meatier proposals still suffer from a lack of clear guidelines about how to even be reviewed, as well as a far larger investment in time to come up with and potentially implement in the first place. Personally, I would love having partitioning, rotation, permutation, and binary search in the standard library, in addition to many other things, but I don't have the expertise or time to implement such things. I find it hilariously ironic that Dave's "Embracing Algorithms" talk at WWDC was about algorithms not included in the standard library. But like I said, these sort of meatier changes suffer from a lack of guidance and support and require a full evolution pass for standard collection algorithms. Perhaps these things would be added more easily if they were just approved and just awaiting implementation?

1 Like

I basically agree with everything that Ben wrote.

After a lot of thought, I came down very weakly in favor of isEven and isOdd, so those are roughly my threshold. For me the qualifications for inclusion are three out of four of the following:

  1. popular demand (proxy for better discoverability than alternatives)
  2. better readability at use site than any alternative
  3. correctness hazard if users try to implement it themselves
  4. performance hazard if users try to implement it themselves

isEven and isOdd tick off 1, 2 (maybe), 3 (only isOdd), and 4 (only for bignums, and maybe not even there in the face of compiler heroics). The reason I say "maybe" for 2 is that I'm not totally convinced that we aren't better served by apis for alternating-color list layouts and checkerboards, rather than isEven and isOdd.

isNotEmpty satisfies 1 and maybe 2. It does not satisfy 3 or 4. So (from my perspective), it's just sugar, no protein.

3 Likes

I think there's a middle ground: a menu of algorithms that are approved in principal, but still need proposals to flesh out the details. Even the more straightforward algorithms have nuances in terms of implementation. For example, out-of-place rotation could return either an array or a lazily rotated wrapper, binary search leads to the question of whether you use the type system to guard against searching unsorted collections etc.

4 Likes

I do have a metric ton of guard !.isEmpty in my code.

I wonder if a better solution (while we are waiting for true non-empty collections) would be to have a property/method on Collection which converts empty collections to nil. That is, you either get back a collection which is guaranteed not to be empty or nil.

var nonEmpty : Self? {
    return self.isEmpty ? nil : self
}

Then the call site for guards would look like this:

guard let nonEmpty = myCollection.nonEmpty else {...}
1 Like

One other thing to note about algorithms not currently in the std lib: this is to some extent a result of waiting for ABI stability. Currently apps must ship with a copy of the std lib, so there is a cost to be weighed in including lesser-used but important complex algorithms, which also tend to be longer. ABI stability will allow for the library to be shipped once for all apps, reducing this particular concern.

2 Likes

Collections are not the only types that have “isEmpty”. Thus, I’d like to see something like this:

protocol Emptiable {
    var isEmpty: Bool { get }
}

extension Emptiable {
    var isInhabited: Bool {
        return isEmpty == false
    }

    var inhabitedValue: Self? {
        guard isInhabited
        else {
            return nil
        }

        return self
    }
}

For the rationale behind the naming, see:
Empty Set
Inhabited Set

SetAlgebra & Collection should conform to this protocol.
Perhaps these (and others) should as well?

ClosedRange
Data
Dictionary
Range
1 Like

What useful generic algorithms, spanning those different types, do you see being enabled by this new protocol?

2 Likes

I feel like almost nobody who wants sugar for

guard !myCollection.isEmpty { … }

is going to be happy with

guard let nonEmpty = myCollection.nonEmpty else {...}

I would also say that support for non-empty collections in the standard library is a tricky issue, because any methods that might remove an item would need to drop back to the possibly-empty form, limiting the use cases to situations where you preserve or increase the count. The benefits also seem fairly minor, mostly removing some Optional wrappers and precondition tests. The cost for these benefits is the need to maintain a whole parallel implementation for most/all generic Collection methods, preserving the distinction.

In similar situations, Swift seems to prefer a more pragmatic approach (e.g. preferring to traffic in Int, even for things like counts that can't be negative), instead enforcing these kind of constraints with preconditions.

It's not so much sugar as it is clarity around the ! symbol.

I don't think it should be implemented that way. Instead it should probably be a special set of annotations (or something similar) that get carried around with the type. I don't want to derail the discussion with implementation of non-empty collections (which won't come for a while), but I think it could be done without parallel implementations (or at least any written by humans).

It is true that anything which decreases the count would need to be possibly-empty. It is also true that anything that adds to the count will be non-empty. In my experience, the percentage of code which removes things from collections is much smaller than that which adds to them. All of those little checks and wrap/unwraps do add up.

As you might surmise, I don't have any specific generic algorithm an Emptiable protocol would enable. I find it interesting for two reasons:

Clarity: I find "isInhabited" to be much clearer and easier to reason about than "isEmpty == false" & "!isEmpty" (& even "isNonEmpty").

Consistency: Across a large code base utilizing a wide variety of types that implement "isEmpty", consistently having "isInhabited" & "inhabitedValue" available is invaluable.

I find the following concise & clear compared to other spellings:

guard let inhabitedStrings = strings.filter { $0.isInhabited }.inhabitedValue
else {
    return
}

Implementing "isNonEmpty" for clarity in Collection misses the opportunity to clarify all the other types that are emptiable.

1 Like

I too like isInhabited better than isNotEmpty. I find getting rid of the negation makes things clearer than just moving the negation around.

It doesn't necessarily have to come with a protocol though.

I've never heard anyone refer to a non-empty array as an inhabited array, is that something people do? Or is it mostly an attempt to get rid of the negation? I generally agree that negations in boolean property names should be avoided because you may end up with double negatives, but that will never happen in the case of isNotEmpty because you'd just use isEmpty if you want to negate it.

2 Likes

I weakly favour the addition of isNotEmpty, somewhat more strongly that of isEven / isOdd and rather more strongly isMultiple(of:). But I'll agree that the amount of discussion for such minor changes is not proportional to the actual utility derived, especially compared with bigger topics. It's a classic case of bikeshedding. So either:

  • Strictly additive, non-breaking changes that have popular demand / precedent, get added without a lot of bureacracy; or
  • More unambiguous guidelines that can be applied to pitches are defined, and things like isNotEmpty are ruled out faster.

I can understand the reasoning behind the second option (even when coming from sugary Ruby land). At the same time, nothing is stopping anyone from creating a swift-sugar library, including isNotEmpty, isEven et al., which can be what ActiveSupport was for Ruby: a library that is well-known and can be added in order to enable nicer syntax and sugar. If there was an official package index, that would make the situation even better. In time, this library could become something that is well-maintained and used by a lot of projects.

4 Likes

As for whether it’s done with any regularity in software development, I’m not sure. I suggest it for two reasons:

  1. It’s a “term of art” in both classical mathematics & set theory, among others.
  2. It’s not a “negation” and thus enhances clarity.

If “isNotEmpty” (or whatever color the bikeshed is painted) is added merely to Array or even just Collection, then I’m against this pitch. But when viewed more generally, as a protocol to which both Collection and SetAlgebra conform (something we can’t add retroactively), then I’m for it.

1 Like

I like the idea of a secondary library containing this kind of extensions (which I believe already exists in Github). I think the main barrier for the adoption of such libraries is that they are not official, making the developers afraid of adding a third party dependency, which will be used all around the codebase and can not be isolated. What about an official extension of the standard library to contain all this syntactic sugar coming up lately?

1 Like

swift evolution: calm down no one’s talking about adding .isNotEmpty to the standard library

swift evolution: what if we added .isNotEmpty to the standard library

1 Like

IMHO, there really shouldn't be this extended discussion about such trivial matters. It's just bikeshedding. Either this is added or it isn't. Neither of these two options is going to make or break Swift.

How about instead:

  • Standardise on a package management solution. Yes, there is the SPM, but is anyone actually using it? (We use it on the server side, but I mean in iOS land).
  • Building a package index
  • Building a well-maintained (maybe even (semi-)official?) "Swift sugar" library (or multiple ones), so everyone can choose for themselves how much of it they want to have
  • Let the community decide whether the Swift sugar is cool or not cool (see Ruby with ActiveSupport).
2 Likes
Terms of Service

Privacy Policy

Cookie Policy