寻求澄清有关弱类型语言的明显矛盾

Posted

技术标签:

【中文标题】寻求澄清有关弱类型语言的明显矛盾【英文标题】:Seeking clarification on apparent contradictions regarding weakly typed languages 【发布时间】:2012-04-13 08:21:31 【问题描述】:

我想我理解 strong typing,但每次我寻找弱类型的示例时,我最终都会找到简单地自动强制/转换类型的编程语言示例。

例如,在这篇名为Typing: Strong vs. Weak, Static vs. Dynamic 的文章中,Python 是强类型的,因为如果你尝试这样做会得到一个异常:

Python

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

然而,这样的事情在 Java 和 C# 中是可能的,我们不认为它们是弱类型的。

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C#

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

在另一篇名为 Weakly Type Languages 的文章中,作者说 Perl 是弱类型的,因为我可以将字符串连接到数字,反之亦然,而无需任何显式转换。

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

所以同样的例子使得 Perl 是弱类型的,而不是 Java 和 C#?。

哎呀,这令人困惑

作者似乎暗示,一种阻止对不同类型的值应用某些操作的语言是强类型的,相反则意味着弱类型。

因此,在某些时候,我觉得如果一种语言提供大量自动转换或类型之间的强制转换(如 perl)最终可能会被认为是弱类型,而其他只提供少量转换的语言可能最终被认为是强类型的。

不过,我倾向于认为我的这种解释一定是错误的,我只是不知道为什么或如何解释它。

所以,我的问题是:

真正弱类型的语言到底意味着什么? 您能否提及任何与语言自动转换/自动强制无关的弱类型的好例子? 一种语言可以同时进行弱类型和强类型吗?

【问题讨论】:

强类型与弱类型完全是关于类型转换(还有什么可能?)如果你想要一个“非常”弱语言的例子,请看这个:destroyallsoftware.com/talks/wat。 @Wildduck 所有语言都提供类型转换,但并非所有语言都被视为弱类型。下面显示的示例演示了程序员如何根据与其他被认为是强类型的语言相同的示例来考虑弱类型的语言。因此,我的问题仍然存在。有什么区别? 我认为简短的回答是“Typedness”不是二进制状态。 Java 和 C# 的类型更强,但不是绝对的。 我相信这更适合Software Engineering。 @Brendan 将浮点数和整数相加怎么样? Python中的整数不是被强制转换为浮点数吗?你现在会说 Python 不是绝对强类型的吗? 【参考方案1】:

更新:This question was the subject of my blog on the 15th of October, 2012.感谢您提出的好问题!


“弱类型化”语言的真正含义是什么?

它的意思是“这种语言使用了一种我觉得讨厌的类型系统”。相比之下,“强类型”语言是一种具有我觉得令人愉快的类型系统的语言。

这些术语基本上没有意义,您应该避免使用它们。 Wikipedia 为“强类型”列出了十一种不同的含义,其中一些是相互矛盾的。这表明在任何涉及“强类型”或“弱类型”的对话中,产生混淆的可能性都很高。

您真正可以肯定地说的是,正在讨论的“强类型”语言在类型系统中存在一些额外的限制,无论是在运行时还是编译时,而正在讨论的“弱类型”语言所缺乏的。如果没有进一步的上下文,则无法确定该限制可能是什么。

不要使用“强类型”和“弱类型”,您应该详细描述您的意思是哪种类型安全。例如,C# 是一种静态类型 语言和一种类型安全 语言和一种内存安全 语言,在大多数情况下时间>。 C# 允许违反所有这三种形式的“强”类型。强制转换运算符违反了静态类型;它对编译器说“我比你更了解这个表达式的运行时类型”。如果开发者错了,那么运行时会抛出异常以保护类型安全。如果开发人员希望破坏类型安全或内存安全,他们可以通过设置“不安全”块来关闭类型安全系统来实现。在不安全的块中,您可以使用指针魔术将 int 视为浮点数(违反类型安全)或写入您不拥有的内存。 (违反内存安全。)

