Reputation: 11539
I'd like to do something like this:
class Config<T> {
func configure(x:T)
// constraint B to be subclass of A
class func apply<A,B:A>(c:Config<A>, to:B) {
c.configure(to)
}
}
So later, for example, I can apply a Config to a UILabel:
class RedViewConfig<T:UIView> : Config<T> {
func configure(x:T) {
x.backgroundColor = .redColor();
}
}
let label = UILabel()
Config.apply(RedViewConfig(), to:label)
Or extend Config classes:
class RedLabelConfig<T:UILabel> : RedViewConfig<T> {
func configure(x:T) {
super.configure(x)
x.textColor = .redColor();
}
}
Config.apply(RedLabelConfig(), to:label)
I tried to do it, but I couldn't constraint classes. So I tried with protocols and associated types, but when subclassing I found problems (like this) when overriding the associated type.
Upvotes: 1
Views: 171
Reputation: 299663
Classes make this way too complicated. Inheritance is almost always a bad idea in Swift if you can possibly avoid it.
Structs, though closer, still make this a bit over-complicated and restrictive.
Really, these configurators are just functions. They take a thing and they do something to it, returning nothing. They're just T -> Void
. Let's build a few of those.
func RedViewConfig(view: UIView) { view.backgroundColor = .redColor() }
func VisibleConfig(view: UIView) { view.hidden = false }
And we can use them pretty easily:
let label = UILabel()
VisibleConfig(label)
We can compose them (like super
, but without the baggage) if their types are compatible:
func RedLabelConfig(label: UILabel) {
RedViewConfig(label)
label.textColor = .redColor()
}
We can pass them around in data structures, and the compiler will apply the right covariance for us:
let configs = [RedLabelConfig, VisibleConfig]
// [UILabel -> ()]
// This has correctly typed visibleConfig as taking `UILabel`,
// even though visibleConfig takes `UIView`
// And we can apply them
for config in configs { config(label) }
Now if we want other syntaxes, we can build those pretty easily too. Something more like your original:
func applyConfig<T>(f: T -> Void, to: T) {
f(to)
}
applyConfig(VisibleConfig, to: label)
or even closer to your original:
struct Config {
static func apply<T>(config: T -> Void, to: T) { config(to) }
}
Config.apply(VisibleConfig, to: label)
The point is that just using functions here makes everything very flexible without adding any of the complexity of class inheritance or even structs.
Upvotes: 1
Reputation: 70255
Do you actually need the generic parameter B
? If your argument to:
was typed as A
as well, it could be any subtype of A
. Like such:
class View {}
class LabelView : View {}
class Config<T> {
func configure(x:T) { print ("Configured: \(x)") }
}
func applyConfig<A> (c:Config<A>, to:A) {
c.configure(to)
}
applyConfig(Config<View>(), to: LabelView())
Upvotes: 1