2016-07-20 12 views
2

Ich habe eine generische struct, die jede Art annimmt, und eine Funktion:Wie funktioniert die Standardfunktion in Swift, abhängig vom generischen Typ?

struct Test<T> { 
    func makeSomething() -> T? { 
     print("Default implementation") 
     return nil 
    } 
} 

ich auch ein Protokoll haben, die eine statische Methode enthält, die eine Instanz von sich selbst zurückgibt:

protocol TestProtocol { 
    static func defaultValue() -> Self 
} 

ich will makeSomething Funktion, die spezialisiert werden soll, wenn TTestProtocol entspricht. Beispiel Nutzung:

struct TestItem: TestProtocol { 
    static func defaultValue() -> TestItem { 
     return TestItem() 
    } 
} 

let normalTest: Test<String> = Test() 
let normalReturn = normalTest.makeSomething() // Should use default, returns nil. 

let specialTest: Test<TestItem> = Test() 
let specialReturn = specialTest.makeSomething() // Should use specialised, returns `TestItem`. 

kann ich denke an ein paar Möglichkeiten, dies zu tun, aber keiner von ihnen arbeiten (oder ich weiß nicht, wie man sie richtig implementieren).

Option 1

eine Typ-constrained Erstellen Erweiterung für Test:

extension Test where T : TestProtocol { 
    func makeSomething() -> T? { 
     print("Special implementation") 
     return T.defaultValue() 
    } 
} 

Problem: Versuch, die makeSomething Funktion führt zu einem Fehler zu verwenden: ambiguous use of 'makeSomething()'. Ich verstehe, warum der Fehler auftritt; nur weil ich eine spezialisierte Funktion erstelle, bedeutet das nicht, dass die Standardfunktion weniger gültig ist und daher nicht weiß, welche Funktion verwendet werden soll.

Es könnte auch möglich sein, die Standardimplementierung in eine Erweiterung zu bewegen, wie gut, aber es wäre Typ Zwänge haben muß, die entlang der Linien etwas sagen ‚jede Art, die nicht-TestProtocol entsprechen‘, und Soweit ich weiß, ist das nicht möglich.

Option 2

Spezialteile auf die makeSomething Funktion hinzufügen, so dass er sein Verhalten von der Art der T abhängig ändert:

func makeSomething() -> T? { 
    if let specialType = T.self as? TestProtocol.Type { 
     print("Special implementation") 
     return specialType.defaultValue() 
    } else { 
     print("Default implementation") 
     return nil 
    } 
} 

Problem: Wie erwartet, das nicht funktioniert, seit specialType ist jetzt vom Typ TestProtocol.Type und nicht T, so bekomme ich den Fehler: cannot convert return expression of type 'TestProtocol' to return type 'T?'. Ich weiß nicht, wie man den Compiler informiert, dass T in diesem Fall ein TestProtocol ist.

Das eigentliche Problem, das ich habe, ist komplexer, aber ich denke, dass dies vereinfacht und das Problem, das ich habe, richtig darstellt. Gibt es eine Möglichkeit, in Abhängigkeit von der Übereinstimmung von T innerhalb der oben genannten Einschränkungen Standard und spezialisierte Funktionalität zu haben?

Antwort

1

Die einfachste Lösung wäre einfach Option zu verwenden, # 2 und wirft nur das Ergebnis zurück zu T?, um die Lücke mit der Tatsache zu überbrücken, dass wir nicht T.self auf eine abstrakte Art von T.self werfen können, die TestProtocol.Type entspricht auch :

func makeSomething() -> T? { 
    if let specialType = T.self as? TestProtocol.Type { 
     print("Special implementation") 
     return specialType.defaultValue() as? T 
    } else { 
     print("Default implementation") 
     return nil 
    } 
} 

als defaultValue kehrt Self, diese Besetzung sollte niemals scheitern (wir verwenden eine bedingte Besetzung als das Verfahren eine optionale zurückgibt).

