2017-09-25 1 views
2

Ich bin in einem Problem, wenn ich versuche, Self als Teil einer where Klausel in einer generischen Funktion, die Teil eines Protokolls ist zu verwenden.Fehler bei der Verwendung von Self in generische Funktion where-Klausel

Zum Beispiel sagen, ich habe dieses Protokoll und diese generische Funktion definiert:

protocol Animal { 
    associatedtype FoodSource 
    func eat(_ food:FoodSource) 
} 

// The where clause specifies that T2 must conform to 
// whatever type is T1's FoodSource associated type 
func feed<T1: Animal, T2>(animal:T1, food:T2) where T2 == T1.FoodSource { 
    animal.eat(food) 
} 

Der Funktion-Feed verwendet die klammert Aussage zu erklären, dass der erste Parameter mit dem Animal Protokoll entsprechen. Es verwendet die -Klausel, um zu deklarieren, dass der Typ des zweiten Parameters dem zugeordneten Typ des ersten Parameters entsprechen muss.

Es ist möglich, Klassen zu erstellen, die den Anforderungen dieser generischen Funktion entsprechen und alles funktioniert perfekt. Zum Beispiel:

protocol Meat {} 
protocol Vegetable {} 

class Rabbit : Animal { 
    typealias FoodSource = Vegetable 
    func eat(_ food:FoodSource) { 
     print("the Rabbit ate the \(type(of:food))") 
    } 
} 

class Lion : Animal { 
    typealias FoodSource = Meat 
    func eat(_ food:FoodSource) { 
     print("the Lion ate the \(type(of:food))") 
    } 
} 

class Carrot : Vegetable {} 
class Steak : Meat {} 
class ChickenSalad : Meat, Vegetable {} 

// works because Carrot conforms to Vegetable 
// prints: "the Rabbit ate the Carrot" 
feed(animal: Rabbit(), food: Carrot()) 

// works because Steak conforms to Meat 
// prints: "the Lion ate the Steak" 
feed(animal: Lion(), food: Steak()) 

// works because ChickenSalad conforms to Meat 
// prints: "the Lion ate the ChickenSalad" 
feed(animal: Lion(), food: ChickenSalad()) 

// works because ChickenSalad conforms to Vegetable 
// prints: "the Rabbit ate the ChickenSalad" 
feed(animal: Rabbit(), food: ChickenSalad()) 

So weit so gut.

Allerdings, wenn ich das gleiche Muster von Generika im Rahmen eines Protokolls implementieren, ist es nicht mehr funktioniert:

protocol Food { 
    func feed<T:Animal>(to:T) where Self == T.FoodSource 
} 

extension Food { 
    func feed<T:Animal>(to animal:T) where Self == T.FoodSource { 
     animal.eat(self) 
    } 
} 

class SteakSalad : Food, Meat, Vegetable {} 

SteakSalad().feed(to: Lion()) 

Wenn er ausgeführt wird, wird dieser Block den folgenden Fehler führt:

error: generic parameter 'T' could not be inferred 
SteakSalad().feed(to: Lion()) 
      ^

Ist Gibt es einen Weg, das gewünschte Verhalten zu erreichen?

+0

https://stackoverflow.com/questions/36810270/swift-protocols-with-associated-type-requirement-and-default-implementation – suhit

+0

Vielleicht bin ich dick, aber ich bin mir nicht sicher, genau, wie dies für meine gilt Fall. Ich weiß, dass die Beispiele inhaltlich sehr ähnlich sind, aber es scheint ein separates Thema zu sein. Im Fall des verknüpften Posts funktioniert Typinferenz nicht mehr auf "Kuh", da der zugehörige Typ für "Kuh.Food" ohne diese Methodensignatur nicht bestimmt werden kann. Im Fall meines Beispiels verstehe ich nicht, warum der Typ nicht direkt aus dem angegebenen Parameter (in diesem Fall "Löwe") abgeleitet werden würde. – sak

Antwort

2

Bevor Sie dies diskutieren, rate ich Ihnen dringend, Ihr Problem zu überdenken und Ihre Typen zu vereinfachen. Sobald Sie in Swift die Straße des Mischens von Generika und Protokollen durchlaufen haben, werden Sie das Typsystem nonstop bekämpfen. Ein Teil davon ist, dass komplexe Typen komplex sind und es schwierig ist, sie selbst mit einem sehr starken Typsystem korrekt zu machen. Ein Teil davon ist, dass Swift kein sehr starkes Typsystem hat. Verglichen mit Objective-C oder Ruby ist es zwar unglaublich mächtig, aber es ist immer noch ziemlich schwach um generische Typen und es gibt viele Konzepte, die man nicht ausdrücken kann (es gibt keine höher-kinierten Typen, es gibt keine Möglichkeit Kovarianz oder Kontravarianz auszudrücken) , keine abhängigen Typen, und es gibt komische Macken, wie Protokolle nicht immer mit sich selbst übereinstimmen). In fast allen Fällen, in denen ich mit einem Entwickler an ihren komplexen Typen gearbeitet habe, stellte sich heraus, dass ihr tatsächliches Programm nicht so viel Komplexität erforderte. Protokolle mit zugehörigen Typen sollten als ein fortgeschrittenes Werkzeug betrachtet werden; Greife nicht nach ihnen, außer du brauchst sie wirklich. Weitere Informationen hierzu finden Sie unter Beyond Crusty.

