2015-11-17 14 views
6

Betrachten Sie diese Klassen:Swift: Gussgenerischer Typ in demselben generischen Typ, aber mit einer Unterklasse von zugehörigen Typ

struct OrderedSet<T: Hashable> {} 

class Exercise: Hashable {} 

class StrengthExercise: Exercise {} 

class CardioExercise: Exercise {} 

Ich möchte folgendes tun:

var displayedExercises = OrderedSet<Exercise>() { 
    didSet { 
     self.tableView.reloadData() 
    } 
} 
var cardioExercises = OrderedSet<CardioExercise>() 
var strengthExercises = OrderedSet<StrengthExercise>() 


@IBAction func segmentControlChanged(segmentControl: UISegmentedControl) { 
    switch segmentControl.selectedSegmentIndex { 
    case 0:  self.displayedExercises = self.strengthExercises 
    case 1:  self.displayedExercises = self.cardioExercises 
    default: break 
    } 
} 

Aber ich bekomme diese Fehler:

Cannot assign value of type 'OrderedSet<StrengthExercise>' to type 'OrderedSet<Exercise> 

ich habe nicht ganz das, da StrengthExercise eine Unterklasse von Exercise und wird Habe alles, was OrderedSet<Exercise> erwartet.

Die Frage (n)

  • Warum ist dieser Fehler notwendig?
  • Wie schreibe ich etwas, das die Funktionalität erreicht, die ich anstrebe?



Radar eingereicht
rdar://23608799


Blog-Post auf Kovarianz und Kontra
https://www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html

+2

Ist dies ein Fall, wo ein Protokoll (Exercisable? Lol) ist besser als eine Klassenhierarchie? Oder vielleicht Kategorien/Erweiterungen? – Cocoadelica

+1

Spaß beiseite: für die didSet: Funktion der angezeigten Übung würden Sie den Aufruf wickeln, um die Tabellenansicht in einem Dispatch zu laden) in die Hauptwarteschlange async oder verlassen Sie sich auf die Schnittstelle zu sagen, dass Sie den Wert auf die Hauptwarteschlange setzen müssen? – Cocoadelica

+0

@ Cocoadelica natürlich! Schöne Idee! Es! Das Hinzufügen von 'dispatch_async()' zur Hauptwarteschlange ist ebenfalls eine gute Idee. Ich würde es vorziehen, das zu dokumentieren, denn dann wird es nur einmal geschrieben und der neue Entwickler, der sich ihm nähert, muss nichts extra lernen. Vielleicht solltest du das als Antwort anbieten? Wenn niemand eine Antwort anbietet, die auch erklärt, warum der Fehler notwendig ist, könnte ich Ihre akzeptieren :) – kylejm

Antwort

6

Ich fürchte, thi s ist derzeit nicht möglich ab Swift 2.1.Nur die folgenden Konvertierungen werden unterstützt

  • Integrierte Sammlungsarten sind kovariant für ihren Elementtyp.
  • Konvertierungen zwischen Funktionstypen werden unterstützt, die Kovarianz in Funktionsergebnistypen und Kontravarianz in Funktionsparametertypen aufweisen. (Vgl Xcode 7.1 Release Notes)

Als Objective-C-Generika-Stüt Varianz und die Fortschritte gegeben hat auf Funktion Typkonvertierungen in Swift 2.1, ich glaube, es Grund ist Typ Varianz Unterstützung zu glauben, dass in der zu Swift hinzugefügt werden Zukunft. In der Zwischenzeit, denken Sie daran, ein Radar, wie jlieske has.

In der Zwischenzeit müssen Sie die Sammlung kopieren oder einen der integrierten Sammlungstypen verwenden.

-Update seit Swift Open Source geworden: Ich glaube, die komplette Generika Abschnitt Swift 3.0 Dev Roadmap Art Varianz in 3.0 angesprochen werden anzeigt. Während die Typvarianz nicht explizit aufgerufen wird, gibt es spezielle in der Standardbibliothek enthaltene Ausnahmen (einschließlich Typvarianz).

+0

