欢迎光临散文网 会员登陆 & 注册

[值与类型]关于编程中的“量”和“值”——JavaScript视角

2020-10-03 15:52 作者:useStrict  | 我要投稿

本文全文依cc0公开授权。 

https://creativecommons.org/choose/zero/?lang=zh


很多人,包括很多有相当经验的开发者/程序员,很容易混淆或忽略量和值的概念和区别,而这种混淆有时,尤其是在某些编程语言中,会造成一些微妙的误会,因此恰当得区分这两者是有意义的。


实现/物理/机器层面的“量”和“值”

这里的“机器层面”并不指实际的某种电子计算机,而是指针对现在的计算机的抽象模型。实际上,我们在实践中所讨论的“机器层面”,经常是“C编译器和libc眼中的”机器层面,也就是C编译器为程序员提供的对机器的抽象。

与图灵机相似,这种模型中都存在一条纸带,这条纸带被划分为一个一个的格子,而每个格子可以被写入数字0或1。但与图灵机不同,这条纸带是被编址的并能够被随机访问,也就是说给定一个地址,“读写头”可以立即转到地址所指向的格子,而这条纸带被叫做内存。当然,这都是小事。

另外,如果你使用过lisp机的话,那么是不是lisp机的机器层面会与C的机器层面大不相同呢?

在这样的机器层面上,“量”指的是内存的“格子”,而“值”指的是“格子中的内容”:量是容器,值是内容,这两者的区分虽然相当显而易见,但也相当容易被忽略。毕竟把“一杯水”区分为“一杯”和“水”似乎并没有太大必要——虽然你大概从来没有喝过玻璃。


但语言层面和机器层面是不同的

这实际上本来应该是一件挺显然的事情:编程语言提供了一系列抽象,不是为了让程序员试图去窥探和接触底层逻辑的。但是C和C++却似乎很喜欢培养黑客,这两门语言中的很多概念都与底层实现有着清晰的对应,这在某种意义上是好的,但是这也让很多人养成了某些微妙的习惯,然而这种清晰的对应却不是何时何地都存在的。

把编译器视作魔法,虽然很多时候它并不是那么得魔法,至少能让人轻松很多。当然,也不是任何时候都必须把编译器看作魔法,不然就没有人再去维护编译器了。

简单得说,标识符本身即是量,而标识符所指向的东西,无论它指向的是什么,是如何指向的,这个被指向的东西是值。与其说量是容器而值是内容,不如说量是能指而值是所指,量是名称而值是实体。

identifier和signifier的确很像呢...毕竟形式语言也是语言,语言学理论当然也对形式语言有效。

比如在`let a = 1`中,a是一个量,量a指向值1。

这里的“指向”是抽象意义上,或者说语言意义上的指向。

而在`a = a + 1`中,则是计算(a+1)的值,令a重新指向它:“令a指向a+1”,而不是“令a加1”,才是这一语句在几乎所有语言中的确切语义。

注意,此处只谈语义而不谈实现,实现是编译器才需要关心的。

至少在JavaScript中,这并不是像C一样的,a对应了某一段内存,而这一段内存中的数字是1。所以请把JavaScript代码看作是直接运行在JavaScript引擎这一魔法之上的。

也是存在着某些语句具有“令a加1”这一语义的:at&t语法的`addq $1, %rax`便具“令rax加1”的语义。

机器/计算模型层面只允许0和1两个值,但绝大多数语言允许的值比这些要多得多,包括0, 1, 2, 3,'a', 'b', c', d'以及'abcd',也包括自定义值,比如[1, 2, 3]和{a: 1, b: 2, c: 3},这些值被编译器/引擎通过某种简单的魔法映射到了底层实现,但是我们并不真的需要关心这一实现。


JavaScript视角

JavaScript引擎允许7种"原始类型"的值,外加仅此一种的非原始类型,Object。

虽然说实际上某些Object值,就是说Array和Function,对引擎有某种特权,但总得来说,它们依然是Object。

