Specialised, Overloaded Function Extension access in a Generic Context

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