2017-09-25 85 views
2

我在嘗試將Self作爲協議一部分的泛型函數中的where子句的一部分時遇到問題。在泛型函數中使用Self時出錯,其中子句

例如,說我有這個協議和這個定義泛型函數:

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) 
} 

功能飼料使用括號內的語句聲明的是,第一個參數必須符合Animal協議。它使用where子句聲明第二個參數的類型必須符合第一個參數的關聯類型。

有可能創建符合這個通用函數的要求的類,並且一切都很完美。例如:

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()) 

到目前爲止這麼好。

然而,當我實現仿製藥相同的模式作爲協議的一部分,它不再起作用:

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()) 

執行時,該塊將引發以下錯誤:

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

是有一些方法可以達到理想的行爲?

+0

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

+0

也許我很厚,但我不確定這究竟是如何適用於我案件。我知道所提供的例子在內容方面非常相似,但它似乎是一個單獨的問題。在鏈接帖子的情況下,類型推斷不再適用於'Cow',因爲如果沒有該方法簽名,就不能確定Cow.Food的關聯類型。在我的例子中,我不明白爲什麼不直接從提供的參數(在這種情況下是'Lion')推斷出類型。 – sak

回答

2

在討論此事之前,我強烈建議您重新考慮您的問題並簡化您的類型。一旦你在Swift中混合泛型和協議的道路上,你將會不停地與類型系統作鬥爭。部分原因是複雜的類型很複雜,即使使用非常強大的類型系統也很難使它們正確。部分原因是Swift沒有一個非常強大的類型系統。與Objective-C或Ruby相比,它確實非常強大,但它在泛型類型方面仍然相當薄弱,並且有很多概念無法表達(沒有更高級的類型,沒有辦法表示協變或逆變,沒有依賴類型,並且存在奇怪的怪癖,像協議並不總是符合自己的)。在幾乎所有與複雜類型的開發人員合作的案例中,事實證明,他們的實際程序並不需要那麼複雜。具有關聯類型的協議應被視爲高級工具;除非你真的需要他們,否則不要接觸他們。有關更多信息,請參閱Beyond Crusty

這不工作,因爲它侵犯了您where條款:

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

所以Animal.FoodSource必須匹配Self。讓我們來看看你如何使用它:

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

所以SelfSteakSaladLion.FoodSourceMeat。那些不是平等的,所以這不適用。你真正的意思是這樣的:

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

但是,這是不合法的斯威夫特(「錯誤:一致性要求第一類‘T.FoodSource’並不是指一個泛型參數或相關類型」)。問題是T.FoodSource可能是任何東西;它不一定是一個協議。 「自我符合任意類型」並不意味着Swift。

我們可以嘗試通過使FoodSource改善這種至少符合Food,但它會變得更糟:

protocol Food {} 
protocol Meat: Food {} 

protocol Animal { 
    associatedtype FoodSource: Food 
} 

然後讓獅子吃肉:

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

隱而不宣肉符合食物嗎?哈,不。這是Swift中更大的「協議不符合自己」限制的一部分。你不能僅僅將協議視爲繼承。有時他們會,有時他們不會。

你可以做的是,肉可以喂到肉食者:

protocol Meat {} 

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

和蔬菜可以供應給蔬菜食:

protocol Vegetable {} 

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

但是沒有辦法,我知道通過與關聯類型(PAT)協議進行通用。這對Swift類型系統來說太過分了。我的建議是擺脫PAT,只使用泛型。這些問題大多會消失。即使在像Scala這樣的語言中,它具有更強大的類型系統並且也有相關的類型,正確的答案通常是更簡單的泛型(通常甚至不是這樣;當我們不需要時,我們通常會使用泛型)。

+0

感謝您的詳細回覆。關於一致性(即Self:T.FoodSource)與平等(Self == T.FoodSource)之間的差異,這仍然是一個混淆之源。如果你看看我的例子,在通用函數中工作,它似乎打破了這個規則。 「ChickenSalad」被認爲是「兔子」和「獅子」的食物,即使類型與任何一類的「T.FoodSource」都不完全匹配。 – sak

+0

那是因爲函數沒有調用'Self'。所以'ChickenSalad'被解釋爲'Meat'或'Vegetable'來使這些類型起作用。原則上類似的事情可以爲'Self'(或類似的東西)完成,但這超出了編譯器的類型引擎。關於協議(特別是PAT)有很多事情,「我可以在紙上寫出如何工作」是不夠的;編譯器無法處理它。通用函數在Swift中一直比泛型方法靈活得多。隨着時間的推移,這種情況已經變得更好了,但它依然如此。 –

+0

感謝您的澄清。是的,我認爲我正在觸及類型系統的限制,但是有助於準確理解這些限制是什麼原因。 – sak