4
votes

I'm trying to write a function to unwrap optionals with an arbitrary number of levels of nesting. Here's the test I'm using:

let a: Int??? = 1
let b: Int??? = nil
print(a.unwrap(0), b.unwrap(0)) // should print 1, 0

I can get the correct output with a basic generic function:

extension Optional {
    func unwrap<T> (_ defaultValue: T) -> T {
        return (self as? T) ?? defaultValue
    }
}

print(a.unwrap(0), b.unwrap(0)) // 1, 0

But this doesn't prevent the function from being called with a different type than the optional. For instance, I could call a.unwrap("foo") and it would print "foo" instead of "1" since of course you can't cast Int??? to String.

I tried it using Wrapped instead, which semi-properly restricts the default value but doesn't give the correct output:

extension Optional {
    func unwrap (_ defaultValue: Wrapped) -> Wrapped {
        return (self as? Wrapped) ?? defaultValue
    }
}

print(a.unwrap(0), b.unwrap(0)) // Optional(Optional(1)), nil

It only unwraps one level of the optional, instead of all three, and since nil is a valid value for Int?? it doesn't return the default.

Is there any way to safely do what I want here?

1
Probably just a duplicate of stackoverflow.com/questions/50283215/… - read the comments and linked code, please. The posited situation seems highly artificial, though, so it's hard to see any real-life point in what you're asking to do.matt
Couldn't think of compile time error on type mismatch. But for runtime, you can certainly throw one as if defaultValue is Wrapped { return (self as? T) ?? defaultValue } else { fatalError("Type mismatch") }Kamran
What are the use cases you need this for? Maybe you'd be better with some other data models.Cristik

1 Answers

1
votes

This code does what you ask for. The drawback is that you need to implement the Unwrappable protocol in every type you want to put inside optionals. Maybe Sourcery can help with that.

protocol Unwrappable {
  associatedtype T

  func unwrap(_ default: T) -> T
}

extension Optional {}

extension Optional: Unwrappable where Wrapped: Unwrappable {
  typealias T = Wrapped.T

  func unwrap(_ defaultValue: T) -> T {
    if let value = self {
      return value.unwrap(defaultValue)
    }
    return defaultValue
  }
}

extension Int: Unwrappable {
  typealias T = Int

  func unwrap(_ default: Int) -> Int {
    return self
  }
}

let nestedOptionalValue: Int??? = 6
let nestedOptionalNil: Int??? = nil
let optionalValue: Int? = 6
let optionalNil: Int? = nil
print(nestedOptionalValue.unwrap(0)) // prints 6
print(nestedOptionalNil.unwrap(0))   // prints 0
print(optionalValue.unwrap(0))       // prints 6
print(optionalNil.unwrap(0))         // prints 0

The trick is that the Unwrappable protocol marks types that can be eventually unwrapped (as in after 0, 1 or more unwraps) to a certain type.

The difficulty in this problem comes from the fact that in an extension to Optional, you can get the Wrapped type, but if Wrapped is optional again, you can't access Wrapped.Wrapped (in other words, Swift doesn't support Higher Kinded Types).

Another approach, would be to try to add an extension to Optional where Wrapped == Optional. But again you'd need Higher Kinded Types support to access the Wrapped.Wrapped type. If we try to extend Optional where Wrapped == Optional<T>, we also fail, because we cant extend Optional generically over T.