Collection.nonEmpty

I often find use for treating an empty string, or generally an empty collection, as nil.

One use case is a guard chain like:

guard
  let inputToCollection = optionalValue(),
  let collection = getCollection(), // Not valid, as it's not optional
  let finalValue = valueFromCollection(collection)
{
  // do stuff
}

I would like to propose this addition to the Collection protocol:

public extension Collection {
    var nonEmpty: Self? { return isEmpty ? nil : self }
}

guard
  let inputToCollection = optionalValue(),
  let collection = getCollection().nonEmpty,
  let finalValue = valueFromCollection(collection)
{
  // do stuff
}

If also works for strings:

let value = getString().nonEmpty.map({ useNonEmptyString($0) }) ?? fallback

1 Like

Your guard statement examples are a little odd, because they're missing the else, and read more like if statements (i.e. // do stuff is the important work, rather than an early bail-out).

So assuming you do want guard because you're bailing, the code could look something like:

guard let inputToCollection = optionalValue() else { return }
let collection = getCollection()
guard !collection.isEmpty else { return }
let finalValue = valueFromCollection(collection)
// do stuff

It's not clear to me why being able to combine those two guards in a single compound statement is a win. Breaking the statements out seems clearer, at least to me.

Perhaps if the error handling were more complex than a simple return (say, logging then throwing) it might help avoid repeating yourself in the else clauses. But then, if these are true errors I suspect you'd want to log different things depending on which case failed.

Yes, sorry, that example was if you e.g. have several error types and perhaps non-trivial error handling:

guard check1 else {
    throw Exception("Error 1")
}

guard
    let inputToCollection = optionalValue(),
    let collection = getCollection().nonEmpty,
    let finalValue = valueFromCollection(collection)
{
    let msg = "error 2"
    log(msg)
    throw Exception(msg)
}

// do real stuff here

FWIW, you can write case let collection = getCollection() in the guard statement if you want.

3 Likes

But perhaps the most important example for me is the use with functional programming.

Awesome! Thanks!

It feels a bit weird, but it works :smile: :+1:

Collection.nonEmpty would still make it easier, and more readable, to make expressions where only non-empty collections (especially strings) should be considered.

+1 since I've been using this pattern since ObjC, and it‘s even nicer in Swift. However the name seems bad to me. Not that I have a better name (I call mine chuzzle).

I think if this were to be in the stdlib it should reference optionality or nill-ness is some manner. or perhaps compacted() would fit? Since we now have the related compactMap.

Edit: well compacted() suggests it might remove nils in the middle so… no. But I think the name with adjustment has legs.

I like nonEmpty because it clearly states that we want a non-empty collection. And some day this could even return a NonEmptyCollection of some sort with a compile time guarantee of non-emptiness.

It also works well with my other suggestion of isNonEmpty ;-)

This example only works if you are interested in the first or last item of a collection. In that case you could as well use first and last.

guard
  let inputToCollection = optionalValue(),
  let finalValue = collection.last
else {
	// ...
}
1 Like

But if using functional programming it would still be awkward compared to e.g:

let msg = getString().nonEmpty.map { "Valid result: \($0)" } ?? "Invalid or empty result"

Sure, but this is an even more fringe example.

My examples may be bad, but I do not consider using Functional Programming a fringe example :stuck_out_tongue: To me Collection.nonEmpty enables writing clear and concise expressions that are very easy to reason about.

The functional style here would be to use a monad, instead of special return values to indicate failure.

1 Like