Ich frage mich, ob die Übereinstimmung mit 'CollectionType' diese Funktionalität geben würde? Einen Versuch wert? – kylejm

+0

@ kylejm es scheint, dass Sie mit 'CollectionType' recht haben, was das spezielle verkapselte Verhalten des Typsystems ermöglicht. –

+0

@kylejm hat die Antwort auf die Originalversion zurückgesetzt, basierend auf der Antwort von @ user3441734, die darauf hinweist, dass es nicht ausreicht, von 'CollectionType' zu ​​übernehmen. –

2

Da der OrderedSet<StrengthExercise> von diesem spezifischen Typ ist, kann er nicht dem allgemeineren OrderedSet<Exercise> zugewiesen werden. Denken Sie darüber nach, was passieren würde, wenn Sie nach dem Zuordnen eine Cardio-Übung an dieses OrderedSet anhängen würden.

Die Antwort könnte sein, den Inhalt der Kraftübungen an den Übungssatz anzuhängen, anstatt den gesamten typisierten Satz zuzuweisen.

+0

Ich verstehe immer noch nicht, warum das Zuweisen eines spezifischeren Typs zu einem allgemeineren Typ hier ein Problem ist. Das OrderedSet ist eine Struktur, also wird es nach Wert übergeben? Das Anhängen von etwas an 'displayedExercises' würde daher' StärkeÜbungen' nicht beeinflussen, wenn Sie vorher 'StärkeAusübungen' zugewiesen haben. Darüber hinaus können Sie '' DisplayExecises' 'nicht an 'CardioExercise' anhängen, da es vom Typ' OrderedSet 'ist. – kylejm

+0

Ich denke, ich verstehe es jetzt! Mit diesem Kommentar habe ich das Problem erkannt! Was wäre, wenn ich '' Exercises'' an '' Exercises'' anhängen würde, die tatsächlich '' StrengthExercises' enthielten! Deshalb können Sie '' AngezeigteSet 'nicht auf' AngezeigteAusübungen' zuweisen! Ich habs! Vielen Dank! – kylejm

+0

Im Nachhinein sehe ich immer noch nicht, warum dies ein Problem wäre, da 'displayedExercises' eine eigene Kopie von' OrderedSet 'haben würde, weil' OrderedSet' eine Struktur ist (pass by value). Es wäre also kein Problem, eine "Übung" anzuhängen, da diese "Übung" auch an "Stärkeübungen" angehängt würde. – kylejm

1

sollte diese

class Base {} 
class A: Base {} 
class B: Base {} 

var arrBase: Array<Base> = [] 

var arrA: Array<A> = [] 
arrA.append(A()) 

var arrB: Array<B> = [] 
arrB.append(B()) 

arrBase = arrA // no error 
arrBase = arrB // no error 

arbeiten ... Ihre Probleme woanders in Ihrem Code zu sein scheint. Können Sie uns Ihre generische Struktur OrderedSet-Implementierung zeigen? es scheint, wie Sie etwas zu tun, wie

class Base {} 
class A: Base {} 

let base = Base() 
let a = A() 

struct S<T:Base> { 
    var t: T 
} 
var s = S(t: base) 
let sa = S(t: a) 
//s = sa // error: cannot assign value of type 'S<A>' to type 'S<Base>' 
let sb = S(t: a as Base) 
s = sb 

versuchen ... das funktioniert

protocol P { 
    func whoAmI()->Void 
} 
class Base:P { 
    func whoAmI() { 
     print("I am Base") 
    } 
} 
class A: Base { 
    override func whoAmI() { 
     print("I am A") 
    } 
} 

let base = Base() 
let a = A() 

struct S<T: Base> { 
    var t: Base 
} 
var s = S(t: base) 
let sa = S(t: a) 
s = sa 

s.t.whoAmI() // I am A 

.... Jungs, build-in-Typ oder nicht

import Foundation 
// Int and Double conforms to Hashable protocol 
var a: Set<Int> = [] 
var b: Set<Double> = [] 
a = b // IMPOSSIBLE eventhough Set<T:Hashable> is build-in Swift type 

.. Wie man mit OrderedSet umgeht

