Type erasure with factory

Hi

I wondered if anyone could help me with this problem. I built a protocol to allow conformance for multiple different db connections, I then used type erasure to unwrap the concrete type. But my final step was to allow a factory to return the given connection, but I'm having some trouble. So any help would be appreciated. This is the code:

public protocol ViiConnection: class {
    associatedtype ViiConnectionType
    var connection: ViiConnectionType { get set }
}

public class AnyViiConnection<ErasedConnectionType>: ViiConnection {
    public var connection: ErasedConnectionType
    
    public init<Injected: ViiConnection>(_ connect: Injected) where Injected.ViiConnectionType == ErasedConnectionType{
        connection = connect.connection
    }
}

public class ConnectionFactory {
    @available(OSX 10.15.0, *)
    public static func getViiConnection(selectedDb: ViiDatabaseType, eventLoop: EventLoop, credentials: Credential) throws -> some ViiConnection {
            switch selectedDb {
            case .mysql:
                return AnyViiConnection(ViiMySQLConnection(eventLoop: eventLoop, credentials: credentials))
            case .postgres:
                return AnyViiConnection(ViiPostgresConnection(eventLoop: eventLoop, credentials: credentials))
            ...
        }
    }
}

So, I get Function declares an opaque return type, but the return statements in its body do not have matching underlying types, which I understand, I just don't understand if there is an approach to solve my problem? I tried a few different approaches with the factory, this was just the latest :slight_smile:

What's the type of

  • ViiMySQLConnection.ViiConnectionType
  • ViiPostgresConnection.ViiConnectionType, etc.

Do they match? If not, you'll probably need to type-erase that too.

1 Like

No they don't one returns EventLoopFuture<MySQLConnection> the other EventLoopFuture<PostgresConnection>, I figured I might have to do another Type erasure, but was trying to avoid for my own sanity when I go to edit this a few months later :laughing:

Is it really necessary to know the type of ViiConnectionType at compile time? It's be easier, and far more maintainable, to make ViiConnectionType a protocol and return that.

I believe so as I need to run some db specific queries rather than standard SQL

If there are some type-specific information, that may be in the protocol too.

Especially since ConnectionFactory seems to be public-facing interface with opaque return type, it already means the user of such function doesn't really care at all about the internal working of it, only the methods they have.

Originally I was just returning the protocol type of ViiConnectionType. But my problem was I couldn't satisfy the compiler when I tried to use the underlying type. Here's an example of the connection type.

public class ViiPostgresConnection: ViiConnection {
    
    public typealias ViiConnectionType = EventLoopFuture<PostgresConnection>
    
    public var connection: EventLoopFuture<PostgresConnection>
    
    public init(eventLoop: EventLoop, credentials: Credential){
        self.connection = PostgresConnection.create(on: eventLoop, credentials: credentials)
    }
}

It's public because It's a console application and I split the targets, so that the library of code could be tested as the console application it can't be. I haven't done much with more advanced aspects of Swift's type system other than some not overly complex generics. So it's a bit of a new area for me

Rather, I'm questioning if you need to explore that far to get to the solution that will satisfy you. Likely the user can be agnostic to the underlying distinction between SQLConnection and PostgresConnection–they are just Connection.

So that it may be possible to do

protocol Connection { ... }
class PostgresConnection: Connection { ... }
class MySQLConnection: Connection { ... }

protocol ViiConnection: AnyObject {
  var connection: Connection { get set }
}

class ViiPostgresConnection: ViiConnection {
  public var connection: Connection { PostgresConnection(...) }
  ...
}

Which would be much easier to reason. (And you don't need to do any type-erase to begin with). Though it you insist that ViiConnection must be Protocol with Associated Type (PAT), you'll need to type-erase both ViiConnection and ViiConnectionType. IMO it's a code smell that you may be trying to preserve too much type information (unless of course, you utilise that information).

1 Like

Ok, let me look a bit more into this, because it's becoming harder than what I want and I would agree. Thanks for your help and your time too :slight_smile:

1 Like