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.

然后我们可以创建MeatVegetable ,它们是Food子类。

class Meat(val name: String) extends Food
class Vegetable(val name: String) extends Food

In 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菜肴。 每个要实例化的餐厅都需要一个菜单​​,该菜单是食谱列表,并且厨师可以在菜单中烹饪食谱。 这是RecipeChef的子类型关系起作用的地方。

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是协变的,因此GenericRecipeMeatRecipeWhiteMeatRecipe的超类,因此我可以将它们传递给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.

MeatRestaurantWhiteMeatRestaurant 。 我只能将由所需菜单组成的菜单传递给实例,但是该菜单可以烹制比所需菜单更通用的食物。

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差异相关推荐

  1. Scala与Java差异(六)之类定义

    一.类定义 (1)定义类,包含field以及方法 class ScalaClass {private var field = "one"def aMethod() { print( ...

  2. Scala与Java差异(五)之Map与Tuple

    一. 创建Map (1)创建Map // 创建一个不可变的Map val ages = Map("Leo" -> 30, "Jen" -> 25, ...

  3. Scala与Java差异(四)之数组操作

    一.数组操作之Array.ArrayBuffer以及遍历数组 (1)Array 在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组.此外,由于Scala与Java都是运行在J ...

  4. Scala与Java差异(三)之函数

    一.函数定义 (1)函数的定义与调用 在Scala中定义函数时,需要定义函数的函数名.参数.函数体. 第一个函数如下所示: def sayHello(name: String, age: Int) = ...

  5. Scala与Java差异(二)之条件控制与循环

    一.if表达式 (1)if表达式的定义 在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值. 例如,val age = 30; if (age > 18) 1 els ...

  6. Scala与Java差异(一)之基础语法

    一.Scala解释器的使用 (1)REPL Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环). scala解释器也被称为REPL,会快 ...

  7. Scala与Java语言的差异

    1.源文件后缀名 Java:.java Scala:.scala 2.变量 Java: int param1 = 100; int param2 Scala: 格式: var VariableName ...

  8. 大卫·波拉克(David Pollak)和迪克·沃尔(Dick Wall)讨论了采用Scala的障碍

    著名的Scala倡导者David Pollak写了一篇博客文章, "是的,弗吉尼亚州,Scala很难" ,这在Scala社区引起了一些麻烦. 该帖子声称Scala试图做太多事情,I ...

  9. 【连载】Scala程序设计:Java虚拟机多核编程实战——简介

    可以在JVM上编程的语言有很多.通过这本书,我希望让你相信花时间学习Scala是值得的. Scala语言为并发.表达性和可扩展性而设计.这门语言及其程序库可以让你专注于问题领域,而无需深陷于诸如线程和 ...

最新文章

  1. 项目管理指标_项目经理必须掌握的九大项目管理问题!
  2. vue脚手架解决跨域问题-------配置反向代理
  3. 51nod 3 * problem
  4. git 解决多人修改相同的文件导致的冲突
  5. 移动硬盘加密方法赏析
  6. Web.config详解
  7. 英特尔SVT-AV1 0.8 AV1视频编码基准发布
  8. s5pv210——时钟系统
  9. java struts 框架_java中struts 框架的实现
  10. js中常用方法以及document.readyState 判断页面是否加载完成 complete和interactive
  11. App Store审核标准
  12. 销售自用计算机损益计入哪里,用友创业者4.0下的ERP沙盘模拟经营规则中,销售所需紧急采购产品时,按成品直接成本的(    )倍直接扣除现金,付款即到货,紧急采购多付出的成本计入费用表损失项。...
  13. 匿名函数的this指向
  14. 输电线缺陷检测 计算机工程与设计,小波去噪和混沌理论应用于输电线缺陷检测-计算机工程与应用.pdf...
  15. 来自百度,为什么要重构(Refactoring)
  16. 解决Android studio 导入工程慢的方法
  17. JTAG/C2 接口定义
  18. 阅读记录-统计你的每一次读书和笔‪记
  19. catgroup linux_linux中/etc/group文件详解
  20. 解决开启TCP/IP筛选,使用Serv-u 需要开放的端口

热门文章

  1. 【Leetcode | 12】342. 4的幂
  2. Java集合类中绝对占有一席之地的List,涨薪7K!
  3. vSphere HA 原理与配置
  4. JDK 下载相关资料
  5. [博客..配置?]博客园美化
  6. Android App 的主角:Activity
  7. javascript之 原生document.querySelector和querySelectorAll方法
  8. PHP移动互联网开发笔记(3)——运算符
  9. 优秀的SharePoint 2013开发工具有哪些(二)
  10. 怎样配置键盘最方便,以及一些设计的思考