import Foundation 

class Exercise: Hashable { 
    var name: String = "" 
    var hashValue: Int { 
     return name.hashValue 
    } 
} 
func ==(lhs: Exercise, rhs: Exercise) -> Bool { 
    return lhs.name == rhs.name 
} 
class StrengthExercise: Exercise {} 
class CardioExercise: Exercise {} 
var displayedExercises = Set<Exercise>() 
let strengthExercises = Set<StrengthExercise>() 
let cardioExercises = Set<CardioExercise>() 
displayedExercises = strengthExercises 

// OK, the question is how to implement OrderedSet<T:Hashable> 
// ------------------------------------------------------------------------------------------ 
// 
// OrderedSet.swift 
// Weebly 
// 
// Created by James Richard on 10/22/14. 
// Copyright (c) 2014 Weebly. All rights reserved. 
// 
// Slightly modified by user3441734 on 11/18/15 
// 
// original code OrderedSet is available under the MIT license 


/// An ordered, unique collection of objects. 
public struct OrderedSet<T: Hashable> { 
    private var contents = [T: Index]() // Needs to have a value of Index instead of Void for fast removals 
    private var sequencedContents = Array<UnsafeMutablePointer<T>>() 

    /** 
    Inititalizes an empty ordered set. 

    :return: An empty ordered set. 
    */ 
    public init() { } 

    /** 
    Initializes a new ordered set with the order and contents 
    of sequence. 

    If an object appears more than once in the sequence it will only appear 
    once in the ordered set, at the position of its first occurance. 

    :param: sequence The sequence to initialize the ordered set with. 
    :return: An initialized ordered set with the contents of sequence. 
    */ 
    public init<S: SequenceType where S.Generator.Element == T>(sequence: S) { 
     // FIXME: For some reason, Swift gives the error "Cannot convert the expression's type 'S' to type 'S'" with a regular for-in, so this is a hack to fix that. 
     var gen = sequence.generate() 
     while let object: T = gen.next() { 
      if contents[object] == nil { 
       contents[object] = contents.count 

       let pointer = UnsafeMutablePointer<T>.alloc(1) 
       pointer.initialize(object) 
       sequencedContents.append(pointer) 
      } 
     } 
    } 

    /** 
    Replace, remove, or retrieve an object in the ordered set. 

    When setting an index to nil the object will be removed. If 
    it is not the last object in the set, all subsequent objects 
    will be shifted down one position. 

    When setting an index to another object, the existing object 
    at that index will be removed. If you attempt to set an index 
    that does not currently have an object, this is a no-op. 

    :param:  index The index to retrieve or set. 
    :return: On get operations, the object at the specified index, or nil 
    if no object exists at that index. 
    */ 
    public subscript(index: Index) -> T { 
     get { 
      return sequencedContents[index].memory 
     } 

     set { 
      contents[sequencedContents[index].memory] = nil 
      contents[newValue] = index 
      sequencedContents[index].memory = newValue 
     } 
    } 


    /** 
    Locate the index of an object in the ordered set. 

    It is preferable to use this method over the global find() for performance reasons. 

    :param:  object  The object to find the index for. 
    :return: The index of the object, or nil if the object is not in the ordered set. 
    */ 
    public func indexOfObject(object: T) -> Index? { 
     if let index = contents[object] { 
      return index 
     } 

     return nil 
    } 

    /// The number of objects contained in the ordered set. 
    public var count: Int { 
     return contents.count 
    } 

    /// Whether the ordered set has any objects or not. 
    public var isEmpty: Bool { 
     return count == 0 
    } 

    /** 
    Tests if the ordered set contains an object or not. 

    :param:  object The object to search for. 
    :return: true if the object exists in the ordered set, otherwise false. 
    */ 
    public func contains(object: T) -> Bool { 
     return contents[object] != nil 
    } 

    /** 
    Appends an object to the end of the ordered set. 

    :param:  object The object to be appended. 
    */ 
    mutating public func append(object: T) { 
     if contents[object] != nil { 
      return 
     } 

     contents[object] = contents.count 

     let pointer = UnsafeMutablePointer<T>.alloc(1) 
     pointer.initialize(object) 
     sequencedContents.append(pointer) 
    } 

