如何通过建造餐厅来了解Scala差异
I understand that type variance is not fundamental to writing Scala code. It's been more or less a year since I've been using Scala for my day-to-day job, and honestly, I've never had to worry much about it.
我了解類型差異并不是編寫Scala代碼的基礎。 自從我在日常工作中使用Scala以來,已經差不多一年了,說實話,我從來沒有為此擔心過。
However, I think it is an interesting "advanced" topic, so I started to study it. It is not easy to grasp it immediately, but with the right example, it might be a little bit easier to understand. Let me try using a food-based analogy...
但是,我認為這是一個有趣的“高級”主題,因此我開始研究它。 立即掌握它并不容易,但是有了正確的例子,可能會更容易理解。 讓我嘗試使用基于食物的類比...
什么是類型差異? (What is type variance?)
First of all, we have to define what type variance is. When you develop in an Object-Oriented language, you can define complex types. That means that a type may be parametrized using another type (component type).
首先,我們必須定義什么類型差異。 使用面向對象的語言進行開發時,可以定義復雜的類型。 這意味著可以使用另一個類型(組件類型)對一個類型進行參數化。
Think of List for example. You cannot define a List without specifying which types will be inside the list. You do it by putting the type contained in the list inside square brackets: List[String]. When you define a complex type, you can specify how it will vary its subtype relationship according to the relation between the component type and its subtypes.
例如以List為例。 你不能定義List沒有指定哪些類型將是名單內。 您可以通過將列表中包含的類型放在方括號內來實現: List[String] 。 定義復雜類型時,可以指定如何根據組件類型與其子類型之間的關系來更改其子類型關系。
Ok, sounds like a mess... Let's get a little practical.
好吧,聽起來像是一團糟...讓我們實際一點。
建立餐廳帝國 (Building a restaurant empire)
Our goal is to build an empire of restaurants. We want generic and specialised restaurants. Every restaurant we will open needs a menu composed of different recipes, and a (possibly) starred chef.
我們的目標是建立餐廳帝國。 我們想要普通和專門的餐廳。 我們將要開設的每家餐廳都需要一個菜單??,菜單中包含不同的食譜,以及一位(可能是)主廚。
The recipes can be composed of different kinds of food (fish, meat, white meat, vegetables, etc.), while the chef we hire has to be able to cook that kind of food. This is our model. Now it's coding time!
食譜可以由不同種類的食物(魚,肉,白肉,蔬菜等)組成,而我們雇用的廚師必須能夠烹飪這種食物。 這是我們的模型。 現在是編碼時間!
不同種類的食物 (Different types of food)
For our food-based example, we start by defining the Trait Food, providing just the name of the food.
對于以食物為基礎的示例,我們首先定義Trait Food ,僅提供Trait Food名稱。
trait Food {def name: String}Then we can create Meat and Vegetable, that are subclasses of Food.
然后我們可以創建Meat和Vegetable ,它們是Food子類。
class Meat(val name: String) extends Food class Vegetable(val name: String) extends FoodIn the end, we define a WhiteMeat class that is a subclass of Meat.
最后,我們定義了WhiteMeat類,它是Meat的子類。
class WhiteMeat(override val name: String) extends Meat(name)Sounds reasonable right? So we have this hierarchy of types.
聽起來合理吧? 因此,我們具有這種類型的層次結構。
We can create some food instances of various type. They will be the ingredients of the recipes we are going to serve in our restaurants.
我們可以創建一些各種類型的食物實例。 它們將成為我們將在餐廳中提供的食譜的成分。
// Food <- Meat val beef = new Meat("beef")// Food <- Meat <- WhiteMeat val chicken = new WhiteMeat("chicken") val turkey = new WhiteMeat("turkey")// Food <- Vegetable val carrot = new Vegetable("carrot") val pumpkin = new Vegetable("pumpkin")配方,協變類型 (Recipe, a covariant type)
Let's define the covariant type Recipe. It takes a component type that expresses the base food for the recipe - that is, a recipe based on meat, vegetable, etc.
讓我們定義協變類型Recipe 。 它采用表示配方基本食物的成分類型-即基于肉,蔬菜等的配方。
trait Recipe[+A] {def name: Stringdef ingredients: List[A]}The Recipe has a name and a list of ingredients. The list of ingredients has the same type of Recipe. To express that the Recipe is covariant in its type A, we write it as Recipe[+A]. The generic recipe is based on every kind of food, the meat recipe is based on meat, and a white meat recipe has just white meat in its list of ingredients.
Recipe有名稱和成分清單。 配料表具有相同的Recipe類型。 為了表示Recipe在其類型A是協變A ,我們將其寫為Recipe[+A] 。 通用食譜基于每種食物,肉類食譜基于肉類,而白肉食譜中的成分表中僅包含白肉。
case class GenericRecipe(ingredients: List[Food]) extends Recipe[Food] {def name: String = s"Generic recipe based on ${ingredients.map(_.name)}"} case class MeatRecipe(ingredients: List[Meat]) extends Recipe[Meat] {def name: String = s"Meat recipe based on ${ingredients.map(_.name)}"} case class WhiteMeatRecipe(ingredients: List[WhiteMeat]) extends Recipe[WhiteMeat] {def name: String = s"Meat recipe based on ${ingredients.map(_.name)}"}A type is covariant if it follows the same relationship of subtypes of its component type. This means that Recipe follows the same subtype relationship of its component Food.
如果類型遵循其組件類型的子類型的相同關系,則該類型是協變的。 這意味著, Recipe遵循其組成食品的相同子類型關系。
Let's define some recipes that will be part of different menus.
讓我們定義一些食譜,這些食譜將成為不同菜單的一部分。
// Recipe[Food]: Based on Meat or Vegetable val mixRecipe = new GenericRecipe(List(chicken, carrot, beef, pumpkin)) // Recipe[Food] <- Recipe[Meat]: Based on any kind of Meat val meatRecipe = new MeatRecipe(List(beef, turkey)) // Recipe[Food] <- Recipe[Meat] <- Recipe[WhiteMeat]: Based only on WhiteMeat val whiteMeatRecipe = new WhiteMeatRecipe(List(chicken, turkey))主廚,一個反型 (Chef, a contravariant type)
We defined some recipes, but we need a chef to cook them. This gives us the chance to talk about contravariance. A type is contravariant if it follows an inverse relationship of subtypes of its component type. Let's define our complex type Chef, that is contravariant in the component type. The component type will be the food that the chef can cook.
我們定義了一些食譜,但我們需要一名廚師來烹飪。 這使我們有機會談論自變量。 如果類型遵循其組件類型的子類型的逆關系,則該類型是協變的。 讓我們定義復雜類型Chef ,它與組件類型相反。 成分類型將是廚師可以烹飪的食物。
trait Chef[-A] {def specialization: Stringdef cook(recipe: Recipe[A]): String }A Chef has a specialisation and a method to cook a recipe based on a specific food. We express that it is contravariant writing it as Chef[-A]. Now we can create a chef able to cook generic food, a chef able to cook meat and a chef specialised on white meat.
Chef具有專門知識和一種根據特定食物烹制食譜的方法。 我們表示將其寫為Chef[-A] 。 現在,我們可以創建一個能夠烹飪通用食品的廚師,一個能夠烹飪肉類的廚師和一個專門從事白肉的廚師。
class GenericChef extends Chef[Food] {val specialization = "All food"override def cook(recipe: Recipe[Food]): String = s"I made a ${recipe.name}" } class MeatChef extends Chef[Meat] {val specialization = "Meat"override def cook(recipe: Recipe[Meat]): String = s"I made a ${recipe.name}" } class WhiteMeatChef extends Chef[WhiteMeat] {override val specialization = "White meat"def cook(recipe: Recipe[WhiteMeat]): String = s"I made a ${recipe.name}" }Since Chef is contravariant, Chef[Food] is a subclass of Chef[Meat] that is a subclass of Chef[WhiteMeat]. This means that the relationship between subtypes is the inverse of its component type Food.
由于Chef是協變的,因此Chef[Food]是Chef[Meat]的子類,而后者是Chef[WhiteMeat]的子類。 這意味著子類型之間的關系與其組件類型Food相反。
Ok, we can now define different chef with various specialization to hire in our restaurants.
好的,我們現在可以定義各種專業的不同廚師來在我們的餐廳聘用。
// Chef[WhiteMeat]: Can cook only WhiteMeat val giuseppe = new WhiteMeatChef giuseppe.cook(whiteMeatRecipe)// Chef[WhiteMeat] <- Chef[Meat]: Can cook only Meat val alfredo = new MeatChef alfredo.cook(meatRecipe) alfredo.cook(whiteMeatRecipe)// Chef[WhiteMeat]<- Chef[Meat] <- Chef[Food]: Can cook any Food val mario = new GenericChef mario.cook(mixRecipe) mario.cook(meatRecipe) mario.cook(whiteMeatRecipe)餐廳,東西匯集 (Restaurant, where things come together)
We have recipes, we have chefs, now we need a restaurant where the chef can cook a menu of recipes.
我們有食譜,我們有廚師,現在我們需要一家餐廳,廚師可以在這里烹飪菜單。
trait Restaurant[A] {def menu: List[Recipe[A]]def chef: Chef[A]def cookMenu: List[String] = menu.map(chef.cook) }We are not interested in the subtype relationship between restaurants, so we can define it as invariant. An invariant type does not follow the relationship between the subtypes of the component type. In other words, Restaurant[Food] is not a subclass or superclass of Restaurant[Meat]. They are simply unrelated. We will have a GenericRestaurant, where you can eat different type of food. The MeatRestaurant is specialised in meat-based dished and the WhiteMeatRestaurant is specialised only in dishes based on white meat. Every restaurant to be instantiated needs a menu, that is a list of recipes, and a chef able to cook the recipes in the menu. Here is where the subtype relationship of Recipe and Chef comes into play.
我們對餐廳之間的子類型關系不感興趣,因此可以將其定義為不變的。 不變類型不遵循組件類型的子類型之間的關系。 換句話說, Restaurant[Food]不是Restaurant[Meat]的子類或超類。 它們根本無關。 我們將設有GenericRestaurant ,您可以在這里吃不同類型的食物。 MeatRestaurant專供肉類菜肴, WhiteMeatRestaurant僅專供WhiteMeatRestaurant菜肴。 每個要實例化的餐廳都需要一個菜單??,該菜單是食譜列表,并且廚師可以在菜單中烹飪食譜。 這是Recipe和Chef的子類型關系起作用的地方。
case class GenericRestaurant(menu: List[Recipe[Food]], chef: Chef[Food]) extends Restaurant[Food] case class MeatRestaurant(menu: List[Recipe[Meat]], chef: Chef[Meat]) extends Restaurant[Meat] case class WhiteMeatRestaurant(menu: List[Recipe[WhiteMeat]], chef: Chef[WhiteMeat]) extends Restaurant[WhiteMeat]Let's start defining some generic restaurants. In a generic restaurant, the menu is composed of recipes of various type of food. Since Recipe is covariant, a GenericRecipe is a superclass of MeatRecipe and WhiteMeatRecipe, so I can pass them to my GenericRestaurant instance. The thing is different for the chef. If the Restaurant requires a chef that can cook generic food, I cannot put in it a chef able to cook only a specific one. The class Chef is covariant, so GenericChef is a subclass of MeatChef that is a subclass of WhiteMeatChef. This implies that I cannot pass to my instance anything different from GenericChef.
讓我們開始定義一些通用餐廳。 在一家普通餐廳中,菜單由各種食物的食譜組成。 由于Recipe是協變的,因此GenericRecipe是MeatRecipe和WhiteMeatRecipe的超類,因此我可以將它們傳遞給GenericRestaurant實例。 對于廚師而言,情況有所不同。 如果餐廳需要一位可以烹制普通食品的廚師,那么我不能放入只能烹制特定食品的廚師。 類Chef是協變的,所以GenericChef是的子類MeatChef是的子類WhiteMeatChef 。 這意味著我不能將與GenericChef不同的任何東西傳遞給我的實例。
val allFood = new GenericRestaurant(List(mixRecipe), mario) val foodParadise = new GenericRestaurant(List(meatRecipe), mario) val superFood = new GenericRestaurant(List(whiteMeatRecipe), mario)The same goes for MeatRestaurant and WhiteMeatRestaurant. I can pass to the instance only a menu composed of more specific recipes then the required one, but chefs that can cook food more generic than the required one.
MeatRestaurant和WhiteMeatRestaurant 。 我只能將由所需菜單組成的菜單傳遞給實例,但是該菜單可以烹制比所需菜單更通用的食物。
val meat4All = new MeatRestaurant(List(meatRecipe), alfredo) val meetMyMeat = new MeatRestaurant(List(whiteMeatRecipe), mario) val notOnlyChicken = new WhiteMeatRestaurant(List(whiteMeatRecipe), giuseppe) val whiteIsGood = new WhiteMeatRestaurant(List(whiteMeatRecipe), alfredo) val wingsLovers = new WhiteMeatRestaurant(List(whiteMeatRecipe), mario)That's it, our empire of restaurants is ready to make tons of money!
就是這樣,我們的餐廳帝國已準備好賺很多錢!
結論 (Conclusion)
Ok guys, in this story I did my best to explain type variances in Scala. It is an advanced topic, but it is worth to know just out of curiosity. I hope that the restaurant example can be of help to make it more understandable. If something is not clear, or if I wrote something wrong (I'm still learning!) don't hesitate to leave a comment!
好的,在這個故事中,我盡力解釋了Scala中的類型差異。 這是一個高級主題,但是出于好奇,值得了解。 我希望餐館的例子可以幫助使它更容易理解。 如果不清楚,或者我寫錯了什么(我還在學習!),請隨時發表評論!
See you! ?
再見! ?
翻譯自: https://www.freecodecamp.org/news/understand-scala-variances-building-restaurants/
總結
以上是生活随笔為你收集整理的如何通过建造餐厅来了解Scala差异的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么梦到帅哥
- 下一篇: aws 弹性三剑客_AWS和弹性:超越用