简体   繁体   中英

How To constraint parameter of generic type in function, to sibling parameter

To understand the origin of the question, let's start with some code:

protocol MyProtocol {
   var val1: Int { get set }
}


struct StructA: MyProtocol {
   var val1: Int
   var structAVal: Int
}


struct StructB: MyProtocol {
   var val1: Int
   var structBVal: Int 
   var thirdProperty: Int
}

And then I have a struct with a heterogeneous array of type MyProtocol :

struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]
}

if I was to change one of the values with a method in Values such as:

  struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]

    mutating func set<T: MyProtocol>(at index: Int, _ newValue: T) {
        arr[index] = newValue
    }
}

That would be smooth. The problem which I am facing is, say I wanted to change var thirdProperty: Int in the structB item in var arr: [MyProtocol] , I would not be able to do so which my mutating func set<T: MyProtocol>(at index: Int, _ newValue: T) , since It only knows of MyProtocol types.

So my 2 cents to resolve this matter was using a closure something like this:

 mutating func set<T: MyProtocol>(at index: Int, closure: (T?) -> (T)) {
        arr[index] = closure(arr[index] as? T)
 }

The problem with this is that every time I invoke this method, I would first need to downcast the parameter (from MyProtocol to StructB ). which seems more of a workaround which could invite unwanted behaviours along the road.

So I started thinking maybe there is a way to constraint the generic parameter to a sibling parameter something like this (pseudo code):

 mutating func set<T: MyProtocol>(type: MyProtocol.Type, at index: Int, closure: (T?) -> (T)) where T == type {
        arr[index] = closure(arr[index] as? T)
}

Which as you guessed, does not compile.

Any thought on how to approach this matter in a better manner. TIA

Use T.Type instead of MyProtocol.Type in the set(type:at:closure:) method.

struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0, thirdProperty: 0)]

    mutating func set<T: MyProtocol>(type: T.Type, at index: Int, closure: ((T?) -> (T?))) {
        if let value = closure(arr[index] as? T) {
            arr[index] = value
        }
    }
}

Example:

var v = Values()
v.set(type: StructB.self, at: 1) {
    var value = $0
    value?.thirdProperty = 20
    return value
}

Do let me know if this is the right understanding of your requirement.

PGDev's solution gets to the heart of the question, but IMO the following is a bit easier to use:

enum Error: Swift.Error { case unexpectedType }
mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
                                     applying: ((inout T) throws -> Void)) throws {
    guard var value = arr[index] as? T else { throw Error.unexpectedType }
    try applying(&value)
    arr[index] = value
}

...

var v = Values()
try v.set(type: StructB.self, at: 1) {
    $0.thirdProperty = 20
}

The = T.self syntax allows this to be simplified a little when the type is known:

func updateThirdProperty(v: inout StructB) {
    v.thirdProperty = 20
}
try v.set(at: 1, applying: updateThirdProperty)

Another approach that is more flexible, but slightly harder on the caller, would be a closure that returns MyProtocol, so the updating function can modify the type. I'd only add this if it were actually useful in your program:

mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
                                 applying: ((T) throws -> MyProtocol)) throws {
    guard let value = arr[index] as? T else { throw Error.unexpectedType }
    arr[index] = try applying(value)
}

...

try v.set(type: StructB.self, at: 1) {
    var value = $0
    value.thirdProperty = 20
    return value // This could return a StructA, or any other MyProtocol
}

(Which is very close to PGDev's example, but doesn't require Optionals.)

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