    /** 
    Appends a sequence of objects to the end of the ordered set. 

    :param:  objects The objects to be appended. 
    */ 
    mutating public func appendObjects<S: SequenceType where S.Generator.Element == T>(objects: S) { 
     var gen = objects.generate() 
     while let object: T = gen.next() { 
      append(object) 
     } 
    } 

    /** 
    Removes an object from the ordered set. 

    If the object exists in the ordered set, it will be removed. 
    If it is not the last object in the ordered set, subsequent 
    objects will be shifted down one position. 

    :param:  object The object to be removed. 
    */ 
    mutating public func remove(object: T) { 
     if let index = contents[object] { 
      contents[object] = nil 
      sequencedContents[index].dealloc(1) 
      sequencedContents.removeAtIndex(index) 

      for (object, i) in contents { 
       if i < index { 
        continue 
       } 

       contents[object] = i - 1 
      } 
     } 
    } 

    /** 
    Removes the given objects from the ordered set. 

    :param:  objects  The objects to be removed. 
    */ 
    mutating public func removeObjects<S: SequenceType where S.Generator.Element == T>(objects: S) { 
     var gen = objects.generate() 
     while let object: T = gen.next() { 
      remove(object) 
     } 
    } 

    /** 
    Removes an object at a given index. 

    This method will cause a fatal error if you attempt to move an object to an index that is out of bounds. 

    :param:  index  The index of the object to be removed. 
    */ 
    mutating public func removeObjectAtIndex(index: Index) { 
     if index < 0 || index >= count { 
      fatalError("Attempting to remove an object at an index that does not exist") 
     } 

     remove(sequencedContents[index].memory) 
    } 

    /** 
    Removes all objects in the ordered set. 
    */ 
    mutating public func removeAllObjects() { 
     contents.removeAll() 
     sequencedContents.removeAll() 
    } 

    /** 
    Return an OrderedSet containing the results of calling 
    `transform(x)` on each element `x` of `self` 

    :param:  transform A closure that is called for each element in the ordered set. 
    The result of the closure is appended to the new ordered set. 
    :result:  An ordered set containing the result of `transform(x)` on each element. 
    */ 
    public func map<U: Hashable>(transform: (T) -> U) -> OrderedSet<U> { 
     var result = OrderedSet<U>() 

     for object in self { 
      result.append(transform(object)) 
     } 

     return result 
    } 

    /// The first object in the ordered set, or nil if it is empty. 
    public var first: T? { 
     return count > 0 ? self[0] : nil 
    } 

    /// The last object in the ordered set, or nil if it is empty. 
    public var last: T? { 
     return count > 0 ? self[count - 1] : nil 
    } 

    /** 
    Swaps two objects contained within the ordered set. 

    Both objects must exist within the set, or the swap will not occur. 

    :param:  first The first object to be swapped. 
    :param:  second The second object to be swapped. 
    */ 
    mutating public func swapObject(first: T, withObject second: T) { 
     if let firstPosition = contents[first] { 
      if let secondPosition = contents[second] { 
       contents[first] = secondPosition 
       contents[second] = firstPosition 

       sequencedContents[firstPosition].memory = second 
       sequencedContents[secondPosition].memory = first 
      } 
     } 
    } 

    /** 
    Tests if the ordered set contains any objects within a sequence. 

    :param:  sequence The sequence to look for the intersection in. 
    :return: Returns true if the sequence and set contain any equal objects, otherwise false. 
    */ 
    public func intersectsSequence<S: SequenceType where S.Generator.Element == T>(sequence: S) -> Bool { 
     var gen = sequence.generate() 
     while let object: T = gen.next() { 
      if contains(object) { 
       return true 
      } 
     } 

     return false 
    } 

    /** 
    Tests if a the ordered set is a subset of another sequence. 

    :param:  sequence The sequence to check. 
    :return: true if the sequence contains all objects contained in the receiver, otherwise false. 
    */ 
    public func isSubsetOfSequence<S: SequenceType where S.Generator.Element == T>(sequence: S) -> Bool { 
     for (object, _) in contents { 
      if !sequence.contains(object) { 
       return false 
      } 
     } 

     return true 
    } 

