2017-09-25 28 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