Das funktioniert nicht, weil es Ihre where Klausel verletzt:

func feed<T:Animal>(to:T) where Self == T.FoodSource 

So Animal.FoodSourceSelf übereinstimmen muss. Schauen wir uns an, wie Sie es verwenden:

SteakSalad().feed(to: Lion()) 

So Self ist SteakSalad und Lion.FoodSourceMeat ist. Diese sind nicht gleich, so dass dies nicht gilt. Was Sie wirklich meinen, ist dies:

func feed<T:Animal>(to animal:T) where Self: T.FoodSource 

Aber das ist nicht legal Swift („error: erster Typ‚T.FoodSource‘in Übereinstimmung Anforderung bezieht sich nicht auf einen generischen Parameter oder zugehörigen Typen“). Das Problem ist, dass T.FoodSource alles sein könnte; es muss kein Protokoll sein. "Selbst entspricht einem beliebigen Typ" ist Swift nicht sinnvoll.

Wir könnten versuchen, dies zu verbessern, indem sie zu FoodFoodSource zumindest entsprechen zu machen, aber es kommt noch schlimmer:

protocol Food {} 
protocol Meat: Food {} 

protocol Animal { 
    associatedtype FoodSource: Food 
} 

Und dann Lion machen essen Fleisch:

class Lion : Animal { 
    typealias FoodSource = Meat 

MyPlayground.playground:1:15: note: possibly intended match 'Lion.FoodSource' (aka 'Meat') does not conform to 'Food'

typealias FoodSource = Meat

Doesn 't Fleisch entspricht dem Essen? Hah, nein. Dies ist Teil der größeren Einschränkung "Protokolle entsprechen sich nicht selbst" in Swift. Sie können Protokolle nicht einfach so behandeln, wie sie Vererbung haben. Manchmal tun sie das und manchmal nicht.

Was können Sie tun, ist, dass das Fleisch zu Fleischesser eingespeist werden kann:

protocol Meat {} 

extension Meat { 
    func feed<T:Animal>(to animal:T) where T.FoodSource == Meat { 
     animal.eat(self) 
    } 
} 

Und Veg gefüttert werden können, um veg-Esser:

protocol Vegetable {} 

extension Vegetable { 
    func feed<T:Animal>(to animal:T) where T.FoodSource == Vegetable { 
     animal.eat(self) 
    } 
} 

Aber es gibt keinen Weg, ich weiß um dies generisch über Protokolle mit assoziierten Typen (PATs) zu machen. Es ist einfach zu viel für das Swift-System. Meine Empfehlung ist, die PAT loszuwerden und nur Generika zu verwenden. Die meisten dieser Probleme werden verschwinden. Sogar in einer Sprache wie Scala, die ein leistungsfähigeres Typsystem und auch verwandte Typen hat, ist die richtige Antwort in der Regel einfachere Generika (und oft nicht einmal das; wir machen Dinge oft generisch, wenn es nicht nötig ist).

+0

Danke für die ausführliche Antwort. In Bezug auf den Unterschied zwischen der Übereinstimmung (d. H. "Selbst: T.FoodSource") und der Gleichheit ("Self == T.FoodSource") ist dies immer noch eine Quelle der Verwirrung. Wenn Sie sich mein Beispiel anschauen, scheint es in der generischen Funktion, die funktioniert, diese Regel zu brechen. 'ChickenSalad' wird als Nahrung für' Rabbit' und 'Lion' akzeptiert, auch wenn der Typ für jede Klasse nicht genau mit' T.FoodSource' übereinstimmt. – sak

+0

Das ist, weil die Funktion 'Self' nicht aufruft. So wird 'ChickenSalad' als' Fleisch' oder 'Gemüse' interpretiert, um die Typen arbeiten zu lassen. Im Prinzip könnte eine ähnliche Sache für "Self" (oder etwas Ähnliches) gemacht werden, aber dies ist jenseits der Typengine des Compilers. Es gibt viele Dinge über Protokolle (insbesondere PATs), bei denen "Ich kann auf Papier herausfinden, wie das funktionieren würde" ist nicht genug; der Compiler kann damit nicht umgehen. Generische Funktionen waren in Swift immer viel flexibler als generische Methoden. Das ist mit der Zeit besser geworden, aber es ist immer noch wahr. –

+0

Danke für die Klarstellung. Ja, ich habe angenommen, dass ich an die Grenzen des Typsystems stoße, aber es ist hilfreich zu verstehen, was genau diese Grenzen sind. – sak

Verwandte Themen