问题:寻求澄清有关弱类型语言的明显矛盾
我想我了解强类型,但是每次我寻找弱类型的示例时,我最终都会找到简单地自动强制转换类型的编程语言示例。
例如,在这篇名为“ 打字:强vs.弱”,“静态vs.动态 ”的文章中,Python是强类型的,因为如果尝试执行以下操作,则会得到异常:
Python
1 + "1"
Traceback (most recent call last):
File "", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int' and 'str'
但是,在Java和C#中这种事情是可能的,因此我们不认为它们只是弱类型的。
爪哇
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);
在另一篇名为弱类型语言的文章中,作者说Perl弱类型仅仅是因为我可以将字符串连接成数字,反之亦然,而无需任何显式转换。
佩尔
$a=10;
$b="a";
$c=$a.$b;
print $c; #10a
因此,同一示例使Perl的类型较弱,但Java和C#?的类型却没有。
真是的
作者似乎暗示一种阻止对不同类型的值执行某些操作的语言是强类型的,而相反的意思是弱类型。
因此,在某些时候,我感到被提示相信,如果一种语言在类型之间提供大量自动转换或强制转换(例如perl),最终可能会被认为是弱类型,而其他仅提供少量转换的语言可能最终会被视为弱类型。被认为是强类型的。
但是,我倾向于相信,在这种相互交流中我一定是错的,我只是不知道为什么或如何解释它。
因此,我的问题是:
- 语言真正弱键入到底意味着什么?
- 您能否提及与该语言完成的自动转换/自动强制无关的弱类型的任何好例子?
- 语言可以同时弱输入和强输入吗?
回答 0
更新:这个问题是我在2012年10月15日发布的博客的主题。感谢您提出的伟大问题!
语言“弱类型化”的真正含义是什么?
它的意思是“这种语言使用的类型系统令人讨厌”。相比之下,“强类型”语言是具有令人愉悦的类型系统的语言。
这些术语本质上是没有意义的,您应该避免使用它们。维基百科列出了“强类型”的十一种不同含义,其中有几种是矛盾的。这表明在涉及术语“强类型”或“弱类型”的任何对话中,造成混乱的可能性很高。
您真正可以肯定地说的是,正在讨论的“强类型”语言在类型系统上有一些其他限制,无论是在运行时还是编译时,都缺乏在讨论中的“弱类型”语言。没有进一步的上下文,就无法确定该限制是什么。
不应使用“强类型”和“弱类型”,而应详细描述您所指的类型安全。例如,C#在大多数情况下是静态类型的语言,类型安全的语言和内存安全的语言。C#允许违反所有三种“强”类型的输入形式。强制转换运算符违反静态类型;它对编译器说:“我比您更了解此表达式的运行时类型”。如果开发人员错误,则运行时将抛出异常以保护类型安全。如果开发人员希望破坏类型安全性或存储安全性,则可以通过制作“不安全”块来关闭类型安全性系统来做到这一点。在不安全的块中,您可以使用指针魔术来将int视为浮点型(违反类型安全性)或写入您不拥有的内存。(破坏内存安全。)
C#施加了在编译时和运行时都进行检查的类型限制,因此与进行较少的编译时检查或较少的运行时检查的语言相比,C#使其成为“强类型”语言。C#还允许您在特殊情况下绕这些限制进行最终运行,与不允许您进行此类最终运行的语言相比,它成为“弱类型”语言。
到底是什么 很难说。这取决于说话者的观点及其对各种语言功能的态度。
回答 1
正如其他人指出的那样,术语“强类型”和“弱类型”具有许多不同的含义,因此您的问题没有一个答案。但是,由于您在问题中特别提到了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"
还是数字9
几乎没有意义,因为就Perl而言,它们是同一回事。实际上,Perl标量甚至有可能在内部同时具有字符串和数字值,例如$foo
上面第2行之后的情况。
不利的一面是,由于Perl变量是无类型的(或者,不向用户公开其内部类型),因此不能重载运算符以对不同类型的参数执行不同的操作。您不能只说“此运算符将对数字执行X,对字符串执行Y”,因为该运算符无法(不会)告诉其参数是哪种类型的值。
因此,例如,Perl同时具有并且需要数字加法运算符(+
)和字符串连接运算符(.
):如上所述,添加字符串("1" + "2" == "3"
)或连接数字(1 . 2 == 12
)很好。同样,数字比较操作符==
,!=
,<
,>
,<=
,>=
并<=>
比较它们的参数的数值,而字符串比较操作符eq
,ne
,lt
,gt
,le
,ge
和cmp
字典顺序比较它们为字符串。所以2 < 10
,但是2 gt 10
(但是"02" lt 10
,虽然"02" == 2
)。(请注意,某些其他语言(例如JavaScript)会尝试容纳类似Perl的弱类型还做运算符重载。这通常会导致丑陋,例如失去与+
。)的关联性。
(美中不足的是,由于历史原因,Perl 5确实有一些极端情况,例如按位逻辑运算符,其行为取决于其参数的内部表示。通常认为这是令人讨厌的设计缺陷,因为内部表述可能会由于令人惊讶的原因而发生变化,因此仅预测那些操作员在给定情况下的操作可能很棘手。)
综上所述,可以说Perl 确实具有强类型。它们不是您可能期望的那种类型。具体来说,除了上面讨论的“标量”类型外,Perl还具有两种结构化类型:“数组”和“哈希”。这些是非常从标量不同,到了那里的Perl变量具有不同的点印记,指示它们的类型($
用于标量,@
数组,%
对于散列)1。有有这些类型之间的强制规则,这样你就可以写例如%foo = @bar
,但其中不少是相当有损耗:例如,$foo = @bar
分配长度的数组 @bar
来$foo
,而不是其内容。(此外,还有其他一些奇怪的类型,例如typeglob和I / O句柄,您通常不会看到它们是公开的。)
同样,在这种出色的设计中,有一个小缺点是引用类型的存在,它们是一种特殊的标量(可以使用ref
运算符将其与普通标量区分开)。可以将引用用作普通标量,但是它们的字符串/数字值并不是特别有用,并且如果您使用普通标量操作对其进行修改,它们往往会失去其特殊的引用性。同样,任何Perl变量2都可以作为范例。通常的意见是,如果您发现自己在Perl中检查了对象的类,则说明您做错了什么。bless
编入一个类,将其变成该类的对象。Perl中的OO类系统在某种程度上与上述原始类型(或无类型性)系统正交,尽管它在遵循鸭子类型的意义上也是“弱”的
1实际上,印记表示被访问的值的类型,以使得例如在阵列中的第一标@foo
记$foo[0]
。有关更多详细信息,请参见perlfaq4。
2(通常)通过引用访问Perl中的对象,但实际上得到的bless
是引用所指向的(可能是匿名的)变量。但是,祝福实际上是变量的属性,而不是变量的值,因此,例如,将实际的祝福变量分配给另一个变量,只会给您一个浅浅的,没有祝福的副本。有关更多详细信息,请参见perlobj。
回答 2
除了Eric所说的以外,请考虑以下C代码:
void f(void* x);
f(42);
f("hello");
与诸如Python,C#,Java或其他语言之类的语言相比,上面的类型是弱类型的,因为我们会丢失类型信息。Eric正确指出,在C#中,我们可以通过强制转换来绕过编译器,有效地告诉它“我比您更了解此变量的类型”。
但是即使那样,运行时仍会检查类型!如果强制转换无效,则运行时系统将对其进行捕获并引发异常。
使用类型擦除不会发生这种情况–类型信息会被丢弃。强制转换void*
为C可以做到这一点。在这方面,以上内容与C#方法声明(例如)从根本上有所不同void f(Object x)
。
(从技术上讲,C#还允许通过不安全的代码或编组来擦除类型。)
这是尽可能弱的类型。其他的一切只是一个静态还是动态类型检查,即时间的事当一个类型被选中。
回答 3
Wikipedia的“强类型”文章就是一个很好的例子:
通常,强类型意味着编程语言对允许发生的混合进行了严格的限制。
弱打字
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)
)。
这一切都取决于键入的解释。
回答 4
我想通过自己对这个问题的研究来为讨论做出贡献,正如其他人评论并做出贡献一样,我一直在阅读他们的答案并遵循他们的参考文献,并且发现了有趣的信息。如建议的那样,在程序员论坛中可能会更好地讨论其中的大多数内容,因为它似乎是理论上的而非实际的。
从理论的角度来看,我认为Luca Cardelli和Peter Wegner撰写的关于理解类型,数据抽象和多态性的文章是我所读过的最好的论据之一。
一种类型可以看作是一套衣服(或盔甲),可以保护基础的无类型表示形式免受任意使用或非预期使用。它提供了一个保护性遮盖物,该遮盖物隐藏了底层表示并限制了对象与其他对象交互的方式。在无类型的系统中,无类型的对象是裸露 的,其基础表示形式公开给所有人看。违反字体系统需要脱下防护服并直接在裸露的衣服上操作。
该说法似乎表明,弱类型输入将使我们能够访问类型的内部结构并像对待其他类型(另一种类型)一样对其进行操作。也许我们可以用不安全的代码(由Eric提及)或由Konrad提及的用c类型擦除的指针来做。
文章继续…
所有表达式类型一致的语言称为强类型语言。如果语言是强类型的,则其编译器可以保证所接受的程序将在没有类型错误的情况下执行。通常,我们应该努力实现强类型化,并在可能的情况下采用静态类型。请注意,每种静态类型的语言都是强类型的,但相反不一定是正确的。
因此,强类型表示没有类型错误,我只能假设弱类型意味着相反:可能存在类型错误。在运行时还是编译时?在这里似乎无关紧要。
有趣的是,按照该定义,具有强大类型强制性的语言(如Perl)将被视为强类型化的,因为系统不会失败,但是它通过将类型强制为适当的和定义良好的对等来处理类型。
另一方面,我是否可以说ClassCastException
和ArrayStoreException
(在Java中)和InvalidCastException
,ArrayTypeMismatchException
(在C#中)的允许表示至少在编译时处于弱类型的水平?埃里克的答案似乎同意这一点。
在此问题的答案之一提供的参考文献之一中提供的第二篇名为类型化编程的文章中,Luca Cardelli深入研究了类型冲突的概念:
大多数系统编程语言都允许任意类型的违反,有些是不加区分的,有些仅在程序的受限部分中。涉及类型冲突的操作称为不健全。类型违规分为几类[我们可以提到]:
基本值强制:包括整数,布尔值,字符,集合等之间的转换。这里不需要类型冲突,因为可以提供内置接口来以类型健全的方式执行强制。
这样,像操作员提供的那样的类型强制可以被认为是类型冲突,但是除非它们破坏了类型系统的一致性,否则我们可以说它们不会导致弱类型系统。
基于此,Python,Perl,Java或C#都不是弱类型。
Cardelli提到了两个类型错误,我很好地考虑了真正弱类型的情况:
地址算术。如有必要,应该有一个内置的(不健全的)接口,提供对地址和类型转换的适当操作。各种情况都涉及到堆的指针(对于重定位收集器非常危险),指向堆栈的指针,指向静态区域的指针以及指向其他地址空间的指针。有时,数组索引可以代替地址算法。 内存映射。这涉及将内存区域视为非结构化数组,尽管它包含结构化数据。这是内存分配器和收集器的典型特征。
诸如C(由Konrad提及)或.Net中不安全代码(由Eric提及)之类的语言中可能发生的这类事情实际上暗示着弱键入。
我相信到目前为止,最好的答案是埃里克(Eric),因为这个概念的定义是非常理论性的,当涉及到特定语言时,对所有这些概念的解释可能会得出不同的结论。
回答 5
弱类型的确确实意味着可以隐式强制转换很大比例的类型,试图猜测编码器的意图。
强类型意味着没有强制类型,或者至少没有强制类型。
静态类型意味着您的变量类型在编译时确定。
最近,许多人将“明显键入”与“强烈键入”混淆。“清单式输入”是指您明确声明变量的类型。
Python通常是强类型的,尽管您可以在布尔上下文中使用几乎所有内容,布尔值可以在整数上下文中使用,并且您可以在浮点上下文中使用整数。它没有明显的类型,因为您不需要声明您的类型(Cython除外,尽管它很有趣,但它并不完全是python)。它也不是静态类型的。
C和C ++是明显类型化,静态类型化和某种程度强类型化的,因为您声明类型,类型是在编译时确定的,并且可以混合使用整数和指针,整数和双精度,甚至将指向一种类型的指针转换为指向另一种类型的指针。
Haskell是一个有趣的示例,因为它不是显式键入的,而是静态和强类型的。
回答 6
强<=>弱类型不仅是关于一种数据类型的语言将语言自动将多少值强制转换为另一种数据的连续性,还涉及到对实际值的强弱程度。在Python和Java中,大多数情况下在C#中,值的类型设置为固定。在Perl中,不是那么多-实际上只有少数几个不同的值类型可以存储在变量中。
让我们一一打开案例。
Python
在Python示例中1 + "1"
,+
运算符调用__add__
for类型int
,将字符串"1"
作为参数,但是这会导致NotImplemented:
>>> (1).__add__('1')
NotImplemented
接下来,解释器尝试__radd__
str的:
>>> '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示例String result = "1" + 1;
之所以起作用,仅是因为为方便起见,运算符+
被字符串重载。Java +
运算符使用创建一个替换序列StringBuilder
(请参阅参考资料):
String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()
这是一个非常静态的键入的示例,没有实际的强制性- StringBuilder
有一种append(Object)
专门用于此的方法。该文档说:
追加
Object
参数的字符串表示形式。总体效果就好像参数已由方法转换为
String.valueOf(Object)
字符串,然后将该字符串的字符附加到此字符序列。
返回Object参数的字符串表示形式。[返回]如果参数为
null
,则字符串等于"null"
; 否则,obj.toString()
返回的值。
因此,这种情况绝对不会被语言强制-将所有问题都委派给对象本身。
C#
根据此处的Jon Skeet答案,该类+
甚至都不会重载运算符string
-类似于Java,这归功于静态和强类型化,这是编译器生成的便利。
佩尔
正如perldata解释的那样,
Perl具有三种内置数据类型:标量,标量数组和标量的关联数组,称为“哈希”。标量是单个字符串(任何大小,仅受可用内存限制),数字或对某物的引用(将在perlref中进行讨论)。普通数组是按数字索引的标量的有序列表,从0开始。哈希是通过其关联的字符串键索引的无序标量值的集合。
但是,Perl没有用于数字,布尔值,字符串,空值,undefined
s,对其他对象的引用等的单独数据类型-它仅具有一种用于所有这些的类型,即标量类型。0是“ 0”的标量值。设置为字符串的标量变量实际上可以变成数字,并且从此开始,如果在数字上下文中访问,则其行为就不同于“只是字符串”。标量可以在Perl中容纳任何内容,它与系统中存在的对象一样多。而在Python中,名称仅指对象,而在Perl中,名称中的标量值是可变对象。此外,基于对象的类型系统还基于此:perl中只有3种数据类型-标量,列表和哈希。Perl中的用户定义对象是对包的引用(指向之前3个中的任何一个的指针)bless
-您可以获取任何此类值,并在需要的任何时候将其祝福给任何类。
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给人TypeError
的1 + "1"
是一种迹象表明,语言是强类型,即使做一些有用的一个相反,如Java或C#不排除他们是强类型语言。
回答 7
正如许多其他人所表示的那样,“强”键入与“弱”键入的整个概念都是有问题的。
作为一个原型,Smalltalk是非常强类型的- 如果两个对象之间的操作不兼容,它将始终引发异常。但是,我怀疑此列表中很少有人将Smalltalk称为强类型语言,因为它是动态类型。
我发现“静态”与“动态”打字的概念比“强”与“弱”的打字更有用。静态类型的语言具有在编译时确定的所有类型,否则程序员必须明确声明。
与动态类型语言相反,后者是在运行时执行键入的。这通常是多态语言的要求,因此程序员不必事先确定关于两个对象之间的操作是否合法的决定。
在多态,动态类型的语言(如Smalltalk和Ruby)中,将“类型”视为“符合协议”更为有用。如果一个对象遵循与另一个对象相同的协议(即使两个对象不共享任何继承,混合或其他伏都教),则在运行系统中它们被视为相同的“类型”。更正确地说,此类系统中的对象是自治的,并且可以决定响应任何引用特定参数的特定消息是否有意义。
是否需要一个对象,该对象可以使用描述蓝色的对象参数对消息“ +”做出有意义的响应?您可以在动态类型的语言中执行此操作,但是在静态类型的语言中则很麻烦。
回答 8
我喜欢@Eric Lippert的答案,但要解决这个问题-强类型语言通常在程序的每个点都具有变量类型的显式知识。弱类型语言不会,因此它们可以尝试执行某种特定类型可能无法执行的操作。它认为最简单的方法是在函数中。C ++:
void func(string a) {...}
a
已知该变量的类型为字符串,任何不兼容的操作都将在编译时捕获。
Python:
def func(a)
...
该变量a
可以是任何东西,我们可以拥有调用无效方法的代码,该方法只会在运行时被捕获。