首页 > 其他分享 >[Typescript 5] Intro to Variants (keyword in & out)

[Typescript 5] Intro to Variants (keyword in & out)

时间:2024-02-01 16:12:17浏览次数:35  
标签:function cookies Snack package Typescript Cookie new Variants Intro

Covariance - producer - out - function return position - same arrow direction

Contravariance - packager - in - function param position - different arrow direction

Invariance - both producer and packager - one in function return position and another in function param position - no arrow

Bivariance - quality checker - in both function return position and function param position - no arrow

 

We’re writing software that controls machinery at a snack-making factory. Let’s start with a base class and two subclasses.

class Snack {
  protected constructor(
    public readonly petFriendly: boolean) {}
}
 
class Pretzel extends Snack {
  constructor(public readonly salted = true) {
    super(!salted)
  }
}
 
class Cookie extends Snack {
  public readonly petFriendly: false = false
  constructor(
    public readonly chocolateType: 'dark' | 'milk' | 'white') {
    super(false)
  }
}

The object oriented inheritance at play makes it pretty easy to understand which of these is a subtype of the other. Cookie is a subtype of Snack, or in other words.

All Cookies are also Snacks, but not all Snacks are Cookies

 

Covariance

Our factory needs to model machines that produce these items. We plan for there to be many types of snacks, so we should build a generalized abstraction for a Producer<T>

interface Producer<T> {
  produce: () => T;
}

We start out with two kinds of machines

  • snackProducer - which makes Pretzels and Cookiess at random
  • cookieProducer - which makes only Cookies
let cookieProducer: Producer<Cookie> = {
  produce: () => new Cookie('dark')
};
 
const COOKIE_TO_PRETZEL_RATIO = 0.5
 
let snackProducer: Producer<Snack> = {
  produce: () => Math.random() > COOKIE_TO_PRETZEL_RATIO
    ? new Cookie("milk")
    : new Pretzel(true)
};

Great! Let’s try assignments in both directions of snackProducer and cookieProducer

snackProducer = cookieProducer // ✅
cookieProducer = snackProducer // ❌
Interesting! We can see that if we need a snackProducer, a cookieProducer will certainly meet our need, but if we must have a cookieProducer we can’t be sure that any snackProducer will suffice.  
Cookie direction Snack
Cookie --- is a ---> Snack
Producer<Cookie> --- is a ---> Producer<Snack>

Because both of these arrows flow in the same direction, we would say Producer<T> is covariant on T

 

TypeScript 5 gives us the ability to state that we intend Producer<T> to be (and remain) covariant on T using the out keyword before the typeParam.

interface Producer<out T> {
  produce: () => T;
}

 

 

Contravariance

Now we need to model things that package our snacks. Let’s make a Packager<T> interface that describes packagers.

interface Packager<T> {
  package: (item: T) => void;
}

 

Let’s imagine we have two kinds of machines

  • cookiePackager - a cheaper machine that only is suitable for packaging cookies
  • snackPackager - a more expensive machine that not only packages cookies properly, but it can package pretzels and other snacks too!
let cookiePackager: Packager<Cookie> = {
  package(item: Cookie) {}
};
 
let snackPackager: Packager<Snack> = {
  package(item: Snack) {
    if (item instanceof Cookie ) {
      /* Package cookie */
    } else if (item instanceof Pretzel) {
      /* Package pretzel */
    } else {
      /* Package other snacks? */
    }
  }
};

Check the assigement:

cookiePackager = snackPackager // ✅
snackPackager = cookiePackager // ❌

If we need to package a bunch of Cookies, our fancy snackPackager will certainly do the job. However, if we have a mix of Pretzels, Cookies and other Snacks, the cookiePackager machine, which only knows how to handle cookies, will not meet our needs.

Let’s build a table like we did for covariance

Cookie direction Snack
Cookie --- is a ---> Snack
Packager<Cookie> <--- is a --- Packager<Snack>

Because these arrows flow in opposite directions, we would say Packager<T> is contravariant on T

 

TypeScript 5 gives us the ability to state that we intend Packager<T> to be (and remain) covariant on T using the in keyword before the typeParam.

interface Packager<in T> {
  package: (item: T) => void;
}

 

 

Invariance

What happens if we merge these Producer<T> and Packager<T> interfaces together?

