How to Write a Function That Will Unwrap a Generic Property in Swift Assuming It Is an Optional Type

How can I write a function that will unwrap a generic property in swift assuming it is an optional type?

The "trick" is to define a protocol to which all optionals conform
(this is from Creating an extension to filter nils from an Array in Swift
with a minor simplification; the idea goes back to this Apple Forum Thread):

protocol OptionalType {  
typealias Wrapped
func intoOptional() -> Wrapped?
}

extension Optional : OptionalType {
func intoOptional() -> Wrapped? {
return self
}
}

You can use that in your case as:

class GenericClass<SomeType> {
var someProperty: [SomeType] = []
}

extension GenericClass where SomeType : OptionalType {
func filterNilValuesOfSomeProperty() -> [SomeType.Wrapped] {
return someProperty.flatMap { $0.intoOptional() }
}
}

which uses the flatMap() method from SequenceType:

extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

Example:

let aClass = GenericClass<Int?>()
aClass.someProperty = [3,5,6,nil,4,3,6, nil]

let x = aClass.someProperty
print(x) // [Optional(3), Optional(5), Optional(6), nil, Optional(4), Optional(3), Optional(6), nil]

let y = aClass.filterNilValuesOfSomeProperty()
print(y) // [3, 5, 6, 4, 3, 6]

In Swift 3 and later the protocol has to be defined as

protocol OptionalType {
associatedtype Wrapped
func intoOptional() -> Wrapped?
}

How to use generic function to tear down swift's optional pyramid of doom

The problem is that your second implementation of if_let no longer takes as a final parameter a function of type (T,U,V)->(). It now needs a function of type ([T])->(). If you call it with one, it compiles:

if_let(s1, s2, s3) { args in // or: (args: [String])->() in
print("\(args[0]) - \(args[1]) - \(args[2])")
}

Unwrap/coalesce a multi-level optional

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.

Returning a nil from an optional generic extension

I don't think you can achieve this with a static property, however you can achieve it with a static function:

extension Result {
static func `nil`<U>() -> Result where T == U? {
return .init { nil }
}
}

let x: Result<Int?> = .nil()

Functions are way more powerful than properties when it comes to generics.


Update After some consideration, you can have the static property, you only need to add an associated type to OptionalType, so that you'd know what kind of optional to have for the generic argument:

protocol OptionalType {
associatedtype Wrapped
}

extension Optional: OptionalType { }

extension Result where T: OptionalType {
static var `nil`: Result<T.Wrapped?> {
return Result<T.Wrapped?> { nil }
}
}

let x: Result<Int?> = .nil

One small downside is that theoretically it enables any kind of type to add conformance to OptionalType.

Swift: Extension on [ SomeType T ?] to produce [ T ?] possible?

I don't know if there is a simpler solution now, but you can use the same “trick” as in How can I write a function that will unwrap a generic property in swift assuming it is an optional type? and Creating an extension to filter nils from an Array in Swift, the idea goes back to this Apple Forum Thread.

First define a protocol to which all optionals conform:

protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}

extension Optional : OptionalType {
var asOptional: Wrapped? {
return self
}
}

Now the desired extension can be defined as

extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
var values: [Element.Wrapped.NumberType?] {
return self.map( { $0.asOptional?.value })
}
}

and that works as expected:

let arr = [SomeType(value: 123), nil, SomeType(value: 456)]
let v = arr.values

print(v) // [Optional(123), Optional(456)]
print(type(of: v)) // Array<Optional<Int>>

how to extend optional chaining for all types

is there no way to make this generic function work as a protocol method?

No, you must "explicitly apply the protocol to the types I care about".

However, you are in fact reinventing the wheel. This is the use case of flatMap/map. If both foo and bar are optional, you can write:

bar.flatMap { foo?.doSomething($0) }

Note the lack of ? after bar. You are calling flatMap on Optional, rather than bar's type. If doSomething returns T, the above expression will return T?.

If only bar is optional, use map:

bar.map { foo.doSomething($0) }

Optional extension for any types

The answer to your basic question is to just remove the where clause:

extension Optional { 
// ... the rest is the same
func isNil<T>(value: T) -> T {
if self != nil {
return self as! T
}
return value
}
}

Now it applies to all Optionals.

But this code is quite broken. It crashes if T is not the same as Wrapped. So you would really mean a non-generic function that works on Wrapped:

extension Optional {
func isNil(value: Wrapped) -> Wrapped {
if self != nil {
return self! // `as!` is unnecessary
}
return value
}
}

But this is just an elaborate way of saying ?? (as matt points out)

extension Optional {
func isNil(value: Wrapped) -> Wrapped { self ?? value }
}

Except that ?? is much more powerful. It includes an autoclosure that avoids evaluating the default value unless it's actually used, and which can throw. It's also much more idiomatic Swift in most cases. You can find the source on github.

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}

But I can imagine cases where you might a method-based solution (they're weird, but maybe there are such cases). You can get that by just rewriting it as a method:

extension Optional {
public func value(or defaultValue: @autoclosure () throws -> Wrapped) rethrows -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
}

tempInt.value(or: 2)


Related Topics



Leave a reply



Submit