随着现代代码库以疯狂的速度增长,我们通常已经接受这样一个事实,即我们喜爱的编程语言中的某些特性以这样或那样的方式决定了最终产品的质量。无论是对无缝模块化编程、静态类型系统的支持,还是对分布式系统中具有健壮状态维护方式的并发性和并行性的支持。
然而,在某些方面(当然有许多例外),我们仍然使用与大约20年前相同的编程语言。它们中的大多数都获得了一些新的有趣特性,这些特性通常继承自lambda演算的函数式语言-lambda表达式(所谓的箭头函数)、不变性、参照透明性、列表理解;通常还结合了范畴论的结构-代数数据类型(乘积类型也称为元组和和类型,通常称为区分并)、函数器、应用程序(应用函数器)和一元。另一件不容忽视的事情是标准化语言及其工具的常见尝试-正式规范(甚至PHP也有一个后验规范)、链接器、用于自动格式化代码和构建美观文档的工具。
然而,我们在这里必须诚实。今天仍在使用的一些语言不知何故睡过了这个加速增长的阶段。当他们醒来时,已经来不及赶上了。让我们来谈谈PHP。
在当今世界,强静态类型对于创建健壮、高性能和安全的应用程序越来越重要,因此可以很好地说PHP的类型调整可能不是最合适的。考虑到仍然缺乏泛型,最近添加的渐进式类型是不够的。此外,类型系统本身存在许多特性-当谈论泛型时,不用说,无法表示对象装箱和取消装箱有点令人惊讶。现在,让我们回到2000年,当时PHP3还在游戏中,它的继任者PHP4发布了。
旧的PHP3手册明确提到类型杂耍是一种语言特性。类型杂耍不太好的一点是,当您的脚本走入歧途时,您需要在头脑中进行心理杂耍,而不会抛出任何异常。更有趣的是,它在PHP7.4中仍然存在。这是绕过PHP渐进式类型系统的最简单方法,因为运算符被嵌入到语言中,并且它们的语义不受类型系统本身的影响。现在让我们看看我们是否能重现近20年前的一些例子。
//1$foo=";0";;//$foo是字符串(ASCII 48)$foo++;//$foo是字符串";1";(ASCII 49)//2$foo+=1;//$foo现在是整数(2)$foo=$foo+1.3;//$foo现在是双精度(3.3)//3$foo=5+";10小猪。;//$foo为整数(15)。
这在一定程度上是正确的。您仍然可以对字符串使用后增量运算符,但变量将隐式转换为整数。
现在,将格式错误的整数文字(可以是字符串)与算术运算符一起使用是无效的。
但所有这些都很容易写,对我们人类来说也很自然。比方说,与OCaml相比,它具有较低的认知负荷,在OCaml中,算术运算符实际上是规则的中缀函数,在其参数上不是多态的。
(*这些是来自Ocaml REPL会话的代码示例。字符串';utop#';是提示符。*)utop#(+);;-:int->;int->;int=<;Fun>;utop#1+1;;-:int=2 utop#1.0+1.0;;错误:此表达式的类型为Float,但表达式的类型应为int utop#(+.);;-:Float->;Float->;Float=<;Fun>;utop#1.0+。1.0;;-:浮点=2。
当然,像加宽或缩小数值这样的类型提升在许多强类型编程语言中很普遍,但不像在PHP中那样容易脱离语言的类型系统。大多数情况下,它们需要显式发送信号-因此,将长整型缩小为短整型需要显式强制转换。即使这样,也不能仅通过使用算术运算符将字符串隐式转换为整数。
此外,强类型与弱类型和静态类型解析与动态类型解析之间存在非常明显的对比。事实证明,基于可预测的强制,您可以获得非常内聚和合理的强动态类型系统-Ruby就是这种语言的一个很好的例子。
帕斯卡语系的语系相当大。它的一些后代仍然在积极地维持着。例如,object Pascal最初是由苹果公司创建的实现。存在几种对象PASCAL风格的语言。Delphi就是这样一个例子。它有成熟的开源对应产品--Free Pascal。它是一个非常现代化的实现,支持模块化编程、泛型、委托、函数和运算符重载等等。它有很好的工具支持和包管理器。Free Pascal执行ISO 7185:1990官方Pascal标准。
从20世纪70年代开始,Pascal就是一种静态的强类型语言。它的作者Niklaus Wirth设计了几种编程语言,但并不是所有语言都是静态类型的(参见Euler)。这与PHP形成鲜明对比,PHP被设计为一种粘合语言,用于使静态网页不那么静态。
下面是对语言类型系统进行比较的简短、无意义但有趣的尝试,这些语言可能永远不应该以任何方式进行比较。具体地说,我们将看看为什么缺少泛型在PHP中是如此痛苦的原因。
泛型是一种允许您编写由其他类型参数化的代码的功能。有用的参数化结构最明显的例子是集合。
PHP有其专用的类似集合的数据类型来存储值-数组。一旦将某些内容放入数组中,就可以通过其索引进行检索。但是,有时您希望使用SPL集合之一,例如SplFixedArray,它可能被证明比普通数组更节省空间。与数组一样,您可以放入任何类型的值。
我从来没有见过会叫的洗碗机,但我不是这方面的专家。我们希望找到一种以狗感知的方式将元素插入数组集合的方法。一种方法是尝试下面这样的方法。
PHP致命错误:DogFixedArray::offsetSet(int$index,Dog$Dog)的声明必须与SplFixedArray::offsetSet($index,$newval)兼容。
恐怕行不通。PHP简单地禁止了这一点。如果基类中的参数没有类型化,那么它实际上根本没有类型-在PHP中称为“混合”。您不能在子类中指定它,因为这将意味着更改方法签名,而这是被合理禁止的。这是一个有点令人费解的概念,因为没有“混合”类型的提示,所以有时无法实现明显的方差规则。不过,对于这一点,存在一个RFC。
可以求助的一件事是在offsetSet中使用instanceof操作符。但是,应该注意,该操作符获取运行时信息,因此毕竟您需要牺牲一件事来换取另一件事-空间效率与运行时开销。
现在来看看Pascal是如何处理泛型集合的,以及这种处理对语言中的类型安全有什么明显的影响。
免费的Pascal附带了一个丰富的开源标准库,称为RTL(Runtime Library),其中包含一个名为FGL?(Free Pascal Generic List)的单元。以下是使用此单位的示例。
4.“Specialize”关键字用于创建参数化类型的新实例。从那时起,TDogList指的是TDog类型的对象集合。5.方法在类声明之外定义。6.“神奇”变量result的工作原理有点像许多主流语言中的return关键字。7.Create是实际构造函数的名称。9.事实证明,您甚至可以在Free Pascal中使用foreach循环。10.free Pascal没有垃圾收集器,因此对象在使用完后需要释放。
没有必要释放列表的项目,因为默认情况下,它持有其数据的所有权,并在数据被销毁时将其释放。可以通过将false作为第一个也是唯一一个参数传递给TFPGObjectList构造函数来控制它。
最后,第八点证明了不可能向TDog列表中添加TDishwaher类的对象。
在Free Pascal中声明泛型类型的新实例²是强制性的³,与C#相比有点繁琐:
在C#中,类型实例化是临时的,而在Pascal中,您需要事先在单元文件的“type”部分创建类型同义词。
在内部,Free Pascal对泛型的理解与C++或Java中的有所不同。编译单元时,会生成PPU文件。它包含有关该单元的元信息。对我们来说,最重要的是,它维护了一个令牌缓冲区,编译器使用该缓冲区根据泛型类型定义创建专门的实现。工作流程如下所示。
1.对于每个泛型类型:泛型TGeneric<;T>;=...。使用元信息在PPU文件内创建令牌缓冲区。2.对于每次出现:TSpecialized=Specialize TGeneric<;*>;,解析专门化并通过用T替换*来创建Conrete类型,其中*是简单类型。
在概念层面上,它与Java等其他语言没有太大不同,但实现有很大不同。因为专门化发生在编译时,所以Free Pascal的泛型不会像在Java中那样受到类型擦除的影响。
本文总结了为什么PHP中缺少泛型是令人痛苦的,以及为什么它松散的类型系统有时根本无助于编写好的代码。本文介绍了免费的Pascal对泛型的理解,以及它如何简化了即使在PHP的现代版本中也存在的许多问题。在Ruby等动态语言中,类型杂耍与强制并列。最后,演示了一个完整的Free Pascal工作示例,展示了如何通过使用泛型来缓解常见的类型问题。