Generic function that accepts a Type and returns instance of that Type

I have a manager object with a public interface defined by a Protocol (to allow different versions for different situations). Since other objects will only know about the manager as its protocol type, they need to be able to query whether it has an object of a specific type. If the manager has a member of that type, it will return the instance. Otherwise, it will return nil.

protocol ObjectRequestable {
    func getObject<T>(type: T.Type) -> T?
}

struct ShadowFilter {
    let width: Float
}

struct RainbowFilter {
    
}

class FilterManager: ObjectRequestable {
    
    private let shadowFilter = ShadowFilter(width: 8)
    
    func getObject<T>(type: T.Type) -> T? {
        switch type {
        case ShadowFilter.self: return shadowFilter
        default: return nil
        }
    }
}

let filterManager = FilterManager()
// Should contain the shadowFilter instance.
let shadow = filterManager.getObject(type: ShadowFilter.self)
// Should contain nil.
let rainbow = filterManager.getObject(type: RainbowFilter.self)

The case ShadowFilter.self: return shadowFilter line produces 2 compiler errors:

  1. Cannot convert return expression of type 'ShadowFilter' to return type 'T?'
  2. Expression pattern of type 'ShadowFilter.Type' cannot match values of type 'T.Type'

Is this sort of scenario possible, or is there a different way I should be thinking about this?

You need to return shadowFilter as! T

Interestingly this works:

        if type == ShadowFilter.self {
            return (shadowFilter as! T)
        }
        else {
            return nil
        }

but not this (fails to compile):

        switch type {
            case ShadowFilter.self: return (shadowFilter as! T)
            // πŸ›‘ Error: Expression pattern of type 'ShadowFilter.Type' cannot match values of type 'T.Type'
            default: return nil
        }
2 Likes

Thank you! I updated my code like this and it works now:

func getObject<T>(type: T.Type) -> T? {
    if type == ShadowFilter.self, let sf = shadowFilter as? T {
        return sf
    } else {
        return nil
    }
}

Nice. Alternatively:

    func getObject<T>(type: T.Type) -> T? {
        if let v = shadowFilter as? T { return v }
        else { return nil }
    }

I think this is because the first hits a specific overload of == in the standard library for comparing two Any.Type?s, whereas the switch version will only work if the pattern matching operator ~= was overloaded or if the argument is Equatable (because there's a specific ~= overload for Equatable types).

3 Likes

How do I do this in this example?

The following seems to be working (at least it fixes the compilation error):

func ~= (t0: Any.Type, t1: Any.Type) -> Bool {
    t0 == t1
}

That's cleaner looking, but I feel like it's going to be less obvious what types are involved (my real-world code has 7-8 different types that can be requested), so I'm going to keep my wordier version.

I really appreciate your help!

Of course, I don't recommend that anybody define an operator like that in their own code base, for the same reasons it's a bad idea to implement retroactive conformances for types and protocols that you don't own.

In an ideal world, metatypes would just be Equatable and Hashable.

Could you think of a particular scenario when this overload would fail?
Was it just an oversight to not have it in the standard library?

I guess this is a bigger ask.

A cleaner version:

struct Wrapper {
    let type: Any.Type
    static func ~= (t0: Wrapper, t1: Wrapper) -> Bool {
        t0.type == t1.type
    }
}

class FilterManager: ObjectRequestable {
    private let shadowFilter = ShadowFilter(width: 8)
    
    func getObject<T>(type: T.Type) -> T? {
        switch Wrapper(type: type) {
            case Wrapper(type: ShadowFilter.self): return (shadowFilter as! T)
            default: return nil
            // πŸ‘Œ compiles fine
        }
    }
}
2 Likes

Hi Josh :wave:

Using case is ShadowFilter.Type also seems to work:

class FilterManager: ObjectRequestable {
    private let shadowFilter = ShadowFilter(width: 8)

    func getObject<T>(type: T.Type) -> T? {
        switch type {
        case is ShadowFilter.Type: return shadowFilter as! T?
        default: return nil
        }
    }
}
3 Likes

I just use ObjectIdentifier.

1> func typesEqual<T, U>(_ t: T.Type = T.self, _ u: U.Type = U.self) -> Bool { ObjectIdentifier(t) == ObjectIdentifier(u) } 
  2> typesEqual(String.self, Int.self) 
$R0: Bool = false
  3> typesEqual(String.self, String.self) 
$R1: Bool = true
1 Like

This is the simplest solution so far.

Riffing on this idea; you could match the Wrapper against Any.Type to avoid having to wrap each case statement:

class FilterManager: ObjectRequestable {
  private let shadowFilter = ShadowFilter(width: 8)

  func getObject<T>(type: T.Type) -> T? {
    switch Wrapper(type: type) 
    case ShadowFilter.self: return shadowFilter as! T?
    default: return nil
    }
  }

  struct Wrapper {
    let type: Any.Type

    static func ~= (t0: Any.Type, t1: Wrapper) -> Bool {
      t0 == t1.type
    }
  }
}
3 Likes

Or even

func getObject<T>(type: T.Type) -> T? {
    shadowFilter as? T
}

If there multiple properties like that:

func getObject<T>(type: T.Type) -> T? {
    (shadowFilter as? T) ?? (blurFilter as? T) ?? (rainbowFilter as? T)
}
1 Like

This approach, and my suggestion of case is for that matter, have a subtle difference in how they’ll handle subclasses compared to the if … == … or the Wrapper approaches in case that matters.

The cast alone and the case is will succeed for a super type even if they instance is of a subtype depending on the order of the checks, but the equality based approaches should only return exact matches.

Nice, possibly the best so far.

I'd also consider the following approach:

protocol Filter {}
struct ShadowFilter: Filter {
    let width: Float
}
struct RainbowFilter: Filter {}

class FilterManager: ObjectRequestable {
    var allFilters: [Filter] = [
        ShadowFilter(width: 8),
        RainbowFilter()
    ]
    func getObject<T>(type: T.Type) -> T? {
        allFilters.first { $0 is T } as? T
    }
}
Or even
protocol ObjectRequestable {
    func getObject<T: Filter>(type: T) -> T
}
protocol Filter {
    static var value: Self { get }
}
struct ShadowFilter: Filter {
    let width: Float
    static let value = ShadowFilter(width: 8)
}
struct RainbowFilter: Filter {
    static let value = RainbowFilter()
}
class FilterManager {
    func getObject<T: Filter>(type: T) -> T {
        T.value
    }
}

I went with something like:

class FilterManager: ObjectRequestable {
    
    private let shadowFilter = ShadowFilter(width: 8)
    
    func getObject<T>(type: T.Type) -> T? {
        if type == ShadowFilter.self {
            return shadowFilter as? T
        } else {
            return nil
        }
    }
}

My reasons were:

  1. The code is a more obvious "if I have this type then return it" organization.
  2. Doesn't require any operator overloads or extensions.
  3. Complexity expands it to new lines, rather than chaining onto a single very-long line. Slightly better for version control.
  4. Doesn't require additional members to be added to the class.
1 Like