Specialised, Overloaded Function Extension access in a Generic Context

Hi all,

I'm looking for a way to be able to call an overloaded, specialised extension function from a generic context. Is there a way that this can be achieved without just passing a closure of desired signature type? I would ideally like to maintain a full Fetcher (for example) in the context where this is used.

protocol Fetcher {
	func fetch()
}

final class FactFetcher<T>: Fetcher {

	func fetch() {
		print("We don't know any facts about this type yet!")
	}

}

extension FactFetcher where T == Int {

	func fetch() {
		print("Int holds a signed integer.")
	}

}

func performFetch<U>(_ fetcher: U) where U: Fetcher {
	fetcher.fetch()
}


let strFetcher = FactFetcher<String>()
performFetch(strFetcher)
// ✅ expected -> "We don't know any facts about this type yet!"

let intFetcher = FactFetcher<Int>()
performFetch(intFetcher)
// 🛑 unexpected -> "We don't know any facts about this type yet!"

// Passing a closure of the same function type, but I need a `Fetcher` instance ideally
func fetchUsingClosure(_ fetch: () -> Void) {
	fetch()
}
fetchUsingClosure(intFetcher.fetch)
// ✅ expected -> "Int holds a signed integer."

Does it have to be generic?

Or could it be a class hierarchy?
class Fetcher {
  func fetch() { print("We don't know anything about this type yet!") }
}

class FactFetcher: Fetcher {
  override func fetch() { print("We don't know any facts about this type yet!") }
}

final class IntFactFetcher: FactFetcher {
  override func fetch() { print("Int holds a signed integer.") }
}

func performFetch(_ fetcher: Fetcher) {
  fetcher.fetch()
}

let f = IntFactFetcher()
performFetch(f) // Int holds a signed integer.


Here's why your code doesn't work the way you expect:

func performFetch<U>(_ fetcher: U) where U: Fetcher {
  // In this generic context, the compiler doesn't know anything more about
  // U than what is knowable from U's constraints, ie all it knows is that
  // U conforms to Fetcher.
  // FactFetcher satisfies the requirement of Fetcher independently of
  // its T, and this generic context knows nothing about any T, so 
  // it will call that method (which satisfies the requirement independently
  // of any T).
  fetcher.fetch()
}

Also, note that "there cannot be more than one conformance, even with different conditional bounds" (quoted from an error message you'll see later in this post). And that ...

final class FactFetcher<T>: Fetcher {
    func fetch() { // <---  ... this satisfies the requirement of Fetcher ...
        print("We don't know any facts about this type yet!")
    }
}

while

extension FactFetcher where T == Int {
    func fetch() { // <--- ... this is not satisfying any requirement of Fetcher
        print("Int holds a signed integer.")
    }
}

Now, let's say you try this:

protocol Fetcher { func fetch() }

final class FactFetcher<T> {
  // We don't know any facts about this type yet! (So don't conform to Fetcher yet!)
}

extension FactFetcher: Fetcher where T == Int {
  func fetch() { print("Int holds a signed integer.") }
}

func performFetch<U>(_ fetcher: U) where U: Fetcher {
  fetcher.fetch()
}

let intFetcher = FactFetcher<Int>()
performFetch(intFetcher) // Int holds a signed integer.

It seems to work at first. But now you can't have your "default" fetch for any T, and you cannot add support for eg T == String either, because of the error here:

protocol Fetcher {  func fetch() }

final class FactFetcher<T> {
  // We don't know any facts about this type yet! (So don't conform to Fetcher yet.)
}

extension FactFetcher: Fetcher where T == Int {
  func fetch() { print("Int holds a signed integer.") }
}

extension FactFetcher: Fetcher where T == String { // ERROR: Conflicting conformance of 'FactFetcher<T>' to protocol 'Fetcher'; there cannot be more than one conformance, even with different conditional bounds
  func fetch() { print("String holds characters.") }
}

There are probably many ways you can redesign your code to solve your problem, but without knowing more about the actual use case I think it's hard to say what would be the best solution.

But as an example, here's a way that would let you do sort of what I guess you seem to want, except it doesn't support a "default/unimplemented" fetcher:

protocol Fetcher { func fetch() }

protocol FetchableFact { static func fetchFact<F: Fetcher>(for fetcher: F) }

final class FactFetcher<T: FetchableFact>: Fetcher {
  func fetch() {
    T.fetchFact(for: self)
  }
}

extension Int: FetchableFact {
  static func fetchFact<F: Fetcher>(for fetcher: F) {
    print("Int holds a signed integer.")
  }
}

extension String: FetchableFact {
  static func fetchFact<F: Fetcher>(for fetcher: F) {
    print("String holds characters.")
  }
}

func performFetch<U>(_ fetcher: U) where U: Fetcher {
  fetcher.fetch()
}

let strFetcher = FactFetcher<String>()
performFetch(strFetcher) // String holds characters.

let intFetcher = FactFetcher<Int>()
performFetch(intFetcher) // Int holds a signed integer.

Sorry for the many edits, but Swift's generics are currently not always so easy to explain/learn.

1 Like
Terms of Service

Privacy Policy

Cookie Policy