Question Comment déterminer si un générique est une option dans Swift?


Je veux étendre un tableau avec une fonction qui renverrait un nombre de tous les éléments non nuls dans un tableau. Idéalement, cela fonctionnerait avec un tableau de tous les types facultatifs ou non optionnels. J'ai essayé une variété de choses qui ne compilaient pas, écrasaient Xcode ou les deux. J'aurais supposé que cela ressemblerait à ceci:

extension Array {
    func realCount() -> Int {
        var cnt = 0
        for value in self {
            if value != nil {
                cnt++
            }
        }

        return cnt
    }
}

Ici, Swift se plaint que T n'est pas convertible en UInt8. Ou parfois MirrorDisposition ou d'autres classes aléatoires.

Donc, en supposant que c'est possible, quel est le truc?

Edit: à partir de Xcode 6 beta 5, cela compile maintenant mais ne donne pas les résultats attendus. if value != nil évalue vrai à chaque fois.


19
2017-07-31 16:49


origine


Réponses:


Vous ne pouvez pas comparer une valeur arbitraire à nil (EDIT: mais voir le commentaire de Sulthan ci-dessous; il se peut que nous devrait être en mesure de comparer des valeurs arbitraires à nil; le reste de ce paragraphe peut être vrai aujourd'hui, mais uniquement en raison d'un bogue du compilateur). Tandis que Optional y a quelques morceaux de sucre syntaxique appliqués, c’est vraiment juste un enum, et nil est juste Optional.None. Vous voulez un comportement pour un type (Optional) et un autre comportement pour tous les autres types. Swift a cela via les génériques, mais pas dans les extensions. Vous devez le transformer en une fonction:

func realCount<T>(x: [T?]) -> Int {
  return countElements(filter(x, { $0.getLogicValue() } ) )
}

func realCount<T>(x: [T]) -> Int {
  return countElements(x)
}

let l = [1,2,3]
let lop:[Int?] = [1, nil, 2]

let countL = realCount(l) // 3
let countLop = realCount(lop) // 2

Cette approche est beaucoup plus flexible. Optional est juste un des nombreux types que vous voudriez flatMap de cette façon (par exemple, vous pourriez utiliser cette même technique pour gérer Résultat).


EDIT: Vous pouvez aller plus loin en créant un protocole pour les choses que vous considérez comme "réelles". De cette façon, vous n'avez pas à limiter cela aux options. Par exemple:

protocol Realizable {
  func isReal() -> Bool
}

extension Optional: Realizable {
  func isReal() -> Bool { return self.getLogicValue() }
}

func countReal<S:Collection>(x: S) -> S.IndexType.DistanceType {
  return countElements(x)
}

func countReal<S:Collection where S.GeneratorType.Element:Realizable>(x: S) -> Int {
  return countElements(filter(x, {$0.isReal()}))
}

Cela dit, si je passe une collection de choses "réalisables", alors filtrez-les contre leur règle. Sinon, il suffit de les compter. Bien que je n’utiliserais probablement pas vraiment cette fonction (cela semble très spécial), le concept est utile. Les appelants ultérieurs peuvent ajouter de nouveaux types "réalisables" sans modifier aucun de vos codes (ni même savoir comment ils sont mis en œuvre). Et cela montre comment avoir un comportement par défaut pour les choses qui n'implémentent pas votre protocole.

BTW, j'utilise les collections juste parce qu'elles sont plus faciles à compter (et je suis un peu négligé sur les types de retour; notez que l'une est le DistanceType et l'autre est un Int). Obtenir les types corrects sur les fonctions génériques basées sur des collections est toujours un peu compliqué (et bloque souvent le compilateur). Je pense que tout cela s’améliorera dans les prochaines bêtas.


13
2017-07-31 17:59



TL; DR

En utilisant un protocole, vous pouvez étendre SequenceType pour compter le nombre de non-nils.

let array: [Int?] = [1, nil, 3]
assert(array.realCount == 2)