注意,是7+1种“值”,而不是7+1种“量”。

为什么说是7+1种值呢,比如说

let a

定义了一个变量,而

a = 1

使得a指向了值1,或者

a = {a: 1}

使得a指向了值{a: 1}

其中1是number类型的值,而{a: 1}则是Object类型的值。

那么我们看到,a从来没有关心过这些值属于那些分类,而这些值却天生带有分类。所以,这些分类是为了划分值而产生的,与量并没有关系。

像大多数语言一样,JavaScript中的值大都是不可变的,比如1永远都是1,某个量可能会从指向1变成指向2,但1永远不会变。在机器层面上,内存中的某一位可能由0变为1,但是0这一数字本身永远不会是1。值是不变的,会变的只有指向,或者说量和值的对应关系。

不过JavaScript中有一类值却是会变的,这就是被排除在7类primitive value之外的Object。

但是为什么要有一种会变的值呢?

比如说你种下了一棵种子,把它命名为小花,过了几天小花长成了幼苗,你非常开心,无论是那颗种子还是这株幼苗,小花一直都是那个小花,但是它已经不是你种下它的时候的样子的。

Object就是这样一颗能够发芽的种子。

这种可变性使得Object能够被用来做很多事情,不过也容易产生一些不太符合直觉的行为。

值的类型

我们知道值天然具有类型,比如1就是一个数字,而a就是一个字母。从数学上,我们可以把具有某一类性质的值归类于一个集合,这样的集合就叫做类型。

当然,值并不必须按照7种基本类型那样划分,这样的划分实际上是为了实现方便:因为不同类型的值需要不同的实现,引擎将实现相同的值归结为了同种类型:但是类型本身并不必然需要与实现挂钩。

比如“0到255的整数”可以是一种类型,{0, 1, 2, 4}也可以是一种类型,换言之,只要集合论允许,有限或无限的值的任意一种集合都可以是一种类型,比如空集在很多种语言中都对应void类型。

量的类型:静态类型系统与动态类型系统

量本身并不需要具有类型,因为量不需要关心其中的值是什么。但是很多语言中量同样是具有类型的,而这种类型实际上是一种约束:被声明为某个类型的量只能指向某个类型的值,如果这条规则被违反,那么编译器被期望能够产生编译时错误。

之所以需要这么做,是因为大多数编译型语言必须在编译时确定值对应的实现,并且无法在运行时确定值的类型。如果某个量被允许指向实现不同的值,那么编译器就必须用某种方式为其选择实现。能够在编译时选择实现的特性叫做编译时多态,著名的标准模板库(STL)所使用的“模板”就是编译时多态的一种实现。当然也有运行时多态,C++的虚函数就是一种运行时多态的实现。

不过虽然具有多态和运行时类型标注,C++的类型系统依然是静态的。

与之相反的,JavaScript使用了动态类型系统,因而不对量能够指向的值的类型做约束,相对的,JavaScript在运行时进行值类型检查,如果值的类型不符,则会产生运行时错误:事实上,JavaScript引擎从来不会放过任何一个运行时类型错误,毕竟对于这种错误,引擎根本不知道如何执行下去,所以就只好报错了:和某个著名web脚本语言相比,这反而是一件好事。

动态类型系统更为灵活,但也牺牲了在编译时提前进行类型检查的能力:这就使得某些编译时错误成了运行时错误。

不过毕竟JavaScript也并不存在编译时。

厘清量与值的区别,我们就能更好得理解JavaScript以及其他的动态类型语言背后的逻辑,希望这篇文章能够给大家带来更多的一时爽和更少的火葬场。


===============================================


附加题:'1'+1='11'是谁的错?

* 静态类型语言是如何应对这种情况的?

* 如果让你来写ECMA262,你会如何避免这种错误?

* 某个著名web脚本语言没有将operator+重载为字符串拼接运算符,对此你怎么看?

[值与类型]关于编程中的“量”和“值”——JavaScript视角的评论 (共 条)

分享到微博请遵守国家法律