Skip to content

Instantly share code, notes, and snippets.

@jessesquires
Last active June 27, 2018 02:19
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jessesquires/4b6e76bb580600a85c40 to your computer and use it in GitHub Desktop.
Swift optional generic parameters?
protocol FactoryAType {
typealias Product
}
protocol FactoryBType {
typealias Product
}
struct CombinedFactory<T: FactoryAType, U: FactoryBType where T.Product == U.Product> {
let factoryA: T
let factoryB: U?
init(factoryA: T, factoryB: U? = nil) {
self.factoryA = factoryA
self.factoryB = factoryB
}
}
struct IntAFactory: FactoryAType {
typealias Product = Int
}
struct IntBFactory: FactoryBType {
typealias Product = Int
}
let fa = IntAFactory()
let fb = IntBFactory()
let allGood = CombinedFactory(factoryA: fa, factoryB: fb)
// Does not work:
// Cannot invoke initializer for type 'CombinedFactory<_, _>' with an argument list of type '(factoryA: IntAFactory)'
let combinedFactory = CombinedFactory(factoryA: fa)
// This works:
// But this is awkward. I don't want to have to specify B if it is nil
let workingFactory = CombinedFactory<IntAFactory, IntBFactory>(factoryA: fa)
@jessesquires
Copy link
Author

So CombinedFactory has 2 generic type parameter that must satisfy T.Product == U.Product.

However, U is optional, which makes this expression weird:
CombinedFactory<IntAFactory, IntBFactory>(factoryA: fa)
It would be nicer if CombinedFactory(factoryA: fa) worked instead.

Is there a better way to express these types or another (better?) way to achieve this?

@rnapier
Copy link

rnapier commented Nov 6, 2015

In most cases, if U is not passed, then this shouldn't be a CombinedFactory. It should just an AFactory. Just make sure that AFactory and CombinedFactory conform to the same protocol so they can be used interchangeably. It'd be helpful to see the actual use case; my experience is that when you find yourself here, the mistake happened much earlier and you shouldn't even have been here. Most common cause is using a struct and methods when you could have just used a closure and avoided all the intermediate types.

If you really need the idea of "no such U" then you need to say so:

struct EmptyBFactory<A: FactoryAType>: FactoryBType {
    typealias Product = A.Product
}

Then you can use it like:

let onlya = CombinedFactory(factoryA: factoryA, factoryB: EmptyBFactory<FactoryAType>())

or create a function like:

func aOnly<A: FactoryAType>(factoryA: A) -> CombinedFactory<A, EmptyBFactory<A>> {
    return CombinedFactory(factoryA: factoryA, factoryB: EmptyBFactory<A>())
}

At this point, however, you do slam into numerous weaknesses in the compiler. You can't make aOnly into a init. You can't even make it into a class static. Swift just doesn't have the ability to have init explicitly provide its associated types (I don't believe there's any deep reason for that to be impossible, but it is).

I'm very interested in what the underlying problem is, though. "Factory" is already sounding like something un-Swifty.

@natecook1000
Copy link

my experience is that when you find yourself here, the mistake happened much earlier and you shouldn't even have been here.

This is sooooo truuuuuue

@rnapier
Copy link

rnapier commented Nov 7, 2015

@jessesquires
Copy link
Author

"Factory" is already sounding like something un-Swifty.

Ha -- probably because I'm trying to make Cocoa less cubersome. I hate that UICollectionView/UITableView delegates/dataSources are littered with @optional methods 😢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment