简体   繁体   中英

How to constraint generic type to another generic type in Swift?

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.

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())

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM