如何通过建造餐厅来了解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.

然后我们可以创建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.

听起来合理吧? 因此,我们具有这种类型的层次结构。

food subtype relationship

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遵循其组成食品的相同子类型关系。

recipe subtype relationship

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相反。

chef subtype relationship

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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391324.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux的/etc/passwd、/etc/shadow、/etc/group和/etc/gshadow

1./etc/passwd 存储用户信息 [rootoldboy ~]# head /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin 一行记录对应着一个用户&#xff0c;每行记录被冒号:分隔为7个字段&#xff0c;这7个字段的具体含…

组织在召唤:如何免费获取一个js.org的二级域名

之前我是使用wangduanduan.github.io作为我的博客地址&#xff0c;后来觉得麻烦&#xff0c;有把博客关了。最近有想去折腾折腾。先看效果&#xff1a;wdd.js.org 如果你不了解js.org可以看看我的这篇文章:一个值得所有前端开发者关注的网站js.org 前提 已经有了github pages的…

linkedin爬虫_您应该在LinkedIn上关注的8个人

linkedin爬虫Finding great mentors are hard to come by these days. With so much information and so many opinions flooding the internet, finding an authority in a specific field can be quite tough.这些天很难找到优秀的导师。 互联网上充斥着如此众多的信息和众多…

重学TCP协议(4) 三次握手

1. 三次握手 请求端&#xff08;通常称为客户&#xff09;发送一个 S Y N段指明客户打算连接的服务器的端口&#xff0c;以及初始序号。这个S Y N段为报文段1。服务器发回包含服务器的初始序号的 S Y N报文段&#xff08;报文段2&#xff09;作为应答。同时&#xff0c;将确认序…

[设计模式]State模式

《Java与模式》 又称状态对象模式。状态模式是对象的行为模式。GOF95 一个对象的行为取决于一个或者多个动态变化的属性&#xff0c;这样的属性叫做状态。这样的对象叫做有状态的对象&#xff08;stateful&#xff09;。 状态模式把一个所研究的对象的行为包装在不同的状态对象…

java温故笔记(二)java的数组HashMap、ConcurrentHashMap、ArrayList、LinkedList

为什么80%的码农都做不了架构师&#xff1f;>>> HashMap 摘要 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK&#xff08;Java Developmet Kit&#xff09;版本的更新&#xff0c;JDK1.8对HashMap底层的实现进行了优化&#xff0c;例…

前置交换机数据交换_我们的数据科学交换所

前置交换机数据交换The DNC Data Science team builds and manages dozens of models that support a broad range of campaign activities. Campaigns rely on these model scores to optimize contactability, volunteer recruitment, get-out-the-vote, and many other piec…

aws 弹性三剑客_AWS和弹性:超越用户需求

aws 弹性三剑客I’ll assume that, one way or another, you’re already familiar with many of AWS’s core deployment services. That means you now know about:我假设您已经熟悉许多AWS的核心部署服务。 这意味着您现在知道&#xff1a; • EC2 instances and AMIs (Ama…

leetcode 368. 最大整除子集(dp)

给你一个由 无重复 正整数组成的集合 nums &#xff0c;请你找出并返回其中最大的整除子集 answer &#xff0c;子集中每一元素对 (answer[i], answer[j]) 都应当满足&#xff1a; answer[i] % answer[j] 0 &#xff0c;或 answer[j] % answer[i] 0 如果存在多个有效解子集&a…

在Centos中安装mysql

下载mysql这里是通过安装Yum源rpm包的方式安装,所以第一步是先下载rpm包 1.打开Mysql官网 https://www.mysql.com/, 点击如图选中的按钮 点击如图框选的按钮 把页面拉倒最下面,选择对应版本下载,博主这里用的是CentOS7 下载完成后上传到服务器,由于是yum源的安装包,所以…

硕士可以跟别的导师做实验吗_如何成为一名导师可以成为双刃剑

硕士可以跟别的导师做实验吗Mentoring is the ability to give advise or train someone, often times, who is less knowledgeable in a particular field. This is pretty much common place in tech companies. There you usually have senior developers who, besides bein…

linux中权限对文件和目录的意义

1.权限对文件的意义&#xff1a; 读&#xff1a;可查看文件的内容 写&#xff1a;可修改文件的内容&#xff08;但不能删除文件&#xff09; 执行&#xff1a;可执行文件 2.权限对目录的意义&#xff1a; 读&#xff1a;可以查看目录下的内容&#xff0c;即可以读取该目录下的结…

Docker 入门(1)虚拟化和容器

1 虚拟化 虚拟化是为一些组件&#xff08;例如虚拟应用、服务器、存储和网络&#xff09;创建基于软件的&#xff08;或虚拟&#xff09;表现形式的过程。它是降低所有规模企业的 IT 开销&#xff0c;同时提高其效率和敏捷性的最有效方式。 1.1 虚拟化用于程序跨平台兼容 要…

量子相干与量子纠缠_量子分类

量子相干与量子纠缠My goal here was to build a quantum deep neural network for classification tasks, but all the effort involved in calculating errors, updating weights, training a model, and so forth turned out to be completely unnecessary. The above circu…

三角函数式的化简

前言 为什么需要化简三角函数式&#xff1f; 一、什么是三角函数式的化简&#xff1f; 二、三角函数式的化简标准是什么&#xff1f; 三、三角函数式化简可能用到的变形&#xff1a; 弦切互化&#xff0c;1的代换&#xff0c;通分约分&#xff0c;配方展开&#xff0c;提取公因…

Python -- xlrd,xlwt,xlutils 读写同一个Excel

最近开始学习python,想做做简单的自动化测试&#xff0c;需要读写excel,然后就找到了xlrd来读取Excel文件&#xff0c;使用xlwt来生成Excel文件&#xff08;可以控制Excel中单元格的格式&#xff09;&#xff0c;需要注意的是&#xff0c;用xlrd读取excel是不能对其进行操作的&…

计算机工程师分级_这些是每个计算机工程师都应该知道的数字

计算机工程师分级In 2010, Jeff Dean from Google gave a wonderful talk at Stanford that made him quite famous. In it, he discussed a few numbers that are relevant to computing systems. Then Peter Norvig published those numbers for the first time on the inter…

leetcode 377. 组合总和 Ⅳ(dp)

给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保证答案符合 32 位整数范围。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3], target 4 输出&#xff1a;7 解释&…

1.4- 定时任务总结之九句箴言

1.4定时任务之九句箴言九句箴言---- 不会九句箴言别做运维1.定时任务规则之前加注释2.使用脚本代替命令行制定定时任务3.定时任务中date命令%的特殊含义定时任务中,%表示回车 -----可以使用\转义4.运行脚本一定要用/bin/sh或sh脚本不必须有x权限5.定时任务中-命令或脚本的输出…

ubuntu 18.04 vi里面方向键变成abcd 处理办法

sudo apt-get remove vim-common sudo apt-get install vim 转载于:https://www.cnblogs.com/testing-BH/p/11506400.html