Подтвердить что ты не робот

Добавить ограничения для общих параметров в расширении

У меня есть эта функция:

func flatten<Key: Hashable, Value>(dict: Dictionary<Key, Optional<Value>>) -> Dictionary<Key, Value> {
    var result = [Key: Value]()
    for (key, value) in dict {
        guard let value = value else { continue }
        result[key] = value
    }
    return result
}

Как вы можете видеть, он преобразует словарь [Key: Value?] в [Key: Value] один (без дополнительного).

Я хотел расширить класс Dictionary новым методом только для классов, значение которых является Optional любого типа, но я не могу добавить ограничения к общим параметрам словаря.

Это то, что я пробовал:

extension Dictionary where Value: Optional<Any> {
    func flatten() -> [Key: Any] {
        var result = [Key: Any]()
        for (key, value) in self {
            guard let value = value else { continue }
            result[key] = value
        }
        return result
    }
}

Но не с ошибкой:

Type 'Value' constrained to non-protocol type 'Optional<Any>'
4b9b3361

Ответ 1

Попробуйте этот код на игровой площадке:

// make sure only `Optional` conforms to this protocol
protocol OptionalEquivalent {
  typealias WrappedValueType
  func toOptional() -> WrappedValueType?
}

extension Optional: OptionalEquivalent {
  typealias WrappedValueType = Wrapped

  // just to cast `Optional<Wrapped>` to `Wrapped?`
  func toOptional() -> WrappedValueType? {
    return self
  }
}

extension Dictionary where Value: OptionalEquivalent {
  func flatten() -> Dictionary<Key, Value.WrappedValueType> {
    var result = Dictionary<Key, Value.WrappedValueType>()
    for (key, value) in self {
      guard let value = value.toOptional() else { continue }
      result[key] = value
    }
    return result
  }
}

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil]
a.flatten() //["a": "a", "c": "c"]

Поскольку вы не можете указать точный тип в выражении where расширения протокола, одним из способов вы можете точно определить тип Optional, чтобы сделать Optional UNIQUELY соответствующим протоколу (скажем OptionalEquivalent).

Чтобы получить тип обернутого значения Optional, я определил typealias WrappedValueType в пользовательском протоколе OptionalEquivalent, а затем сделал расширение необязательным, отвяжите Wrapped до WrappedValueType, затем вы можете получить тип в методе сглаживания.

Обратите внимание, что метод sugarCast предназначен только для того, чтобы отнести Optional<Wrapped> к Wrapped? (что точно так же), чтобы включить оператор guard.

UPDATE

Благодаря комментарию Роба Напира я упростил и переименовал метод sugarCast() и переименовал протокол, чтобы сделать его более понятным.

Ответ 2

Вы можете сделать это проще. Это работает с Swift 4:

extension Dictionary {
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> {
         return filter { $1 != nil }.mapValues { $0! }
    }
}

Если вам не нравится использование функций более высокого порядка или требуется совместимость с предыдущими версиями Swift, вы также можете сделать это:

extension Dictionary {
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> {
        var result: [Key: Wrapped] = [:]
        for (key, value) in self {
            guard let value = value else { continue }
            result[key] = value
        }
        return result
    }
}