Obwohl das gesagt wird, fühlt sich die Verwendung von Laufzeit-Casting nicht als eine besonders nette Lösung für dieses Problem an. Ein möglicher Weg, um die Überlastung zu erreichen, die Sie in Ihrer Option # 1 erreichen wollten, ist die Verwendung eines Protokolls - das statische Übermitteln & überlastet, wenn der angegebene zugeordnete Typ SomethingTestProtocol entspricht.

// feel free to give this a better name 
protocol MakesSomething { 
    associatedtype Something 
    func makeSomething() -> Something? 
} 

// default implementation 
extension MakesSomething { 
    func makeSomething() -> Something? { 
     return nil 
    } 
} 

protocol TestProtocol { 
    static func defaultValue() -> Self 
} 

// specialised implementation 
extension MakesSomething where Something : TestProtocol { 
    func makeSomething() -> Something? { 
     return Something.defaultValue() 
    } 
} 

struct Test<T> : MakesSomething { 
    // define T == Something 
    typealias Something = T 
} 

let normalTest : Test<String> = Test() 
let normalReturn = normalTest.makeSomething() // nil 

let specialTest : Test<TestItem> = Test() 
let specialReturn = specialTest.makeSomething() // TestItem() 

Vielleicht nicht die bequemste Lösung, da sie eine neue Art umfasst die Erstellung - aber so weit ich bewusst bin der einzige Weg, um die bedingte Überlastung Verhalten erreichen Sie‘ Nachher ist durch Protokolle. Sie können dieses Problem je nach tatsächlichem Problem, das Sie lösen möchten, in ein bereits vorhandenes Protokoll integrieren.

+0

Danke für Ihre Antwort! Ich hatte auch daran gedacht, in Option 1 eine Besetzung zu machen; Es funktioniert, aber wie du selbst, ich dachte, es war nicht sehr elegant. Ich denke, ich akzeptiere wahrscheinlich, dass es für den Moment die beste Lösung ist, dies über Protokolle zu tun. Vielleicht wird es in Zukunft bessere Methoden geben! – Robert

+0

@Robert Glücklich zu helfen! Swift ist noch relativ jung und entwickelt sich rasant weiter. Ich bin mir sicher, dass es in Zukunft bessere Lösungen geben wird :) – Hamish

0

Wenn Sie mehrere verschiedene Testprotokolle haben, kann es unangenehm werden, Abhängigkeiten zu allen in der makeSomething() -Methode zu haben.

Die Verwendung von zwei Protokollebenen kann dabei helfen, die Testprobleme von den verschiedenen Testprotokollen zu trennen.

protocol TestSpecialization 
{ 
    static func makeSomething()->Self? 
} 

class Test<T> 
{ 

    init(_ type:T.Type) {} // the init is merely for syntax candy 

    typealias TestedType = T 

    func makeSomething() -> TestedType? 
    { 
     // only one exception here rather than one for every test protocol 
     if TestedType.self is TestSpecialization.Type 
     { 
     return (TestedType.self as! TestSpecialization.Type).makeSomething() as? TestedType 
     } 
     print("default implementation") 
     return nil 
    } 
} 


// all your test protocols should conform to TestSpecialization 
// using a default implementation for makeSomething() 
// 
protocol TestProtocol:TestSpecialization 
{ 
    static func defaultValue() -> Self 
} 

extension TestProtocol 
{ 
    // this is extra work but it keeps the "special" out of the Test<T> class 
    static func makeSomething()->Self? 
    { 
     print("Special implementation") 
     return defaultValue() 
    } 
} 

// everything else works as expected 

struct TestItem: TestProtocol 
{ 
    static func defaultValue() -> TestItem 
    { 
     return TestItem() 
    } 
} 

let normalTest = Test(String) 
let normalReturn = normalTest.makeSomething() // uses default, returns nil. 

let specialTest = Test(TestItem) 
let specialReturn = specialTest.makeSomething() // uses specialised, returns `TestItem`. 
Verwandte Themen