C# 施加了在编译时和运行时都检查的类型限制,因此与编译时检查或运行时检查较少的语言相比,它是一种“强类型”语言。 C# 还允许您在特殊情况下围绕这些限制进行最终运行,与不允许您进行此类最终运行的语言相比,C# 使其成为“弱类型”语言。

究竟是什么?这是不可能的;这取决于说话者的观点以及他们对各种语言特征的态度。

【讨论】:

@edalorzo:它基于品味和个人意见,关于 (1) 类型理论的哪些方面是相关的,哪些方面是不相关的,以及 (2) 是否需要一种语言来执行 或仅仅鼓励 类型限制。正如我所指出的,可以合理地说 C# 是强类型的,因为它允许并鼓励静态类型,也可以合理地说它是弱类型的,因为它允许这种可能性 违反类型安全。 @edalorzo:至于组装,还是见仁见智。汇编语言编译器不允许您将 64 位双精度数从堆栈移动到 32 位寄存器;它将允许您将 32 位指针从堆栈移动到 64 位 double 到 32 位寄存器中。从这个意义上说,该语言是“类型安全的”——它基于数据的类型分类对程序的合法性施加了限制。该限制是“强”还是“弱”是一个见仁见智的问题,但这显然是一个限制。 我想我现在明白你的意思了,真正的弱类型语言必须完全无类型或单类型,这在现实生活中几乎是不可能的。因此,任何语言都有一定的类型定义,这是安全的,并且取决于语言提供的违反或操纵其数据或数据类型的漏洞数量,您最终可能会或多或少地认为它是弱类型的,甚至可能在仅在某些情况下。 @edalorzo:正确。例如,无类型的 lambda 演算几乎是你能得到的弱类型。每个函数都是从函数到函数的函数;任何数据都可以不受限制地传递给任何函数,因为一切都是“同一类型”。无类型 lambda 演算中表达式的有效性仅取决于其句法形式,而不取决于将某些表达式分类为具有某些类型的语义分析。 @Mark 我会再给他一个+1,因为他预测每个人都会对该主题提供不同的解释。这种“弱打字”似乎是一种“神话概念”或“都市传说”,大家都见过,但没有人能证明它的存在:-)【参考方案2】:

正如其他人所指出的那样,“强类型”和“弱类型”这两个术语有很多不同的含义,以至于您的问题没有单一的答案。但是,由于您在问题中特别提到了 Perl,让我试着解释一下 Perl 在什么意义上是弱类型的。

关键是,在 Perl 中,没有“整数变量”、“浮点变量”、“字符串变量”或“布尔变量”之类的东西。事实上,据用户(通常)所知,甚至没有整数、浮点数、字符串或布尔值:你所拥有的只是“标量”,它们都是同时。例如,你可以这样写:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

当然,正如您正确指出的那样,所有这些都可以看作只是类型强制。但关键是,在 Perl 中,类型是总是强制的。事实上,用户很难判断变量的内部“类型”可能是什么:在上面示例的第 2 行,询问 $bar 的值是字符串 "9" 还是数字 @987654328 @ 几乎毫无意义,因为就 Perl 而言,它们是一回事。实际上,Perl 标量甚至可以在内部同时拥有 both 一个字符串和一个数值,例如上面第 2 行之后的 $foo 的情况。

所有这一切的另一面是,由于 Perl 变量是无类型的(或者更确切地说,不向用户公开它们的内部类型),操作符不能被重载来为不同类型的参数做不同的事情;您不能只说“此运算符将对数字执行 X,对字符串执行 Y”,因为该运算符无法(不会)判断其参数是哪种值。

