Problem with extension using generic constraint


#1

Hi,

I wanted to write an extension to „Optional where Wrapped == String“ which provides a calculated property named makeNilIfEmpty, which should return nil if the optional is nil or the string is empty and the optional string otherwise.
Please let's not discuss if this is useful.

This seems to work, see code below. Than I thought: „isEmpty“ is a property on Collection, why not define the extension on „Optional where Wrapped == Collection“ and extend the functionality to all Collections.

With String this works, with Collection I get the error: „ 'String?' is not convertible to 'Optional'“.

How would I correctly specify the return type of makeNilIfEmpty?
Or am I trying to do something that is not possible?

//extension Optional where Wrapped == Collection {
extension Optional where Wrapped == String {
var makeNilIfEmpty:Optional<Wrapped>{
    if self == nil {
        return nil
    }
    if self!.isEmpty {
        return nil
    }
    return self
}
}

let a:String? = nil
let b:String? = ""
let c:String? = "Test"

print([a,b,c].map{$0.makeNilIfEmpty})

(Adrian Zubarev) #2

You used same-type-requirement in form of Wrapped == Collection where actually you needed Wrapped: Collection constraint.

extension Optional where Wrapped: Collection {
  var makeNilIfEmpty: Wrapped? {
    return flatMap { $0.isEmpty ? .none : $0 }
  }
}

#3

Thanks. That makes sense.

And thanks for the compact code. But would it perform as well?


(Adrian Zubarev) #4

I'm not that much of a benchmark expert myself, but I would expect this to be the same as your example.


#5

Thanks again. I would agree.

Any idea for a swiftier name than makeNilIfEmpty?


(Adrian Zubarev) #6

Well I personally would go into a different direction.

extension Collection {
  var nonEmpty: NonEmpty<Self>? {
    if isEmpty { return .none }
    return NonEmpty(self[startIndex], self[index(after: startIndex)...])
  }
}

But to answer your question maybe noneIfEmpty would read well as a variable.


#7

That would be a better choice for some problems but I need to Decode JSON data into my model and I want to have nil values instead of the empy strings the server provides.


(Adrian Zubarev) #8

Well in theory this exactly what you will get, and as a bonus you'll get more guarantee that whenever you unwrap the type it's a collection that is guaranteed to be non-empty. Again this is my personal preference, you can go with noneIfEmpty solution if it's the simplest and enough for your needs. :slight_smile:


(Adrian Zubarev) #9

In case you have other scenarios where you might need to fallback to .none you could create a helper extension on Optional and slightly modify the implementation of noneIfEmpty.

extension Optional {
  func filter(
    _ isIncluded: (Wrapped) throws -> Bool
  ) rethrows -> Optional {
    switch self {
    case .some(let value):
      return try isIncluded(value) ? value : .none
    case .none:
      return .none
    }
  }
}

extension Optional where Wrapped: Collection {
  var noneIfEmpty: Wrapped? {
    return filter { $0.isEmpty == false }
  }
}

Alternatively you can drop noneIfEmpty and just use filter instead.