如何通过建造餐厅来了解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,一经查实,立即删除!

相关文章

组织在召唤:如何免费获取一个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;将确认序…

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…

在Centos中安装mysql

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

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…

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

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

知识力量_网络分析的力量

知识力量The most common way to store data is in what we call relational form. Most systems get analyzed as collections of independent data points. It looks something like this:存储数据的最常见方式是我们所谓的关系形式。 大多数系统作为独立数据点的集合进行分析…

SCCM PXE客户端无法加载DP(分发点)映像

上一篇文章我们讲到了一个比较典型的PXE客户端无法找到操作系统映像的故障&#xff0c;今天再和大家一起分享一个关于 PXE客户端无法加载分发点映像的问题。具体的报错截图如下&#xff1a;从报错中我们可以看到&#xff0c;PXE客户端已经成功的找到了SCCM服务器&#xff0c;并…

Docker 入门(2)技术实现和核心组成

1. Docker 的技术实现 Docker 的实现&#xff0c;主要归结于三大技术&#xff1a; 命名空间 ( Namespaces )控制组 ( Control Groups )联合文件系统 ( Union File System ) 1.1 Namespace 命名空间可以有效地帮助Docker分离进程树、网络接口、挂载点以及进程间通信等资源。L…

marlin 三角洲_带火花的三角洲湖:什么和为什么?

marlin 三角洲Let me start by introducing two problems that I have dealt time and again with my experience with Apache Spark:首先&#xff0c;我介绍一下我在Apache Spark上的经历反复解决的两个问题&#xff1a; Data “overwrite” on the same path causing data l…

eda分析_EDA理论指南

eda分析Most data analysis problems start with understanding the data. It is the most crucial and complicated step. This step also affects the further decisions that we make in a predictive modeling problem, one of which is what algorithm we are going to ch…

基于ssm框架和freemarker的商品销售系统

项目说明 1、项目文件结构 2、项目主要接口及其实现 &#xff08;1&#xff09;Index&#xff1a; 首页页面&#xff1a;展示商品功能&#xff0c;可登录或查看商品详细信息 &#xff08;2&#xff09;登录&#xff1a;/ApiLogin 3、dao层 数据持久化层&#xff0c;把商品和用户…

简·雅各布斯指数第二部分:测试

In Part I, I took you through the data gathering and compilation required to rank Census tracts by the four features identified by Jane Jacobs as the foundation of a great neighborhood:在第一部分中 &#xff0c;我带您完成了根据简雅各布斯(Jacobs Jacobs)所确定…

Docker 入门(3)Docke的安装和基本配置

1. Docker Linux下的安装 1.1 Docker Engine 的版本 社区版 ( CE, Community Edition ) 社区版 ( Docker Engine CE ) 主要提供了 Docker 中的容器管理等基础功能&#xff0c;主要针对开发者和小型团队进行开发和试验企业版 ( EE, Enterprise Edition ) 企业版 ( Docker Engi…

python:单元测试框架pytest的一个简单例子

之前一般做自动化测试用的是unitest框架&#xff0c;发现pytest同样不错&#xff0c;写一个例子感受一下 test_sample.py import cx_Oracle import config from send_message import send_message from insert_cainiao_oracle import insert_cainiao_oracledef test_cainiao_mo…

抑郁症损伤神经细胞吗_使用神经网络探索COVID-19与抑郁症之间的联系

抑郁症损伤神经细胞吗The drastic changes in our lifestyles coupled with restrictions, quarantines, and social distancing measures introduced to combat the corona virus outbreak have lead to an alarming rise in mental health issues all over the world. Social…

Docker 入门(4)镜像与容器

1. 镜像与容器 1.1 镜像 Docker镜像类似于未运行的exe应用程序&#xff0c;或者停止运行的VM。当使用docker run命令基于镜像启动容器时&#xff0c;容器应用便能为外部提供服务。 镜像实际上就是这个用来为容器进程提供隔离后执行环境的文件系统。我们也称之为根文件系统&a…