interface ProducerPackager<T> {
  package: (item: T) => void;
  produce: () => T;
}

These machines have independent features that allow them to produce and package food items.

  • cookieProducerPackager - makes only cookies, and packages only cookies
  • snackProducerPackager - makes a variety of different snacks, and has the ability to package any snack
let cookieProducerPackager: ProducerPackager<Cookie> = {
  produce() {
    return new Cookie('dark')
  },
  package(arg: Cookie) {}
}
 
let snackProducerPackager: ProducerPackager<Snack> = {
  produce() {
    return Math.random() > 0.5
      ? new Cookie("milk")
      : new Pretzel(true)
  },
  package(item: Snack) {
    if (item instanceof Cookie ) {
      /* Package cookie */
    } else if (item instanceof Pretzel) {
      /* Package pretzel */
    } else {
      /* Package other snacks? */
    }
  }
}

Check the assignement:

snackProducerPackager= cookieProducerPackager  // ❌
cookieProducerPackager = snackProducerPackager // ❌

Looks like assignment fails in both directions.

  • The first one fails because the package types are not type equivalent
  • The second one fails because of produce.

Where this leaves us is that ProducerPackager<T> for T = Snack and T = Cookie are not reusable in either direction — it’s as if these types (ProducerPackager<Cooke> and ProducerPackager<Snack>) are totally unrelated.

 

Let’s make our table one more time

Cookie direction Snack
Cookie --- is a ---> Snack
ProducerPackager<Cookie> x x x x x x ProducerPackager<Snack>

This means that ProducerPackager<T> is invariant on TInvariance means neither covariance nor contravariance.

 

 

Bivariance

For completeness, let’s explore one more example. Imagine we have two employees who are assigned to quality control.

One employee, represented by cookieQualityCheck is relatively new to the company. They only know how to inspect cookies.

Another employee, represented by snackQualityCheck has been with the company for a long time, and can effectively inspect any food product that the company produces.

function cookieQualityCheck(cookie: Cookie): boolean {
  return Math.random() > 0.1
}
 
function snackQualityCheck(snack: Snack): boolean {
  if (snack instanceof Cookie) return cookieQualityCheck(snack)
  else return Math.random() > 0.16 // pretzel case
}

We can see that the snackQualityCheck even calls cookieQualityCheck. It can do everything cookieQualityCheck can do and more.

Our quality control employees go through a process where they check some quantity of food products, and then put them into the appropriate packaging machines we discussed above.

Let’s represent this part of our process as a function which takes a bunch of uncheckedItems and a qualityCheck callback as arguments. This function returns a bunch of inspected food products (with those that didn’t pass inspection removed).

We’ll call this function PrepareFoodPackage<T>

// A function type for preparing a bunch of food items
// for shipment. The function must be passed a callback
// that will be used to check the quality of each item.
type PrepareFoodPackage<T> = (
  uncheckedItems: T[],
  qualityCheck: (arg: T) => boolean
) => T[]

 

Let’s create two of these PrepareFoodPackage functions

  • prepareSnacks - Can prepare a bunch of different snacks for shipment
  • prepareCookies - Can prepare only a bunch of cookies for shipment
// Prepare a bunch of snacks for shipment
let prepareSnacks: PrepareFoodPackage<Snack> = 
  (uncheckedItems, callback) => uncheckedItems.filter(callback)
 
// Prepare a bunch of cookies for shipment
let prepareCookies: PrepareFoodPackage<Cookie> = 
  (uncheckedItems, callback) => uncheckedItems.filter(callback)

Finally, let’s examine type-equivalence in both directions

// NOTE: strictFunctionTypes = false
 
const cookies = [
  new Cookie('dark'),
  new Cookie('milk'),
  new Cookie('white')
]
const snacks = [
  new Pretzel(true),
  new Cookie('milk'),
  new Cookie('white')
]
prepareSnacks (cookies, cookieQualityCheck)
prepareSnacks (snacks,  cookieQualityCheck)
prepareCookies(cookies, snackQualityCheck )

 

In this example, we can see that cookieCallback and snackCallback seem to be interchangeable. This is because, in the code snippet above, we had the strictFunctionTypes option in our tsconfig.json turned off.

 

Let’s look at what we’d see if we left this option turned on (recommended).