因此,例如,Perl 拥有并需要一个数字加法运算符 (+) 和一个字符串连接运算符 (.):正如您在上面看到的,添加字符串 ("1" + "2" == "3") 或连接数字 (1 . 2 == 12)。同样,数值比较运算符==!=<><=>=<=> 比较其参数的数值,而字符串比较运算符eqneltgtlegecmp 按字典顺序将它们作为字符串进行比较。所以2 < 10,但2 gt 10(但"02" lt 10,而"02" == 2)。 (请注意,某些 其他 语言,例如 javascript,尝试适应类似 Perl 的弱类型,而 进行运算符重载。这通常会导致丑陋,例如失去关联性对于+。)

(美中不足的是,由于历史原因,Perl 5 确实有一些极端情况,例如按位逻辑运算符,其行为取决于其参数的内部表示。这些通常被认为是令人讨厌的设计缺陷,因为内部表示可能会因令人惊讶的原因而发生变化,因此预测这些运算符在给定情况下所做的事情可能会很棘手。)

说了这么多,可以说 Perl 确实 有强类型;他们只是不是你可能期望的那种类型。具体来说,除了上面讨论的“标量”类型,Perl 还有两种结构化类型:“数组”和“散列”。这些非常与标量不同,以至于 Perl 变量具有不同的 sigils 指示它们的类型($ 用于标量,@ 用于数组,% 用于哈希) 1。这些类型之间存在强制规则,因此您可以编写例如%foo = @bar,但其中许多是有损的:例如,$foo = @bar 将数组@bar长度 分配给$foo,而不是其内容。 (此外,还有一些其他奇怪的类型,例如 typeglob 和 I/O 句柄,您不会经常看到这些类型。)

此外,这个漂亮设计的一个小缺陷是引用类型的存在,它是一种特殊的标量(使用ref 运算符,可以与普通标量区分开来) .可以将引用用作普通标量,但它们的字符串/数字值并不是特别有用,如果您使用普通标量操作修改它们,它们往往会失去其特殊的引用性。此外,任何 Perl 变量2 都可以blessed 到一个类,将它变成该类的一个对象; Perl 中的 OO 类系统与上面描述的原始类型(或无类型)系统有些正交,尽管它在遵循duck typing 范式的意义上也是“弱的”。一般的看法是,如果您发现自己在 Perl 中检查对象的类,那么您做错了什么。


1 实际上,符号表示正在访问的值的类型,例如数组@foo 中的第一个标量表示为$foo[0]。详情请见perlfaq4。

2 Perl 中的对象(通常)通过对它们的引用来访问,但实际上得到blessed 的是引用指向的(可能是匿名的)变量。然而,祝福确实是变量的属性,而不是它的值,例如将实际的祝福变量分配给另一个变量只会给你一个浅薄的、没有祝福的副本。详情请见perlobj。

【讨论】:

【参考方案3】:

除了 Eric 所说的之外,请考虑以下 C 代码:

void f(void* x);

f(42);
f("hello");

与 Python、C#、Java 或诸如此类的语言相比,以上是弱类型,因为我们丢失了类型信息。 Eric 正确地指出,在 C# 中,我们可以通过强制转换来绕过编译器,有效地告诉它“我比你更了解这个变量的类型”。

但即便如此,运行时仍会检查类型!如果强制转换无效,运行时系统将捕获它并抛出异常。

使用类型擦除,这不会发生 - 类型信息被丢弃。在 C 中对void* 的强制转换正是这样做的。在这方面,上面与void f(Object x)之类的C#方法声明有本质的区别。