    /** 
    Moves an object to a different index, shifting all objects in between the movement. 

    This method is a no-op if the object doesn't exist in the set or the index is the 
    same that the object is currently at. 

    This method will cause a fatal error if you attempt to move an object to an index that is out of bounds. 

    :param:  object The object to be moved 
    :param:  index The index that the object should be moved to. 
    */ 
    mutating public func moveObject(object: T, toIndex index: Index) { 
     if index < 0 || index >= count { 
      fatalError("Attempting to move an object at an index that does not exist") 
     } 

     if let position = contents[object] { 
      // Return if the client attempted to move to the current index 
      if position == index { 
       return 
      } 

      let adjustment = position < index ? -1 : 1 
      let range = index < position ? index..<position : position..<index 
      for (object, i) in contents { 
       // Skip items not within the range of movement 
       if i < range.startIndex || i > range.endIndex || i == position { 
        continue 
       } 

       let originalIndex = contents[object]! 
       let newIndex = i + adjustment 

       let firstObject = sequencedContents[originalIndex].memory 
       let secondObject = sequencedContents[newIndex].memory 

       sequencedContents[originalIndex].memory = secondObject 
       sequencedContents[newIndex].memory = firstObject 

       contents[object] = newIndex 
      } 

      contents[object] = index 
     } 
    } 

    /** 
    Moves an object from one index to a different index, shifting all objects in between the movement. 

    This method is a no-op if the index is the same that the object is currently at. 

    This method will cause a fatal error if you attempt to move an object fro man index that is out of bounds 
    or to an index that is out of bounds. 

    :param:  index The index of the object to be moved. 
    :param:  toIndex The index that the object should be moved to. 
    */ 
    mutating public func moveObjectAtIndex(index: Index, toIndex: Index) { 
     if ((index < 0 || index >= count) || (toIndex < 0 || toIndex >= count)) { 
      fatalError("Attempting to move an object at or to an index that does not exist") 
     } 

     moveObject(self[index], toIndex: toIndex) 
    } 

    /** 
    Inserts an object at a given index, shifting all objects above it up one. 

    This method will cause a fatal error if you attempt to insert the object out of bounds. 

    If the object already exists in the OrderedSet, this operation is a no-op. 

    :param:  object  The object to be inserted. 
    :param:  atIndex  The index to be inserted at. 
    */ 
    mutating public func insertObject(object: T, atIndex index: Index) { 
     if index > count || index < 0 { 
      fatalError("Attempting to insert an object at an index that does not exist") 
     } 

     if contents[object] != nil { 
      return 
     } 

     // Append our object, then swap them until its at the end. 
     append(object) 

     for i in Range(start: index, end: count-1) { 
      swapObject(self[i], withObject: self[i+1]) 
     } 
    } 

    /** 
    Inserts objects at a given index, shifting all objects above it up one. 

    This method will cause a fatal error if you attempt to insert the objects out of bounds. 

    If an object in objects already exists in the OrderedSet it will not be added. Objects that occur twice 
    in the sequence will only be added once. 

    :param:  objects  The objects to be inserted. 
    :param:  atIndex  The index to be inserted at. 
    */ 
    mutating public func insertObjects<S: SequenceType where S.Generator.Element == T>(objects: S, atIndex index: Index) { 
     if index > count || index < 0 { 
      fatalError("Attempting to insert an object at an index that does not exist") 
     } 

     var addedObjectCount = 0 
     // FIXME: For some reason, Swift gives the error "Cannot convert the expression's type 'S' to type 'S'" with a regular for-in, so this is a hack to fix that. 
     var gen = objects.generate() 

     // This loop will make use of our sequncedContents array to update the contents dictionary's 
     // values. During this loop there will be duplicate values in the dictionary. 
     while let object: T = gen.next() { 
      if contents[object] == nil { 
       let seqIdx = index + addedObjectCount 
       let element = UnsafeMutablePointer<T>.alloc(1) 
       element.initialize(object) 
       sequencedContents.insert(element, atIndex: seqIdx) 
       contents[object] = seqIdx 
       addedObjectCount++ 
      } 
     } 

     // Now we'll remove duplicates and update the shifted objects position in the contents 
     // dictionary. 
     for i in index + addedObjectCount..<count { 
      contents[sequencedContents[i].memory] = i 
     } 
    } 
} 