// NOTE: strictFunctionTypes = true
 
prepareSnacks (cookies, cookieQualityCheck) // ❌
prepareSnacks (snacks,  cookieQualityCheck) // ❌
prepareCookies(cookies, snackQualityCheck)  // ✅

 

More plesase read https://www.typescript-training.com/course/intermediate-v2/11-covariance-contravariance/#what-variance-helpers-do-for-you

 

标签:function,cookies,Snack,package,Typescript,Cookie,new,Variants,Intro
From: https://www.cnblogs.com/Answer1215/p/18001473

相关文章

  • 从零搭建Vue3 + Typescript + Pinia + Vite + Tailwind CSS + Element Plus开发脚手架
    项目代码以上传至码云,项目地址:https://gitee.com/breezefaith/vue-ts-scaffold目录前言脚手架技术栈简介vue3TypeScriptPiniaTailwindCSSElementPlusvite详细步骤Node.js安装创建以typescript开发的vue3工程集成Pinia安装pinia修改main.ts创建一个store在组件中使用store集......
  • [Typescript] The type Registry pattern (declare module)
    OurprojectmighthaveafilestructurelikeOurprojectmighthaveafilestructurelikedata/book.ts//AmodelforBookrecordsmagazine.ts//AmodelforMagazinerecordslib/registry.ts//Ourtyperegistry,anda`fetchRecord`f......
  • vue3 在 TypeScript 文件中,const route = useRoute();route undefined 不能在顶层作用
    ts文件内部不能使用import{useRoute}from'vue-router';constroute=useRoute();routeundefined在TypeScript文件中,不能在顶层作用域内使用Vue组件的Hooks函数,例如useRoute。Hooks函数只能在Vue组件中使用。如果你想在TypeScript文件中获取当前路由信息,你可......
  • [Typescript 5] infer Constraints
    Sincetypescript5,weareabletoaddconstraintsoverinfer.Followingcodedoesn'tapplyconstraints,sotheinferredelementcouldbe stringand numbertypeGetFirstStringIshElement<T>=Textendsreadonly[inferS,..._:any[]]?S:n......
  • [Typescript] Native ES module support
    Node.js13.2.0introducedsupportfornativeESmodules.Thismeansyoucannativelyruncodecontainingthinglike import{Foo}from'bar',usetop-level await andmore!Howtounambiguouslyindicatewhichtypeofmoduleyou’reauthoringFil......
  • [转]TypeScript类型编程中的extends和infer示例解析
    转自;https://www.jb51.net/javascript/294261vgi.htm TypeScript类型编程中的extends和infer示例解析 −目录引文extends条件判断约束参数类型约束infer推导的局部变量类型类型转换infer组合使用ReturnTypeParameters引文在刚接触TypeScript的时候,......
  • [Typescript] Handle CommonJS import in Typescript
    Let'ssayweneedtousealibrarywithcommonJScode.classMelon{cutIntoSlices(){}}module.exports=MelonThenwewanttoimportthisinsideourTypescriptproject:import*asmelonNamespacefrom"./melon"//typescriptdoesn......
  • 【THM】Intro to Malware Analysis(恶意软件分析简介)-学习
    本文相关的TryHackMe实验房间链接:https://tryhackme.com/room/intromalwareanalysis本文相关内容:简单介绍一下遇到可疑的恶意软件应该怎么处理。简介当我们在担任SOC(安全运营中心)分析师时,有时会遇到一些可疑的内容(文件或流量),并且我们必须确定这些可疑内容是否为恶意的,为此......
  • 使用命令行方式搭建uni-app + Vue3 + Typescript + Pinia + Vite + Tailwind CSS + uv
    使用命令行方式搭建uni-app+Vue3+Typescript+Pinia+Vite+TailwindCSS+uv-ui开发脚手架项目代码以上传至码云,项目地址:gitee.com/breezefaith…目录一、前言近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台......
  • OpenHarmony—TypeScript到ArkTS约束说明
    对象的属性名必须是合法的标识符规则:arkts-identifiers-as-prop-names级别:错误在ArkTS中,对象的属性名不能为数字或字符串。通过属性名访问类的属性,通过数值索引访问数组元素。TypeScriptvarx={'name':'x',2:'3'};console.log(x['name']);console.log(x[2]);ArkT......