(从技术上讲,C# 还允许通过不安全的代码或编组进行类型擦除。)

This 是弱类型。其他一切都只是静态与动态类型检查的问题,即检查类型的时间何时

【讨论】:

+1 好点,你现在让我认为类型擦除是一种也可以暗示“弱类型”的功能。 Java 中也有类型擦除,在运行时,类型系统会让你违反编译器永远不会批准的约束。 C 示例很好地说明了这一点。 同意,洋葱或地狱有层。这些似乎是类型弱点的更重要定义。 @edalorzo 我不认为这是相当相同,因为即使 Java 允许您绕过编译器,运行时类型系统仍然会捕获违规行为。所以 Java 运行时类型系统在这方面是强类型的(也有例外,例如可以使用反射来规避访问控制)。 @edalorzo 您只能以这种方式绕过编译器,而不是运行时系统。重要的是要认识到诸如 Java 和 C#(在一定程度上也包括 C++)之类的语言有一个类型系统,它被确保了两次:一次在编译时,一次在运行时。 void* 突破了两种类型检查。泛型类型擦除不会,它只会绕过编译时检查。在这方面,它与显式转换(由 Eric 提到)完全一样。 @edalorzo 你的困惑:我们不应该。区别很流畅。是的,类型擦除使 Java 在这方面弱类型化。我的观点是,即使使用泛型类型擦除,你仍然无法绕过运行时类型检查除非你也使用反射【参考方案4】:

一个完美的例子来自the wikipedia article of Strong Typing:

通常强类型意味着编程语言对允许发生的混合设置严格的限制。

弱打字

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

强类型

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

请注意,弱类型语言可以混合不同类型而不会出错。强类型语言要求输入类型是预期的类型。在强类型语言中,可以转换类型(str(a) 将整数转换为字符串)或强制类型转换(int(b))。

这一切都取决于打字的解释。

【讨论】:

但这会导致问题中提供的相互矛盾的例子。强类型语言可能包含隐式强制,这意味着您的两个“类型错误”示例中的一个(或两个)会自动转换为后两个示例的相关内容,但通常该语言仍然是强类型。 是的。我想你可以说有不同程度的强类型和弱类型。隐式转换可能意味着该语言的类型比不进行隐式转换的语言强。【参考方案5】:

我想通过我自己对该主题的研究为讨论做出贡献,因为其他人评论和贡献我一直在阅读他们的答案并关注他们的参考资料,并且我发现了有趣的信息。正如建议的那样,很可能在程序员论坛中讨论其中的大部分内容会更好,因为它似乎更具理论性而不是实践性。

从理论上讲,我认为 Luca Cardelli 和 Peter Wegner 撰写的名为 On Understanding Types, Data Abstraction and Polymorphism 的文章是我读过的最好的论据之一。

一种类型可以被视为一套衣服(或一套盔甲) 保护底层 untyped 表示不受任意或 意外使用。它提供了一个保护罩,可以隐藏 底层表示并限制对象可能交互的方式 与其他物体。在无类型系统中,无类型对象是裸的 因为底层的表示是公开的,所有人都可以看到。 违反类型系统涉及删除保护集 服装和操作直接在裸体代表。

这句话似乎暗示弱类型化可以让我们访问一个类型的内部结构并像操作其他东西(另一种类型)一样操作它。也许我们可以用不安全的代码(Eric 提到的)或 Konrad 提到的 c 类型擦除指针来做些什么。

文章继续……

所有表达式都类型一致的语言被调用 强类型语言。如果一种语言是强类型的,它的编译器 可以保证它接受的程序会在没有类型的情况下执行 错误。一般来说,我们应该争取强类型化,并采用 尽可能使用静态类型。请注意,每个静态类型 语言是强类型的,但反过来不一定正确。

因此,强类型意味着不存在类型错误,我只能假设弱类型意味着相反:可能存在类型错误。在运行时还是编译时?在这里似乎无关紧要。

有趣的是,根据这个定义,像 Perl 这样具有强大类型强制的语言会被认为是强类型的,因为系统没有失败,但它通过将类型强制转换为适当且定义明确的等价来处理这些类型。

另一方面,我能说ClassCastExceptionArrayStoreException(在Java 中)和InvalidCastExceptionArrayTypeMismatchException(在C# 中)的余量将表明弱类型的水平,至少在编译时时间?埃里克的回答似乎同意这一点。

在此问题的一个答案中提供的参考资料之一中提供的第二篇名为 Typeful Programming 的文章中,Luca Cardelli 深入研究了类型违规的概念:

大多数系统编程语言都允许任意类型违规, 有些不分青红皂白,有些只在程序的受限部分。 涉及类型违规的操作称为不健全的。类型 违规行为分为几类[其中我们可以提及]:

基本值强制转换:包括整数、布尔值、字符、集合等之间的转换。不需要类型违规 在这里,因为可以提供内置接口来执行 以类型正确的方式进行强制转换。

因此,像运算符提供的类型强制可能被视为类型违规,但除非它们破坏类型系统的一致性,否则我们可能会说它们不会导致弱类型系统。

基于此,Python、Perl、Java 或 C# 都不是弱类型。

Cardelli 提到了两种类型错误,我很好地考虑了真正弱类型的情况:

地址算法。如有必要,应该有一个内置的(不健全的)接口,提供对地址的足够操作 和类型转换。各种情况涉及指向 堆(对于重新定位收集器非常危险),指向 堆栈,指向静态区域的指针,以及指向其他地址的指针 空格。有时数组索引可以代替地址算术。 内存映射。这涉及将内存区域视为非结构化数组,尽管它包含结构化数据。这是 典型的内存分配器和收集器。

在 C 等语言中(由 Konrad 提到)或通过 .Net 中的不安全代码(由 Eric 提到)可能会发生这种事情,这确实意味着弱类型。

我相信目前为止最好的答案是 Eric 的,因为这个概念的定义是非常理论化的,当涉及到一种特定的语言时,所有这些概念的解释可能会导致不同的有争议的结论。

【讨论】:

【参考方案6】:

弱类型确实意味着很大比例的类型可以被隐式强制,试图猜测编码器的意图。

强类型意味着类型不被强制,或者至少强制更少。

静态类型意味着你的变量类型是在编译时确定的。

最近很多人都混淆了“明显类型”和“强类型”。 “显式类型”意味着您显式声明变量的类型。

Python 主要是强类型的,尽管您可以在布尔上下文中使用几乎任何东西,布尔值可以在整数上下文中使用,您可以在浮点上下文中使用整数。它不是明显类型的,因为您不需要声明您的类型(Cython 除外,它不完全是 python,虽然很有趣)。它也不是静态类型的。

C 和 C++ 是明显类型化、静态类型化和某种强类型化的,因​​为您声明了类型,类型是在编译时确定的,您可以混合使用整数和指针,或者整数和双精度数,甚至将指针转换为一种类型转换成指向另一种类型的指针。

Haskell 是一个有趣的例子,因为它不是明显类型的,但它也是静态和强类型的。

【讨论】:

+1 因为我喜欢创造的术语“显式类型”,它对 Java 和 C# 等语言进行分类,您必须显式声明类型并将它们与其他静态类型语言(如 Haskell 和 Scala)区分开来推理起着重要作用,正如您所说,这通常会使人们感到困惑,并使他们相信这些语言是动态类型的。【参考方案7】:

强弱类型不仅是关于有多少值被语言自动强制转换为另一种数据类型的连续统一体,还在于实际值的强弱程度 被输入。在 Python 和 Java 中,尤其是在 C# 中,值的类型是一成不变的。在 Perl 中,并没有那么多——实际上只有少数几种不同的值类型可以存储在一个变量中。

让我们一一打开案例。


Python

在 Python 示例 1 + "1" 中,+ 运算符为 int 类型调用 __add__,并为其提供字符串 "1" 作为参数 - 但是,这会导致 NotImplemented:

>>> (1).__add__('1')
NotImplemented

接下来,解释器尝试str的__radd__

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

当它失败时,+ 运算符失败,结果为TypeError: unsupported operand type(s) for +: 'int' and 'str'。因此,该异常并没有说明强类型,但运算符+ 不强制其参数自动为同一类型这一事实表明 Python 不是连续体中最弱类型的语言。

另一方面,在 Python 中 'a' * 5 实现的:

>>> 'a' * 5
'aaaaa'

也就是说,

>>> 'a'.__mul__(5)
'aaaaa'

操作不同的事实需要一些强类型 - 但是与 * 在相乘之前将值强制为数字相反,仍然不一定会使值弱类型。


Java

Java 示例String result = "1" + 1; 之所以有效,是因为为了方便起见,运算符+ 对字符串进行了重载。 Java + 运算符将序列替换为创建StringBuilder(参见this):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

这是一个非常静态类型的示例,没有实际的强制 - StringBuilder 有一个方法 append(Object) 在这里专门使用。文档说明如下:

附加Object 参数的字符串表示形式。

整体效果就像将参数转换为 通过String.valueOf(Object) 方法获得的字符串,以及 然后将该字符串附加到此字符序列中。

String.valueOf 然后在哪里

返回 Object 参数的字符串表示形式。 [返回] 如果参数是null,则等于"null"的字符串;否则返回obj.toString()的值。

因此,这是一个绝对没有语言强制的情况 - 将所有关注点委托给对象本身。


C#

根据Jon Skeet answer here,运算符+ 甚至没有为string 类重载——类似于Java,这只是编译器生成的便利,这要归功于静态和强类型。


Perl

正如perldata 解释的那样,

Perl 具有三种内置数据类型:标量、标量数组和标量关联数组,称为“哈希”。标量是单个字符串(任何大小,仅受可用内存限制)、数字或对某物的引用(将在 perlref 中讨论)。普通数组是按数字索引的标量的有序列表,从 0 开始。散列是按相关字符串键索引的标量值的无序集合。

然而,Perl 没有单独的数据类型用于数字、布尔值、字符串、空值、undefineds、对其他对象的引用等——它只有一种类型,即标量类型; 0 是与“0”一样多的标量值。设置为字符串的标量 变量 可以真正变为数字,并且从那里开始的行为不同于“只是一个字符串”if it is accessed in a numerical context。标量可以容纳 Perl 中的任何东西,它与系统中存在的对象一样多。而在 Python 中,名称只是指对象,而在 Perl 中,名称中的标量值是可变对象。此外,面向对象的类型系统粘在此之上:perl 中只有 3 种数据类型——标量、列表和散列。 Perl 中的用户定义对象是对包的引用(即指向前 3 个中的任何一个的指针)blessed - 您可以随时获取任何此类值并祝福它到任何类。

Perl 甚至允许您随心所欲地更改值的类 - 这在 Python 中是不可能的,在 Python 中创建某个类的值您需要使用 object.__new__ 或类似名称显式构造属于该类的值。在 Python 中你不能真正改变对象的本质,而在 Perl 中你可以做很多事情:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

因此类型标识被弱绑定到变量,并且可以通过任何动态引用来改变它。事实上,如果你这样做了

my $another = $val;

\$another 没有类标识,尽管\$val 仍将提供祝福引用。


TL;DR

对于 Perl 来说,弱类型不仅仅是自动强制,更重要的是值的类型本身并不是一成不变的,这与 Python 不同,Python 是一种动态但非常强类型的语言。 python 在1 + "1" 上给出TypeError 表明该语言是强类型的,尽管相反的做一些有用的事情,如在Java 或C# 中并不排除它们是强类型语言。

【讨论】:

这完全是混乱的。 Perl 5 variables 没有类型与 values 没有任何关系,values 总是有类型。 @JimBalter 好吧,是的,一个值的类型是字符串或数字,根据标量变量是包含字符串还是数字,它在某些上下文中的行为可能会有所不同;但是变量中包含的值可以通过访问变量来改变类型,并且由于值本身存在于变量,因此值本身可以被认为在类型之间是可变的。 值不会改变类型——这是不连贯的;值始终是类型。变量包含的值可以更改。从 1 到“1”的变化与从 1 到 2 的变化一样多。 Perl 等弱类型语言允许根据上下文隐式发生前一种类型的值更改。但即使是 C++ 也允许通过运算符定义进行这种隐式转换。正如 Eric Lippert 所指出的,弱类型是一种非常非正式的属性,实际上并不是描述语言的有用方式。 P.S.可以看出,即使在 Perl 中, 和 "" 也有不同的值,而不仅仅是不同的类型。 Perl 通过隐式转换使 和 "" 看起来在大多数情况下具有相同的值,但这种错觉并不完整;例如"12" | "34" 是 36 而 12 | 34 是 46。另一个例子是“00”在大多数情况下在数值上等于 00,但在布尔上下文中不等于“00”为真但 00 为假。【参考方案8】:

正如许多其他人所表达的,“强”与“弱”类型的整个概念是有问题的。

作为一个原型,Smalltalk 是非常强类型的——如果两个对象之间的操作不兼容,它将总是引发异常。但是,我怀疑这个列表中很少有人会称 Smalltalk 为强类型语言,因为它是动态类型的。

我发现“静态”与“动态”类型的概念比“强”与“弱”更有用。静态类型语言的所有类型都在编译时计算出来,否则程序员必须显式声明。

与动态类型语言相比,类型是在运行时执行的。这通常是多态语言的要求,因此关于两个对象之间的操作是否合法的决定不必由程序员提前决定。

在多态、动态类型的语言(如 Smalltalk 和 Ruby)中,将“类型”视为“对协议的一致性”更为有用。如果一个对象以与另一个对象相同的方式遵守协议——即使这两个对象不共享任何继承或混合或其他巫术——它们被运行时系统视为相同的“类型”。更准确地说,此类系统中的对象是自治的,并且可以决定响应引用任何特定参数的任何特定消息是否有意义。

想要一个对象可以对消息“+”做出有意义的响应,并带有描述蓝色的对象参数?您可以在动态类型的语言中做到这一点,但在静态类型的语言中会很痛苦。

【讨论】:

我认为动态与静态类型的概念不在讨论中。尽管我不得不说我不相信多态性在静态类型语言中无论如何都会受到阻碍。最终,类型系统验证给定操作是否适用于给定操作数,无论是在运行时还是在编译时。此外,其他形式的多态性,如参数函数和类,允许在静态类型语言中以您描述的与动态类型相比非常困难的方式组合类型,如果提供类型推断则更好。【参考方案9】:

我喜欢@Eric Lippert's answer,但要解决这个问题 - 强类型语言通常对程序每个点的变量类型有明确的了解。弱类型语言不这样做,因此它们可以尝试执行特定类型可能无法执行的操作。 它认为看到这一点的最简单方法是在函数中。 C++:

void func(string a) ...

变量a已知为字符串类型,任何不兼容的操作都会在编译时被捕获。

Python:

def func(a)
  ...

变量a 可以是任何东西,我们可以拥有调用无效方法的代码,该方法只会在运行时被捕获。

【讨论】:

我认为您可能会将动态类型与静态类型与强类型与弱类型相混淆。在您的代码的两个版本中,运行时类型系统都非常清楚 a 是一个字符串。只是在第一种情况下,编译器可以告诉你,在第二种情况下它不能。但这不会使这些语言中的任何一种成为弱类型。

以上是关于寻求澄清有关弱类型语言的明显矛盾的主要内容,如果未能解决你的问题,请参考以下文章

python到底是强类型语言,还是弱类型语言?

什么是编程语言的强类型,弱类型

不需要声明变量类型的语言的质量是弱类型还是动态类型的例子

弱类型语言与强类型语言

强类型和弱类型,静态类型和动态类型语言如何区分

简单理解弱类型动态类型解释型语言