框架揭秘_揭秘类型系统

框架揭秘

Type systems nurture the interest of many, but few dig under the surface. You may try hard, but still you will face a lack of reliable and informative sources and an abundance of contradictory statements.

类型系统激发了许多兴趣,但很少有人在表象下挖掘。 您可能会尽力而为,但仍然会缺乏可靠和有用的信息来源,以及大量矛盾的陈述。

In this article, I’ve compiled facts and myths about type systems, and I encourage you to read further to discover what is what.

在本文中,我整理了有关类型系统的事实和神话,我鼓励您进一步阅读以了解什么是什么。

We will also cover some neat tricks which can be used to amplify strengths and mitigate weaknesses of different tools.

我们还将介绍一些巧妙的技巧,这些技巧可用于扩大不同工具的优势并减轻其劣势。

先决条件 (Prerequisites)

Most importantly, what is a type? A common belief (especially in the functional programming world) is that type simply a collection of values.

最重要的是,什么是类型 ? 一个普遍的信念(尤其是在函数式编程世界中)是那种类型只是值的集合。

So a Boolean has two possible values, while an Integer and a String potentially have infinite values.

因此,布尔值具有两个可能的值,而整数和字符串可能具有无限值。

type Bool = True | False; type Int = … | -1 | 0 | 1 | 2 | … ; type String = “” | “a” | “ab” | …

A class is no different — its set of values is the combination of its own and ancestors’ properties’ value sets:

一个类没有什么不同-它的值集是其自身和祖先属性的值集的组合:

A type system is what defines and manages types and operations on them. To some extent, it ensures type safety.

类型系统是定义和管理类型和对其进行操作的对象。 在某种程度上,它确保了类型安全

Types such as String, Bool and others are connected with operators and functions defined on them

And type safety is the ability of a type system to prevent type errors, which is applying an operation unsupported by a type to its instance.

类型安全是类型系统防止类型错误的能力,该错误将类型不支持的操作应用于其实例。

For example, in some languages, you can perform operations such as accessing non-existent object properties or dividing numbers by strings, which is not type-safe and may lead to bugs:

例如,在某些语言中,您可以执行诸如访问不存在的对象属性或将数字除以字符串之类的操作,这不是类型安全的,并且可能导致错误:

"20" / 10; // 2, no error
[].missing; // undefined, no error
[].missing(); // runtime TypeError

Every somewhat usable programming language has a type system, even some Assembly versions. To demonstrate the main principles and ideas, I will use a variety of languages:

每种稍微有用的编程语言都有一个类型系统 ,甚至有一些Assembly版本。 为了展示主要的原理和思想,我将使用多种语言:

Logos of languages used in the article for examples

Don’t worry if you are not familiar with some of them, especially with the last three, as these are functional programming ones. I’ll do my best to explain the main ideas.

如果您不熟悉其中的某些功能(尤其是后三个功能),请不要担心,因为它们是函数式编程的功能。 我会尽力解释主要思想。

The last thing worth mentioning before we proceed to the classification is the prevalent myth that:

在进行分类之前,最后值得一提的是一个普遍的神话

Type systems can be binary classified

类型系统可以进行二进制分类

So people say that a language is either statically or dynamically typed, either strong or weak, etc. That’s a very dangerous myth that gives birth to all the holy wars around type systems.

因此人们说一种语言是静态动态输入的,要么是类型的,要么是类型的。这是一个非常危险的神话,它催生了围绕类型系统的所有圣战。

Throughout the article, you will see many examples of this being false. Generally, modern languages have so many features that it’s tough to classify them with discrete measures — it’s more about what a language supports.

在整篇文章中,您将看到许多错误的例子。 一般而言,现代语言具有如此众多的功能,以至于很难用离散量度对它们进行分类-而是语言所支持的更多。

静态与动态 (Static vs. Dynamic)

This classification is the most popular one and might seem easiest, but it has many misconceptions, for example:

这种分类是最受欢迎的分类,看似最简单,但它存在许多误解,例如:

Compiled languages have static type systems and interpreted — dynamic ones