Si vous voulez juste le code, faites défiler jusqu'à "Solution" ci-dessous.


Je devais faire quelque chose de similaire pour créer un array.removeNils() extension méthode.

Le problème est que lorsque vous essayez de faire quelque chose comme:

extension SequenceType where Generator.Element == Optional { }

vous obtenez:

error: reference to generic type 'Optional' requires arguments in <...>
extension SequenceType where Generator.Element == Optional {
                                                  ^
generic type 'Optional' declared here

Donc, la question est, quel type devrions-nous ajouter à l'intérieur du <>? Il ne peut pas s'agir d'un type codé en dur puisque nous voulons qu'il fonctionne pour tout, alors nous voulons plutôt un générique T.

error: use of undeclared type 'T'
extension SequenceType where Generator.Element == Optional<T> {
                                                           ^

On dirait qu'il n'y a aucun moyen de le faire. Cependant, à l'aide de protocoles, vous pouvez réellement faire ce que vous voulez:

protocol OptionalType { }

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    // ...
  }
}

Maintenant, cela ne fonctionnera que sur les tableaux avec des options:

([1, 2] as! [Int]).realCount() // syntax error: type 'Int' does not conform to protocol 'OptionalType'
([1, nil, 3] as! [Int?]).realCount()

La dernière pièce du puzzle est de comparer les éléments à nil. Nous devons étendre le OptionalType protocole pour nous permettre de vérifier si un article est nil ou pas. Bien sûr, nous pourrions créer un isNil() méthode, mais ne rien ajouter à facultatif serait idéal. Heureusement, il a déjà un map fonction cela peut nous aider.

Voici un exemple de ce que le map et flatMap les fonctions ressemblent à:

extension Optional {
  func map2<U>(@noescape f: (Wrapped) -> U) -> U? {
    if let s = self {
      return f(s)
    }
    return nil
  }

  func flatMap2<U>(@noescape f: (Wrapped) -> U?) -> U? {
    if let s = self {
      return f(s)
    }
    return nil
  }
}

Remarquez comment map2 (un équivalent du map fonction) ne renvoie que f(s) si self != nil. Nous ne nous soucions pas vraiment de ce que la valeur retourne, de sorte que nous pouvons réellement le faire revenir true pour plus de clarté. Pour rendre la fonction plus facile à comprendre, j'ajoute des types explicites pour chacune des variables:

protocol OptionalType {
  associatedtype Wrapped
  @warn_unused_result
  func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    var count = 0
    for element: Generator.Element in self {
      let optionalElement: Bool? = element.map {
        (input: Self.Generator.Element.Wrapped) in
        return true
      }
      if optionalElement != nil {
        count += 1
      }
    }
    return count
  }
}

Pour clarifier, voici ce à quoi les types génériques correspondent:

  • OptionalType.Wrapped == Int
  • SequenceType.Generator.Element == Facultatif
  • SequenceType.Generator.Element.Wrapped == Int
  • map.U == Bool

Bien sûr, realCount peut être implémenté sans tous ces types explicites, et en utilisant $0 au lieu de true cela nous empêche d'avoir besoin de spécifier _ in dans le map fonction.


Solution

protocol OptionalType {
  associatedtype Wrapped
  @warn_unused_result
  func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension Optional: OptionalType {}

extension SequenceType where Generator.Element: OptionalType {
  func realCount() -> Int {
    return filter { $0.map { $0 } != nil }.count
  }
}

// usage:
assert(([1, nil, 3] as! [Int?]).realCount() == 2)

L'essentiel à noter est que $0 est un Generator.Element (c'est à dire. OptionalType) et $0.map { $0 } le convertit en un Generator.Element.Wrapped? (par exemple, Int?). Generator.Element ou même OptionalType ne peut pas être comparé à nil, mais Generator.Element.Wrapped? peut être comparé à nil.


1
2017-07-26 06:10