Как связать параметр типа generi c в функции с параметром-братом - PullRequest
0 голосов
/ 08 мая 2020

Чтобы понять происхождение вопроса, давайте начнем с некоторого кода:

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
}

И затем у меня есть структура с гетерогенным массивом типа MyProtocol:

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

если бы мне пришлось изменить одно из значений с помощью метода из Values, например:

  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
    }
}

Это было бы гладко. Проблема, с которой я столкнулся, заключается в том, что, скажем, я хотел изменить var thirdProperty: Int в элементе structB в var arr: [MyProtocol], я не смог бы сделать это, что мой mutating func set<T: MyProtocol>(at index: Int, _ newValue: T), поскольку он знает только типы MyProtocol .

Итак, мои 2 цента для решения этой проблемы использовали закрытие примерно такого типа:

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

Проблема в том, что каждый раз, когда я вызываю этот метод, мне сначала нужно понижать параметр (от MyProtocol до StructB). что больше похоже на обходной путь, который может вызвать нежелательное поведение на дороге.

Итак, я начал думать, что, возможно, есть способ ограничить параметр generi c параметром-братом примерно так (псевдокод):

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

Которая, как вы догадались, не компилируется.

Любая мысль о том, как лучше подойти к этому вопросу. TIA

Ответы [ 2 ]

2 голосов
/ 08 мая 2020

Используйте T.Type вместо MyProtocol.Type в методе set(type:at:closure:).

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
        }
    }
}

Пример:

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

Сообщите мне, если это правильное понимание вашего требования.

1 голос
/ 08 мая 2020
Решение

PGDev доходит до сути вопроса, но ИМО следующее немного проще в использовании:

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
}

Синтаксис = T.self позволяет немного упростить это, когда тип известен :

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

Другой подход, который является более гибким, но немного сложнее для вызывающего, - это замыкание, возвращающее MyProtocol, поэтому функция обновления может изменять тип. Я бы добавил это только в том случае, если бы это действительно было полезно в вашей программе:

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
}

(что очень близко к примеру PGDev, но не требует дополнительных параметров.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...