编译语言具有静态类型系统和解释型-动态系统

This myth can be dispelled with just two points, which might come as a bit unexpected:

仅仅用两点就可以消除这个神话这可能有点出乎意料

  1. There is no such thing as a static or a dynamic type system.

    没有静态动态类型系统。

  2. And there is no such thing as a compiled or an interpreted language.

    而且没有编译语言或解释语言。

A programming language is just a specification, a grammar describing syntax bound by semantics and a type system. Any language can be compiled or interpreted.

编程语言只是一种规范,一种描述受语义约束的语法的语法和一种类型系统。 任何语言都可以编译或解释。

For example, Haskell and C++ — languages with powerful “static” type systems — have both compilers and interpreters. And JavaScript — originally an interpreted language — is often compiled just-in-time, for instance, with V8’s optimizing compiler TurboFan.

例如,具有强大“静态”类型系统的语言Haskell和C ++同时具有编译器解释器 。 例如,JavaScript(最初是一种解释语言)通常是使用V8的优化编译器TurboFan及时进行编译的。

So it’s just a matter of implementation when to check for type errors: statically before the execution or dynamically in the middle of it. Some tools can use type information for optimization, as most compilers do, but it’s not about the language — it’s about which tools you use.

因此,何时检查类型错误只是实现的问题: 静态 在执行之前或动态地在执行之中。 某些工具可以像大多数编译器一样使用类型信息进行优化,但这与语言无关,而与语言有关。

What I’m trying to say is that static type checking is mostly just another static analysis layer that can be added to or removed from any language. For example, you can establish this static type analysis layer with the help of dedicated tools in languages without this layer:

我要说的是,静态类型检查主要只是可以添加到任何语言或从任何语言中删除的另一个静态分析层 。 例如,您可以借助语言专用的工具来建立此静态类型分析层,而无需该层:

Python + MyPy:

Python + MyPy

def add(x: int, y: int) -> int

PHP + Hack:

PHP + hack

function add(int $x, int $y): int

JavaScript + TypeScript/Flow:

JavaScript + TypeScript /

function add(x: number, y: number): number

But what if we want our existing static type checking tool to give us some freedom? Some languages, such as TypeScript and C#, have this built-in — a type with all possible values:

