2017-09-25 5 views
2

句の一部としてSelfをプロトコルの一部で使用しようとすると、問題が発生しています。汎用関数で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句を使用して、2番目のパラメータの型が、1番目のパラメータの関連する型に準拠している必要があることを宣言します。

この汎用関数の要件に準拠したクラスを作成することができ、すべてが完全に機能します。例:

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

これまでのところとても良いです。しかし

、私はプロトコルの一部として、ジェネリック医薬品の同じパターンを実装する場合、それはもはや作品:

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

は、次のとおりです。

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

は、このブロックは、次のエラーがスローされます希望の動作を達成するための何らかの方法がありますか?

+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を参照してください。

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

のでAnimal.FoodSourceSelfと一致する必要があります:それはあなたのwhere句に違反しているため

これは動作しません。あなたがそれを使用する方法を見てみましょう:

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

のでSelfSteakSaladで、Lion.FoodSourceMeatです。それらは等しくないので、これは当てはまりません。

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

しかし、それは法的スウィフト(「エラー:適合要件の最初のタイプ 『T.FoodSource』ジェネリックパラメータまたは関連するタイプを参照していない」)ではありません:何が本当に意味することは、このです。問題は、T.FoodSourceが何でもできることです。それはプロトコルである必要はありません。 「自己は、任意のタイプに準拠しています」という意味のスウィフトは意味がありません。

我々は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

doesnの肉は食物に適合する?ハァー、いいえ。これは、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)でこれを一般化することです。スウィフトタイプのシステムではあまりにも多すぎます。私のお勧めは、PATを取り除き、単にジェネリックを使用することです。これらの問題のほとんどは消え去るでしょう。より強力な型システムを持ち、関連する型を持つScalaのような言語であっても、正しい答えは通常、より単純なジェネリックであり(しばしばそうでなくても、必要のないときに一般的なものにします)。

+0

詳細な応答をありがとう。適合性(すなわち、「自己:T.FoodSource」)と等価性(「自己== T.FoodSource」)との間の差異に関して、これは依然として混乱の原因である。私の例を見れば、動作するジェネリック関数では、このルールを破るようです。 「チキンサラダ」は、「ラビット」と「ライオン」の両方の食べ物として受け入れられますが、どちらのクラスでも「T.FoodSource」と正確に一致しません。 – sak

+0

これは、関数が 'Self'を呼び出さないためです。だから、「チキンサラダ」は、「肉」や「野菜」と解釈され、型を動作させます。原則として、同様のことが 'Self'(またはそれのようなもの)に対して行えますが、これはコンパイラの型エンジンを超えています。 「私は紙でうまくいかない」というプロトコル(特にPAT)には多くのことがあります。コンパイラはそれを処理できません。ジェネリック関数は、ジェネリックメソッドよりもずっと柔軟性があります。これは時間の経過とともに良くなっていますが、それでもなお真です。 –

+0

清算していただきありがとうございます。ええ、私はタイプシステムの限界に当たっていると仮定しましたが、その限界がどういうものなのかを正確に理解することは有益です。 – sak

関連する問題