0

I need a single function to resolve different dependencies in a class. But there is a compilation error appears. Is it possible to create that generic function or there are some compiler constraints in Swift?

import Foundation

protocol Client: class {
    var description: String { get }
}
final class ImportantPerson : Client {
    var description: String {
        return "Important person"
    }
}

protocol Order: class {
    var description: String { get }
}
final class LastOrder : Order {
    var description: String {
        return "Last order"
    }
}

final class A {

    fileprivate func resolveDependency<T>() -> T {
        return resolve() as T
    }

    private func resolve() -> Client {
        return ImportantPerson()
    }
    private func resolve() -> Order {
        return LastOrder()
    }

}

let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()

print("Client: \(client.description)")
print("Order: \(order.description)")

EDIT: This question is not about if Swift allows to create two functions that differs only by return type. I know it's possible. I think there are some artificial constraints in the compiler but not in the fundamental logic that should allow to infer needed type from a context.

adnako
  • 1,287
  • 2
  • 20
  • 30
  • Possible duplicate of [Swift: method overloads that only differ in return type](https://stackoverflow.com/questions/31712447/swift-method-overloads-that-only-differ-in-return-type) – Dávid Pásztor Jul 20 '17 at 15:15
  • @DávidPásztor: I cannot see how that Q&A answers *this* question. – Martin R Jul 20 '17 at 15:16
  • Why do you need a single function? It cannot work because `T` can be *any* type. Why can't you just call `a.resolve()` and let the compiler choose the right overloaded function? – Martin R Jul 20 '17 at 15:21
  • @MartinR, I do not want to modify Order and Person classes, I need the compiler infer type from a context. There are no other types except Client and Order in this code, why the compiler can't just create two distinct functions for both cases and use them? – adnako Jul 20 '17 at 15:26
  • You can call `let client: Client = a.resolve()` and the compiler *does* choose the matching function from the context. – Martin R Jul 20 '17 at 15:29
  • Somewhat related: [Wrong specialized generic function gets called in Swift 3 from an indirect call](https://stackoverflow.com/q/41980001/2976878) – Hamish Jul 20 '17 at 15:45
  • @MartinR, I need a single entry point to use all resolve() functions to prepare an environment before call them. – adnako Jul 20 '17 at 15:53

2 Answers2

0

Let's put yourself into the compiler's shoes. Imagine that this was not causing an error and you had one signature with different outputs.

Whenever you call resolveDependency<T>() -> T, the compiler will return you a type T which is an instance conforming to a protocol in your case.

In your code you call this method with different instances conforming to the same protocol. At that stage the compiler has no idea about this. All it knows is that you have passed an instance of T and it needs to give you a result in shape of T

There is no problem until this point. As soon as you execute

return resolve() as! T

The compiler will be confused. I have a T but I don't know which resolve() I will call... All I know is that I have a T. How would I know if this is an Order or a Client ?

In order to prevent such confusions we have compiler-time errors. At least this is the case for Swift. (I don't know how this works in other languages)

You need to define different methods with different signatures and cast your type accordingly to get a similar result

fileprivate func resolveDependency<T>() -> T {
  // check if this is an Order
  resolveForOrder()

  // check if this is a Client
  resolveForClient()
}

private func resolveForOrder() -> Order {
  return LastOrder()
}

private func resolveForClient() -> Client {
  return ImportantPerson()
}

This is like trying to fix a space shuttle engine with a car mechanic. Yes, they both have an engine, they both run on fuel but the mechanic only knows how to fix your car's engine he is not a rocket scientist(!)

erenkabakci
  • 442
  • 4
  • 15
  • But the complier is able to create two different instances of this function and use them in appropriate case, isn't it? – adnako Jul 20 '17 at 15:55
  • "How would I know if this is an Order or a Client" - I set a type for a variable, why this is not enough? – adnako Jul 20 '17 at 15:58
  • 1
    @adnako As said in [the Q&A I linked to above](https://stackoverflow.com/q/41980001/2976878), the compiler doesn't by default create specialised implementations of `resolveDependency()` for each `T` that it's applied with – there is only *one implementation* that takes any given `T.self` metatype and returns an instance of that type. There's no overload of `resolve()` that can return any `T`, so there's no valid overload to be dispatched to (remember: overload resolution takes place at compile time). – Hamish Jul 20 '17 at 16:08
0

This code works fine:

import Foundation

protocol Client: class {
    var description: String { get }
}
final class ImportantPerson : Client {
    var description: String {
        return "Important person"
    }
}

protocol Order: class {
    var description: String { get }
}
final class LastOrder : Order {
    var description: String {
        return "Last order"
    }
}

final class A {

    fileprivate func resolveDependency<T>() -> T {
        if T.self == Client.self {
            return resolve() as Client as! T
        } else {
            return resolve() as Order as! T
        }
    }

    private func resolve() -> Client {
        return ImportantPerson()
    }
    private func resolve() -> Order {
        return LastOrder()
    }

}

let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()

print("Client: \(client.description)")
print("Order: \(order.description)")

But I believe that compiler should resolve the if else clause himself, it's not so hard as I suppose. Also there is some bug in the compiler when it tries to match types like that:

    switch T.self {
    case is Client:
        return resolve() as Client as! T
    default:
        return resolve() as Order as! T
    }
adnako
  • 1,287
  • 2
  • 20
  • 30
  • But now `resolveDependency()` will crash at runtime for any `T` that isn't `Client` or `LastOrder` (or a super type of `LastOrder`). The function signature is effectively a lie; it says that it can return *any* type `T`, but cannot do so. That's why the compiler doesn't and shouldn't generate such code implicitly. – Hamish Jul 21 '17 at 11:03
  • Yep, and this is the reason why I want the compiler checks his AST what types are used calling this function. He has this information. – adnako Jul 21 '17 at 14:19
  • Not always – what if the function is in another (already compiled) module? – Hamish Jul 21 '17 at 14:25
  • Look at **fileprivate** statement – adnako Jul 21 '17 at 14:32
  • Okay, it is feasible in your specific case; but having the behaviour implicitly tied to access control would be unintuitive. There would likely need to be a new keyword or attribute to express a generic function that will always have its implementation specialised. I'm actually all for such a change, but the Swift team aren't currently; see https://bugs.swift.org/browse/SR-3829. – Hamish Jul 21 '17 at 14:47
  • Your proposition entails deep changes in dispatch mechanism. This change may affect all current sources already written. I think it's a more complex feature than I want to be implemented. I just need the compiler should use the information I gave him. I specifically made the constraints to successfully compile the code, I can't see any reason to hinder it. – adnako Jul 21 '17 at 15:29
  • Yup, that's why it should be a new keyword/attribute :) And it's not really a "deep change" – the compiler can *already* specialise the implementation of generic functions as an optimisation. Although I'm not entirely sure I understand what the feature you're proposing if it isn't specialisation; could you please elaborate? How else would the implementation of `resolveDependency()` know which overload of `resolve()` to call? – Hamish Jul 21 '17 at 15:35
  • The first obvious decision is to create several separate instances of the `resolveDependency()` function. The second one is to generate the code I wrote by hands automatically after the compiler checked that no more entry points are exist and create exatly that count of conversion that it needs. – adnako Jul 21 '17 at 15:59
  • Ah okay I see; well your first option is *exactly* the specialisation of the implementation of generic functions. Your second option is viable, although that being said, specialisation would be more performant (doesn't introduce branching; allows for inlining). But both options would have the same observed behaviour, which can affect existing generic functions, which is why it would need to be introduced as a new keyword/attribute. – Hamish Jul 21 '17 at 16:12