但是,如果我们希望现有的静态类型检查工具给我们一些自由呢? 某些语言(例如TypeScript和C#)具有此内置功能-一种具有所有可能值的类型:

const anything: any = null;
anything.missing(); // runtime Type error
dynamic anything = null;
anything.missing(); // runtime exception

This way, your type analysis tool (e.g., a compiler) won’t care about how the value is used, but in runtime, the program might crash or produce an error.

这样,您的类型分析工具(例如,编译器)就不会在乎如何使用该值,但是在运行时,程序可能会崩溃或产生错误。

You might wonder, “Why would I ever want to do that?” But you’ve probably heard that static type checking makes it easier to maintain a system, while the absence of it speeds up the writing and iteration on ideas.

您可能会想,“我为什么要这么做?” 但是您可能已经听说过,静态类型检查使维护系统更加容易,而缺少静态类型检查则可以加快思想的编写迭代速度。

So, what’s wrong with quickly hacking together some stuff to just see if it works and then add reliable types while it’s still fresh in mind?

那么,快速将一些东西汇总在一起,以查看它们是否有效,然后在仍然新鲜的同时添加可靠的类型又有什么问题呢?

满足渐进式打字! (Meet Gradual typing!)

In languages that support such almighty types, taking the best of both worlds, you can start developing in a dynamic style to iterate and try things quickly and then add all needed static checking later, when the developed module is stable enough:

在支持这两种全能类型的语言中,兼顾了两者的优点,您可以开始以动态方式进行开发,以迭代并快速尝试,然后在开发的模块足够稳定时添加所有需要的静态检查:

// Iterate fast
function makeSomething(arg: any): any


function doSomething(something: any): any


// Build safe
function makeAlert(text: string): Alert


function showAlert(alert: Alert): void

But be careful with this “later” — make sure it’s right after you’ve settled on an implementation and not just “later,” probably never.

但是请谨慎使用此“后期”-确保在您确定实现之后才是正确的,而不仅仅是“后期”,可能永远不会。

Speaking of the advantages of having only dynamic type checks, here is another one — the ease of writing generic functions. You can express anything with no static type checking holding you back. This doesn’t necessarily mean that this “anything” will even work, though.

说到只进行动态类型检查的优点,这是另一种-编写通用函数的简便性。 您可以表达任何内容而无需进行静态类型检查来阻止您。 不过,这不一定意味着“一切”都将起作用。

Look at this JavaScript example:

看下面这个JavaScript示例:

function sliceIfLong(maybeLong) {
  if (maybeLong.length > 5) {
    maybeLong.slice();
  }
}


// All valid
sliceIfLong([1, 2, 3]);
sliceIfLong("string");
sliceIfLong({length: 7, slice() {}});

We can use any value we like here, as long as it satisfies certain requirements, specified implicitly as property accesses and method calls, or get a run-time error.

只要满足特定要求(可以隐式地指定为属性访问和方法调用)或遇到运行时错误,我们就可以使用此处喜欢的任何值。

This mechanism is called Duck typing, and it applies the Duck test:

这种机制称为Duck类型 ,它应用Duck测试:

Yellow rubber duck

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

如果它看起来像鸭子,像鸭子一样游泳,而像鸭子一样嘎嘎叫,那可能就是鸭子。

In our case, if an argument has the needed properties, it fits, and this is only checked at runtime. So if we never access the missing properties we don’t have to implement them:

在我们的例子中,如果参数具有所需的属性,则它适合并且仅在运行时检查。 因此,如果我们从不访问缺少的属性,则不必实现它们:

function sliceIfLong(maybeLong) {
  if (maybeLong.length > 5) {
    maybeLong.slice();
  }
}


sliceIfLong({length: 3}); // 3 < 5, no need for slice
sliceIfLong({length: 7}); // runtime Type error

To achieve a similar result in a type-safe way, some languages, such as C#, have generics:

为了以类型安全的方式实现类似的结果,某些语言(例如C#)具有泛型

void sliceIfLong<T>(T maybeLong) where T : IMaybeLong {
    if (maybeLong.length > 5) {
        maybeLong.slice();
    }
}
	
interface IMaybeLong {
    int length { get; }
    void slice();
}

They apply the Duck test as well, but in a bit differently — generics demand all potentially reachable members to be defined.

他们也应用Duck测试,但有所不同-泛型要求定义所有可能到达的成员。

You can see that we also have to specify a bonding type IMaybeLong for our generic type T. An object passed to the function has to be of a class implementing this interface. So overall, generics are safer than duck typing.

您可以看到,我们还必须为通用类型T指定一个绑定类型IMaybeLong 。 传递给函数的对象必须是实现此接口的类。 因此,总的来说,泛型比鸭子输入更安全

There is also something in between in C++ — templates:

在C ++中, 模板之间也有一些区别

template <typename T>
void clearIfLong(T maybeLong) {
    if (maybeLong.size() > 5) {
        maybeLong.clear();
    }
}


// Will generate two different functions
clearIfLong(string{"string"});
clearIfLong(vector{1, 2, 3});

You don’t have to specify a bonding type, but a type checker would still yell at you if a passed type didn’t have all potentially reachable members. Their idea is to gather all the possible types a template can get and then generate an according function or a class for each type. So in this example, we would get two different functions.

您不必指定绑定类型,但是如果传递的类型没有所有潜在的可到达成员,则类型检查器仍会对您大吼大叫。 他们的想法是收集模板可以获取的所有可能的类型,然后为每种类型生成相应的函数或类。 因此,在此示例中,我们将获得两个不同的功能。

I won’t say that you can express anything with generics or templates, but if you really need to implement something and just can’t, you might be doing something too complicated or are not using the best tool for your problem.

我不会说您可以使用泛型或模板来表达任何内容,但是,如果您确实需要实现某些事情,而实际上却无法实现,则可能是您做的太复杂了,或者没有使用最佳工具来解决问题。

功率 (Power)

Actually, the ability of a language or a type system to express complex concepts and patterns is called power or expressiveness, and different languages have different power levels.

实际上,语言或类型系统表达复杂概念和模式的能力称为能力表现力 ,不同的语言具有不同的能力等级。

For example, in Golang, a rather popular modern language, there are no generics, which are present in many mainstream languages such as Java, Swift, and others.

例如,在流行的现代语言Golang中 ,没有泛型,而在许多主流语言(例如JavaSwift和其他语言)中都存在泛型。

Whereas TypeScript has these and also supports union and intersection types, which are very powerful concepts:

而TypeScript具有这些并且还支持联合交叉类型,这是非常强大的概念:

// Union
type PhoneNumber = string | number;


// Intersection
type Duck = IWalking & IFlying;

Union means that its instance is either of the options, so either a string or a number in this example.

联合表示其实例是选项之一,因此在此示例中为字符串或数字。

Intersection says that an instance has to have properties of both types — IWalking and IFlying.

Intersection表示实例必须具有两种类型的属性IWalkingIFlying

Besides, there are even more powerful but less known languages, such as Idris. It has types as first-class objects so that they can be used as any other values, and also support Dependent types, where a type is dependent on its value:

此外,还有更强大但鲜为人知的语言,例如Idris 。 它具有类型作为一等对象,因此它们可以用作任何其他值,并且还支持Dependent types ,其中类型取决于其值:

isEmpty : List a -> Bool
isEmpty [] = True
isEmpty _  = False 


head : (list : List a) -> {auto predicate : isEmpty list = False} -> a
head (first :: rest) = first

Let me take you through this example.

让我通过这个例子。

Function isEmpty, returns whether a list is empty (duh!) and function head returns a list’s first element, but only if it is not empty. The part in curly braces is a predicate, and we statically require it to be false. So a list passed to this function has to be checked for being not empty.

功能isEmpty ,返回一个列表是否为空(废话!)和函数head返回列表的第一个元素,但只有当它不为空。 花括号中的部分是一个谓词,我们静态地要求它为假。 因此,必须检查传递给此函数的列表是否不为空。

The most important part is that it can be enforced statically, before the execution. And even when the value comes from a dynamic context, such as user input, it has to be checked with isEmpty before being passed to this function.

最重要的部分是它可以在执行之前静态地强制执行。 即使该值来自动态上下文(例如用户输入),也必须在传递给此函数之前使用isEmpty检查它。

显式与隐式 (Explicit vs. Implicit)

Another important yet simple classification which people often overlook is explicit and implicit type systems.

人们经常忽略的另一个重要而简单的分类是显式和隐式类型系统。

Explicit means that a developer has to specify the types of variables, arguments, return values, etc.

显式意味着开发人员必须指定变量,参数,返回值等的类型。

Implicit means that you don’t have to or even can’t specify the types, and the type system will infer them for you.

隐式意味着您不必甚至不能指定类型,类型系统将为您推断它们。

There is a widespread myth around this:

有一个关于这个的普遍神话

To have your code statically type-checked, you have to specify the types

要对代码进行静态类型检查,您必须指定类型

First of all, even if a language doesn’t imply using a static type analysis tool by design, it still can be checked with no changes if it’s possible to infer the types.

首先,即使一种语言不是设计上暗示使用静态类型分析工具,如果可以推断出类型,仍然可以对其进行检查而不进行任何更改。

For example, if your JavaScript uses a browser API with defined types, it’s easy to infer the types at least for some code, which is a common feature in modern editors:

例如,如果您JavaScript使用具有定义类型的浏览器API,则至少对于某些代码而言,很容易推断出类型,这是现代编辑器的常见功能:

document.getElementById(123); // error, expected a string, but got a number :) 


// inferred type - any -> HTMLElement
function byId(id) {
  return document.getElementById(id);
}


byId(); // no error :( 
byId(1); // no error :( 
byId(1, 2); // error, wrong arguments number :)

And more sophisticated type systems and tools such as the Haskell ones allow whole functions to be implicitly typed, as long as their type resolution is not ambiguous:

而且,更复杂的类型系统和工具(例如Haskell的类型系统和工具)允许隐式地对整个函数进行类型化,只要它们的类型解析度不是模棱两可即可

-- inferred type - Num a => a -> a -> a
sum a b = a + b


data Language = Haskell | JavaScript


-- inferred type - Language -> Bool
hasIntegers Haskell    = True
hasIntegers JavaScript = False

Our sum function was inferred to take two Numbers and return a Number. It also works quite well with custom types, such as Language, because their constructors have to be unique.

推断我们的sum函数采用两个Numbers并返回Number 。 它也可以与自定义类型(例如Language一起很好地工作,因为它们的构造函数必须是唯一的。

It’s less code for sure, but doing it this way has a few disadvantages:

当然,它的代码更少,但是这样做有一些缺点:

  • You have to read the whole function to understand what it does, instead of just looking at a signature.

    您必须阅读整个功能以了解其功能,而不仅仅是看签名。
  • The type inference gives us the most generic type, so if we wanted our sum function to operate only on Integers and not also on Floats (which Number includes), we would have to specify the function types ourselves.

    类型推断为我们提供了最通用的类​​型,因此,如果我们希望sum函数仅对Integers而不对Floats (包括Number在内)进行操作,则必须自己指定函数类型。

Anyway, type inference is supported in most modern languages, and even relatively old ones such as C#, Java, and C++ got support for it.

无论如何,大多数现代语言都支持类型推断,甚至相对较老的语言(例如C#,Java和C ++)也都支持它。

But how do we get the most out of it?

但是,我们如何获得最大的呢?

I like and recommend to specify the types for:

我喜欢并建议指定以下类型:

  • Functions and methods arguments;

    函数和方法参数;
  • Functions and methods return values;

    函数和方法返回值;
  • Classes properties.

    类的属性。

The types of simple variables can be easily inferred because they are always assigned a value of a specific type, be it a literal or a function call result. This approach allows both to keep code concise and easily statically type-checked:

简单变量的类型可以很容易地推断出来,因为它们总是被赋予特定类型的值,无论是文字还是函数调用结果。 这种方法既可以保持代码简洁,又可以轻松地进行静态类型检查:

class Product {
  price: number = 1;


  afterTax(amount: string): number {
    return this.price + parseInt(amount);
  }
}


const water = new Product();
const newPrice = water.afterTax("1"); // 2

Some people also like not to specify their return types, which is even more concise, but it might result in a nasty bug in a completely different part of the codebase.

某些人还喜欢不指定返回类型,这更加简洁,但是这可能会导致代码库中完全不同的部分出现讨厌的错误。

Let’s remove the return type of afterTax. Then, say, refactoring happened, and we’ve accidentally removed the parsing from the function, but now we have no explicit type to guard us against this error:

让我们删除afterTax的返回类型。 然后,例如发生了重构,我们不小心从函数中删除了解析,但是现在我们没有显式类型可以防止出现此错误:

class Product {
  price: number = 1;


  afterTax(amount: string) { // no return type
    return this.price + amount;
  }
}


const newPrice = new Product().afterTax("1"); // "11"

As you can see, this is especially dangerous if implicit type conversions are allowed in the language.

如您所见,如果在语言中允许隐式类型转换 ,则这特别危险。

弱vs强 (Weak vs. Strong)

Speaking of the implicit conversions, it’s time to dive into the type system’s strength

说到隐式转换,是时候深入探讨类型系统的优势了 ……

And this notation is a complete mess!

而且这种表示法是一团糟!

Various sources give entirely different definitions of a strong and weak language. Some talk about type conversions, others about memory safety, and some even mistake it for the static and dynamic type checking. And, let’s be honest, who wants to use a weak language or a type system?

各种消息来源给出一的语言完全不同的定义。 有些人谈论类型转换 ,其他人谈论内存安全 ,甚至有人将其误认为是静态动态类型检查。 而且,说实话,谁想使用语言或类型系统?

Of course, no one, and in some more progressive articles and papers, you can find a plea not to use this classification.

当然,没有人,在一些更先进的文章和论文中,您可以找到一种不使用这种分类的请求

Norman Ramsey a  Stack Overflow expert, says “Every time I see a question about ‘strong’ and ‘weak’ typing I kill a kitten”

Shortly I will show you why it’s so bad, while also breaking a few common myths.

不久,我将向您展示为何如此糟糕,同时也打破了一些普遍的神话。

But first, let me introduce a better notation — type-safety, which we’ve already seen at the beginning of the article. Implicit conversions are not considered type-safe, so you can say that a specific language feature or an implementation is more type-safe than the other.

但是首先,让我介绍一个更好的表示法- 类型安全性 ,我们已经在本文开头看到了。 隐式转换被认为是类型安全的,因此可以说特定语言功能或实现比其他语言或类型的实现安全。

With this in mind, we could say that, for example, these languages have few implicit conversions:

考虑到这一点,我们可以说,例如,这些语言几乎没有隐式转换:

  • C++

    C ++
  • C#

    C#
  • Haskell

    哈斯克尔

While these have many:

尽管这些有很多:

  • JavaScript

    JavaScript
  • PHP

    PHP

Can’t we just say that the first camp is type-safe and the second is not?

我们不能只说第一个阵营是类型安全的,而第二个阵营不是吗?

Remember the very first myth — nothing is binary in this topic. Our presumably safe languages are very different.

记住第一个神话– 在这个话题中,没有什么是二元的 。 我们大概安全的语言非常不同。

C++ has many conversions between primitive types, and also implicit constructors and conversion operators:

C ++在原始类型之间以及隐式构造函数和转换运算符之间有许多转换:

int one = true;
char B  = 'A' + 1;




class Dog {
    Dog(string name) { // 1 argument, no `explicit` modifier
        // ...
    }
}


void petDog(Dog dog) {
    // ...
}


petDog("Fido"); // implicit conversion to a Dog

C# allows casting objects, as well as many other mainstream languages:

C#允许转换对象以及许多其他主流语言:

string toString(object o) {
    return (string) o;
}


toString(1); // runtime exception

Haskell doesn’t have any of these, but at least it allows treating an Integer as a Float:

Haskell没有任何这些,但是至少它允许将Integer视为Float

addIntToFloat :: Float -> Float
addIntToFloat x = x + 2


addIntToFloat 5.0 == 7
addIntToFloat 5   == 7.0

But OCaml doesn’t allow mixing integers and floating-point numbers. The operator + is defined only for integers, so you have to use a special operator +. for floats or explicitly convert between them:

但是OCaml不允许混合使用整数和浮点数。 运算符+仅针对整数定义,因此您必须使用特殊的运算符+. 对于浮点数或在它们之间进行显式转换:

1   + 1;;    (* 2 *)
1   + 1.0;;  (* Type error *)
1.0 + 1;;    (* Type error *)
1.0 + 1.0;;  (* Type error *)
1.0 +. 1.0;; (* 2.0 *)


1.0 + float_of_int 1;; (* 2 *)

The somewhat unsafe languages are different as well, for example, in handling operations on strings and numbers:

某些不安全的语言也有所不同,例如,在处理字符串和数字时:

$one = 5 / "5 string???"; // gives a warning, but works
const Nan = 5 / "5 string???"; // NaN

PHP demonstrates a less type-safe behavior allowing to divide a number by a part of a string, while JavaScript requires the whole string to be parsable.

PHP展示了一种类型安全性较低的行为,该行为允许将数字除以字符串的一部分,而JavaScript要求整个字符串都是可解析的。

But in another example, it’s the opposite because PHP warns about indices out of bounds and undefined property accesses, and JavaScript just returns undefined:

但是在另一个示例中,情况恰恰相反,因为PHP会警告索引超出范围和未定义的属性访问,而JavaScript只会返回undefined

<?php


"short"[100]; // runtime error


class EmptyClass {};
$empty = new EmptyClass;
$empty->undefined; // error
"short"[100]; // undefined


const empty = {};
empty.undefined; // undefined

All you can do here is just say that a language is mostly type-safe and then enumerate all its quirks you have in mind to explain what you mean. So, please, only use the type-safety notation when discussing some specific parts of a language or it’s usage. And never use the strength notation.

您在这里所能做的只是说一门语言主要是类型安全的,然后列举您要记住的所有怪癖来解释您的意思。 因此,请仅在讨论语言的某些特定部分或其用法时使用类型安全标记。 永远不要使用强度符号。

The vaguer the topic — the more there are fallacies. A common one is that:

这个话题含糊不清- 谬论越多。 一个常见的是

Languages that are statically-checked are more type-safe than the ones with only dynamic checks

静态检查的语言比仅动态检查的语言更类型安全

But it’s not true because these classes are not connected in any way, and the best mythbusters here are C++ and Python.

但这不是事实,因为这些类没有以任何方式连接,并且最好的神话是C ++和Python。

The former has some fantastic compilers which have required static type checks, but we know already how unsafe it can be. And Python is frequently only dynamically type-checked, yet it has strict rules on type mismatches and is acknowledged as a rather type-safe language:

前者有一些很棒的编译器,它们需要静态类型检查,但我们已经知道它有多不安全。 而且Python通常仅动态地进行类型检查,但是它对类型不匹配有严格的规定,并被认为是一种相当类型安全的语言:

"short"[100]    # error
{}["undefined"] # error
1 + "1"         # error
str(1) + "1"    # 11

标称与结构 (Nominal vs. Structural)

The last major classes of type systems I’d like to cover today are the nominal and structural ones.

我今天要介绍的类型系统的最后主要类别是名义系统结构系统

In short, if the types are considered equivalent just by having the same subset of values (their structure is equal), it’s structural typing.

简而言之,如果仅通过具有相同的值子集(它们的结构相同)就认为类型是等效的,则它是结构化类型。

If a type can be equivalent only to itself and its descendants — this is nominal or nominative typing:

如果类型只能等效于其自身及其后代,则为名义主格类型:

type X = true | false;
type Y = true | false;


const x: X = true;
const y: Y = true;


// Structural
x === y;


// Nominal
x !== y;

Imagine a typical situation in a mainstream language, such as C# — you have two types with exactly the same properties and methods, thus structurally equivalent:

想象一下主流语言(例如C#)中的典型情况-您有两种类型,它们的属性和方法完全相同,因此在结构上是等效的:

class Person {
    string name;
    string favouriteFood() { /* ... */ }
}


class Cat {
    string name;
    string favouriteFood() { /* ... */ }
}

Also, you have a function operating on one of them, and you want to reuse it for the second type, but it’s not so easy:

另外,您有一个函数在其中一个函数上操作,并且想将其重用于第二种类型,但这并不容易:

void feed(Person person) { /* ... */ }


feed(new Person());
feed(new Cat()); // error

There are numerous solutions to this problem: inheritance, overloading, etc.

有许多解决此问题的方法:继承,重载等。

However, we face this issue because C# supports only nominal typing, and this wouldn’t be a problem in a language supporting structural typing, like TypeScript. Because our types are structurally equivalent, they can be used interchangeably:

但是,我们面临此问题,因为C#仅支持名义类型化,并且在支持结构化类型的语言(如TypeScript)中这不是问题。 由于我们的类型在结构上是等效的,因此可以互换使用:

class Person {
  name: string;
  favouriteFood(): string { /* ... */ }
}


class Cat {
  name: string;
  favouriteFood(): string { /* ... */ }
}


function feed(person: Person) { /* ... */ }


feed(new Person());
feed(new Cat()); // fits!

Structural typing in some form is also supported in Go, Scala, and OCaml, to name a few.

GoScala和OCaml等也支持某种形式的结构化键入。

However, sometimes you really want your type not to be equivalent to anything else. For example, when working with user input, you might want to sanitize it. To use your types effectively, you can create different types for sanitized and non-sanitized input:

但是,有时您确实希望您的类型等于其他任何类型。 例如,在处理用户输入时,您可能希望对其进行清理。 为了有效地使用您的类型,可以为已清理和未清理的输入创建不同的类型:

interface NonSanitizedInput {
  text: string;
}


interface SanitizedInput {
  text: string;
}

Then we could create a function pipeline requiring the input to be sanitized before persisting it:

然后,我们可以创建一个函数管道,要求在清除输入之前对其进行清理:

function getInput(): NonSanitizedInput { /* ... */ }
function sanitizeInput(input: NonSanitizedInput): SanitizedInput { /* ... */ }
function saveToDb(input: SanitizedInput): void { /* ... */ }

Alas, this isn’t so simple with structural typing. The types are still structurally equivalent and can be used interchangeably:

,,使用结构化类型输入并不是那么简单。 这些类型在结构上仍然等效,并且可以互换使用:

saveToDb(getInput()); // oops, saved non-sanitized data!

We can emulate nominal typing by adding a tag to distinguish them:

我们可以通过添加标签来区分名义类型来区分它们:

interface NonSanitizedInput {
  __TAG__: "NonSanitizedInput"
  text: string;
}


interface SanitizedInput {
  __TAG__: "SanitizedInput"
  text: string;
}


saveToDb(getInput()); // Type error
saveToDb(sanitizeInput(getInput())); // OK

We used TypeScript literal types, which mean that the property can have only this value. Now our pipeline works as expected and we can’t mismatch the input types.

我们使用了TypeScript文字类型,这意味着该属性只能具有该值。 现在,我们的管道可以按预期工作,并且我们不能使输入类型不匹配。

摘要 (Summary)

Wrapping up, I present you the final myth:

总结一下,我向您介绍最后一个神话:

Every type system has its place and uses

每个类型的系统都有其位置和用途

You’ve probably noticed that we’ve talked about both the strengths and weaknesses of different type systems and paradigms. That’s because I believe that all the type systems, languages, and other tools we’ve covered today have their design goals and purpose.

您可能已经注意到,我们已经讨论了不同类型系统和范例的优点和缺点。 这是因为我相信我们今天介绍的所有类型系统,语言和其他工具都有其设计目标和目的。

For instance:

例如:

  • C++ was designed for incredible performance, sacrificing many useful type checks;

    C ++旨在提供令人难以置信的性能,从而牺牲了许多有用的类型检查。
  • Go was supposed to be simple and fast, not overloaded with features;

    Go应该被认为是简单而快速的,并且没有太多功能。
  • OCaml’s strictness might seem tedious, but it sure prevents a lot of bugs.

    OCaml的严格性可能看起来很乏味,但是它确实可以防止很多错误。

That’s why I propose not to argue about which type system is better, but be more pragmatic: pick a tool that makes your life easier and your work more productive and use it to its maximum.

这就是为什么我建议不要争论哪种类型的系统更好,而要更加务实 :选择一种使您的生活更轻松,工作更有效率的工具,并最大限度地利用它。

For example, when working on a proof of concept, you might want to neglect the type-safety in favor of development speed. But, when working on a long-term project, static type analysis and overall type-safety can save you from many bugs and much pain.

例如,在进行概念验证时,您可能希望忽略类型安全性,而倾向于开发速度。 但是,在进行长期项目时,静态类型分析和总体类型安全性可以使您免受许多错误和痛苦的困扰。

With that said, I assume the final myth Confirmed.

话虽如此,我认为最后一个神话已经确认

Thank you for reading, I hope this post was interesting and insightful to you!Share your thoughts and questions in the comments or contact me directly by mail, Twitter, or any other means.Also, follow me on Medium and GitHub for more engineering insights.

感谢您的阅读,希望本文对您有所帮助和见解!在评论中分享您的想法和问题,或通过邮件Twitter或任何其他方式直接与我联系。此外,请在MediumGitHub上关注我以获取更多工程见解。

翻译自: https://medium.com/wix-engineering/demystifying-type-systems-3a16fafbd92e

框架揭秘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值