extension OrderedSet: MutableCollectionType { 
    public typealias Index = Int 
    public typealias _Element = T 
    public typealias Generator = OrderedSetGenerator<T> 

    public func generate() -> Generator { 
     return OrderedSetGenerator(set: self) 
    } 

    public var startIndex: Int { 
     return 0 
    } 

    public var endIndex: Int { 
     return count 
    } 
} 

public struct OrderedSetGenerator<T: Hashable>: GeneratorType { 
    public typealias Element = T 
    private var generator: IndexingGenerator<Array<UnsafeMutablePointer<T>>> 

    public init(set: OrderedSet<T>) { 
     generator = set.sequencedContents.generate() 
    } 

    mutating public func next() -> Element? { 
     return generator.next()?.memory 
    } 
} 



public func +<T: Hashable, S: SequenceType where S.Generator.Element == T> (lhs: OrderedSet<T>, rhs: S) -> OrderedSet<T> { 
    var joinedSet = lhs 
    joinedSet.appendObjects(rhs) 

    return joinedSet 
} 

public func +=<T: Hashable, S: SequenceType where S.Generator.Element == T> (inout lhs: OrderedSet<T>, rhs: S) { 
    lhs.appendObjects(rhs) 
} 

public func -<T: Hashable, S: SequenceType where S.Generator.Element == T> (lhs: OrderedSet<T>, rhs: S) -> OrderedSet<T> { 
    var purgedSet = lhs 
    purgedSet.removeObjects(rhs) 

    return purgedSet 
} 

public func -=<T: Hashable, S: SequenceType where S.Generator.Element == T> (inout lhs: OrderedSet<T>, rhs: S) { 
    lhs.removeObjects(rhs) 
} 

extension OrderedSet: Equatable { } 

public func ==<T: Hashable> (lhs: OrderedSet<T>, rhs: OrderedSet<T>) -> Bool { 
    if lhs.count != rhs.count { 
     return false 
    } 

    for object in lhs { 
     if lhs.contents[object] != rhs.contents[object] { 
      return false 
     } 
    } 

    return true 
} 
// ------------------------------------------------------------------------------------------ 


// finaly what do you want 

var displayedExercises1 = OrderedSet<Exercise>() 
let strengthExercises1 = OrderedSet<StrengthExercise>() 
let cardioExercises1 = OrderedSet<CardioExercise>() 
displayedExercises = strengthExercises 
+0

Das Problem ist nicht in @ Kylejms Code, das Problem ist, dass sein OrderedSet invariant erscheint. Ich vermute, dass Swift die Typvarianz für nicht eingebaute Typen nicht unterstützt. Wenn das der Grund wäre, warum Array funktioniert, muss es irgendwie speziell verkabelt sein, um kovariant zu sein. –

+0

@NicholasH. wir wissen nichts über seine generische OrderedSet .... – user3441734

+0

Ich denke nicht, dass wir etwas über seine OrderedSet sehen müssen. Es ist eindeutig eine generische Sammlung. Die Tatsache, dass das Typsystem einen Zischen mit leerer Implementierung verursacht, eliminiert seine Implementierung als Faktor. Ihr Vorschlag funktioniert um das Problem herum, indem Sie die Typinferenz "s" und "sa" sowohl "S " machen lassen, die keine passende Antwort auf @ kylejms Frage bietet, da 'sa' nicht wirklich' S 'ist. Versuchen Sie hinzufügen ': S ' nach 'let sa', um das Problem zu sehen, nämlich es würde erscheinen * Swift Generika sind invariant * (mit Ausnahme der speziellen Gehäuse eingebauten Typen wie Array) –

Verwandte Themen