寻求澄清弱types语言的明显矛盾
我认为我理解强打字 ,但每次我寻找什么是弱打字的例子,我最终find了编程语言的例子,只是强迫/自动转换types。
例如,在这个名为Typing:Strong vs. Weak的文章中,Static和Dynamic表示Python是强types的,因为如果您尝试:
python
1 + "1" Traceback (most recent call last): File "", line 1, in ? TypeError: unsupported operand type(s) for +: 'int' and 'str'
但是,Java和C#中可能会出现这种情况,我们并不认为它们的input很弱。
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是弱types的,因为我可以将一个string连接到一个数字,反之亦然,而不需要任何显式的转换。
Perl的
$a=10; $b="a"; $c=$a.$b; print $c; #10a
因此,同样的例子使Perl弱types,但不是Java和C#?。
哎,这很混乱
作者似乎暗示,一种防止某些操作在不同types的值上应用的语言是强types的,而相反的意思是弱types的。
因此,在某种程度上,我觉得如果一种语言提供了很多自动转换或types之间的强制(如perl),最终可能被认为是弱types的,而其他提供了less量转换的语言最终可能被认为是强types的。
不过,我倾向于相信,在这个意义上,我一定是错的,我只是不知道为什么或者怎么解释。
所以,我的问题是:
- 一个语言的真正弱types意味着什么?
- 你能否提一下与语言自动转换/自动强制无关的弱打字的例子?
- 一个语言能够同时弱types和强types吗?
更新: 这个问题是我的博客在2012年10月15日的主题。谢谢你的伟大的问题!
一个语言是“弱types的”意味着什么?
这意味着“这种语言使用我觉得不愉快的types系统”。 相比之下,“强types”语言是一种我觉得愉快的types系统的语言。
这些条款本质上是毫无意义的,你应该避免它们。 维基百科列出了十一种 “强types”的不同含义 ,其中有些是矛盾的。 这表明在任何涉及“强types”或“弱types”的对话中,造成混淆的可能性都很高。
所有你可以确定地说的是,在讨论中的“强types”语言在types系统中有一些额外的限制,无论是在运行时还是编译时,都缺乏讨论中的“弱types”语言。 如果没有进一步的背景,这种限制可能无法确定。
不要使用“强types”和“弱types”,你应该详细描述你的意思是什么types的安全。 例如,C# 大部分是静态types语言, types安全语言和内存安全语言。 C#允许所有三种“强”types的forms被违反。 演员操作违反静态types; 它对编译器说:“我知道更多关于这个expression式的运行时types,而不是你所做的。 如果开发者错了,那么运行时会抛出一个exception,以保护types安全。 如果开发者希望打破型号安全或记忆安全,他们可以通过制造一个“不安全的”模块来closures型号安全系统。 在一个不安全的块中,你可以使用指针魔术来把int当成float(违反types安全),或者写入你不拥有的内存。 (违反记忆安全。)
C#强加了在编译时和运行时检查的types限制,因此与编译时检查或运行时检查较less的语言相比,它成为“强types”语言。 C#还允许您在特殊情况下执行这些限制,使其成为一种“弱types”语言,而语言则不允许您执行此类最终操作。
这是真的吗? 说不出来, 这取决于说话者的观点和对各种语言特征的态度。
正如其他人所指出的那样,“强types”和“弱types”这两个词有很多不同的含义,所以你的问题没有单一的答案。 但是,既然你在你的问题中特别提到了Perl,让我试着解释一下Perl的弱types。
重点是,在Perl中,不存在“整数variables”,“浮点variables”,“stringvariables”或“布尔variables”。 实际上,就用户可以(通常)来说,甚至没有整数,浮点数,string或布尔值 :你所拥有的只是“标量”,这些都是同时发生的。 所以你可以写,例如:
$foo = "123" + "456"; # $foo = 579 $bar = substr($foo, 2, 1); # $bar = 9 $bar .= " lives"; # $bar = "9 lives" $foo -= $bar; # $foo = 579 - 9 = 570
当然,正如你正确地指出的,所有这些都可以被看作是强制式的。 但重要的是,在Perl中,types总是被强制的。 实际上,用户很难说出variables的内部“types”是什么:在上面的例子的第2行,询问$bar
的值是string"9"
还是数字9
是漂亮的很多没有意义,因为就Perl而言, 这些都是一样的 。 实际上,Perl标量甚至可能同时在内部同时包含一个string和一个数值,例如上面第2行之后的$foo
。
所有这一切的另一面是,由于Perlvariables是无types的(或者,而不是将它们的内部types暴露给用户),运算符不能被重载来为不同types的参数做不同的事情; 你不能只是说“这个操作符将会使用X来表示数字,Y来表示string”,因为操作符不能(不会)告诉它的参数是哪种types的值。
因此,例如,Perl有和需要一个数字加法运算符( +
)和一个string连接运算符( .
):正如你在上面看到的,添加string是完全正确的( "1" + "2" == "3"
)或连接数字( 1 . 2 == 12
)。 类似地,数字比较运算符==
, !=
, <
, >
, <=
, >=
和<=>
比较它们的参数的数值,而string比较运算符eq
, ne
, lt
, gt
, le
, ge
和cmp
将它们按字典顺序作为string进行比较。 所以2 < 10
但是2 gt 10
(但是"02" lt 10
,而"02" == 2
)。 (请注意,某些其他语言(如JavaScript)会尽力适应类似Perl的弱typesinput,同时也会导致运算符重载,这往往会导致丑陋,就像失去+
的关联性一样。
(美中不足的是,由于历史的原因,Perl 5确实有一些特例,比如按位逻辑运算符,它们的行为依赖于它们的参数的内部表示,这些通常被认为是一个烦人的devise缺陷,因为内部表示可能会因为令人惊讶的原因而发生变化,因此预测这些操作员在特定情况下所做的操作可能会非常棘手)。
所有这一切说,人们可以争辩说,Perl 确实有很强的types; 他们只是不是你可能期望的types。 具体来说,除了上面讨论的“标量”types外,Perl也有两种结构types:“数组”和“散列”。 这些与标量非常不同,也就是Perlvariables具有不同的标志,表明它们的types( $
表示标量, @
表示数组, %
表示散列) 1 。 在这些types之间有强制规则,所以你可以编写例如%foo = @bar
,但是其中很多都是有损的:例如, $foo = @bar
将数组@bar
的长度 @bar
给$foo
,而不是其内容。 (另外,还有其他一些奇怪的types,比如typeglobs和I / O句柄,你不经常看到。
另外,这个漂亮的devise中的一个小缺陷就是引用types的存在,它是一种特殊的标量types( 可以使用ref
运算符与正常标量区分开来)。 可以使用引用作为普通标量,但是它们的string/数字值并不是特别有用,如果使用正常的标量操作修改它们,它们往往会失去其特殊的引用。 而且,任何Perlvariables2都可以被bless
给一个类,把它变成这个类的一个对象; Perl中的OO类系统与上述的原始types(或无types)系统有些正交,但是在遵循鸭子打字范式的意义上它也是“弱”的。 一般的观点是,如果你发现自己在Perl中检查一个对象的类,你做错了什么。
1实际上,sigil表示被访问的值的types,例如数组@foo
的第一个标量被表示为$foo[0]
。 有关更多详细信息,请参阅perlfaq4 。
Perl中的对象(通常)是通过对它们的引用来访问的,但实际得到bless
是引用指向的(可能是匿名的)variables。 然而,祝福实际上是variables的一个属性, 而不是它的价值,所以例如把实际有福的variables分配给另一个variables只是给你一个浅的,不成文的副本。 有关更多详细信息,请参阅perlobj 。
除了Eric所说的外,考虑下面的C代码:
void f(void* x); f(42); f("hello");
与诸如Python,C#,Java之类的语言相比,上面的types是弱types的,因为我们丢失了types信息。 Eric正确地指出,在C#中,我们可以通过强制转换来绕过编译器,有效地告诉它“我比你更了解这个variables的types”。
但即使如此,运行时仍然会检查types! 如果强制转换无效,则运行时系统将捕获它并抛出exception。
随着types擦除,这不会发生 – types信息被扔掉。 在C中抛出void*
就是这么做的。 在这方面,上面与C#方法声明(如void f(Object x)
有根本的区别。
(从技术上讲,C#还允许通过不安全的代码或编组进行types擦除。)
这是一样微弱的types。 其他的一切只是静态和dynamictypes检查的问题,也就是types检查的时间。
一个完美的例子来自强大的打字维基百科文章 :
通常,强types意味着编程语言对允许发生的混合施加了严格的限制。
弱打字
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
注意,一个弱types语言可以混合不同的types而没有错误。 强types语言要求inputtypes是预期的types。 在强types语言中,types可以转换( str(a)
将整数转换为string)或cast( int(b)
)。
这一切都取决于打字的解释。
我想通过自己的研究来讨论这个问题,其他人的评论和贡献我一直在阅读他们的答案,并在他们的参考文献中find有趣的信息。 正如所build议的那样,在程序员论坛中可能会更好地讨论其中的大部分内容,因为它似乎比实践更为理论化。
从理论angular度来看,我认为Luca Cardelli和Peter Wegner的文章“ 理解types,数据抽象和多态性 ”是我读过的最好的论点之一。
一种types可以被看作是一套衣服(或一套盔甲),可以保护基本的无types表示,防止任意或意外的使用。 它提供了隐藏底层表示的保护层,并限制了对象与其他对象交互的方式。 在一个无types的系统中,无types的对象是裸体的,底层的表示被公开给大家看。 违反types系统涉及到删除保护套衣物,并直接在裸露的表示上操作。
这个陈述似乎表明弱打字会让我们访问一个types的内部结构,并把它作为别的东西(另一种types)来操作。 也许我们可以用不安全的代码(Eric提到的)或Konrad提到的c型擦除指针来做什么。
文章继续…
所有expression式都是types一致的语言称为强types语言。 如果语言是强types的,那么它的编译器可以保证它接受的程序将会执行而不会出现types错误。 一般来说,我们应该努力打字,尽可能采用静态打字。 请注意,每个静态types的语言是强types的,但反过来不一定是正确的。
因此,强types意味着没有types错误,我只能假设弱types意味着相反:types错误的可能存在。 在运行时还是编译时? 在这里似乎无关紧要。
有趣的是,按照这个定义,像Perl这样具有强大types强制的语言会被认为是强types的,因为系统并没有失败,但是它正在通过将它们强制转换为适当的和明确定义的等价来处理types。
另一方面,我可以说比ClassCastException
和ArrayStoreException
(在Java中)和InvalidCastException
, ArrayTypeMismatchException
(在C#中)的津贴会表明弱键入的级别,至less在编译时? 埃里克的答案似乎同意这一点。
在这个问题的答案之一提供的参考文献之一中提到的第二篇文章“ Typeful Programming”中,Luca Cardelli深入探讨了types违反的概念:
大多数系统编程语言允许任意types的违规,有的只是一些程序的受限制的部分。 涉及types违规的操作称为不健全。 types侵犯分为几类[其中我们可以提到]:
基本值强制 :包括整数,布尔值,字符,集合等之间的转换。这里不需要types违反,因为可以提供内置的接口来以types声音的方式执行强制。
因此,类似操作者提供的强制types可以被认为是types违反,但是除非它们打破了types系统的一致性,否则我们可以说它们不会导致弱types系统。
基于此,Python,Perl,Java或C#都是弱types的。
Cardelli提到两种types的vilations,我很好地考虑了真正弱打字的情况:
地址算术。 如有必要,应该有一个内置的(不健全的)接口,提供适当的地址和types转换操作。 各种各样的情况涉及指向堆的指针(对重定位收集器非常危险),指向堆栈的指针,指向静态区域的指针以及指向其他地址空间的指针。 有时数组索引可以replace地址算术。 内存映射。 这涉及将内存区域视为非结构化数组,尽pipe它包含结构化数据。 这是内存分配器和收集器的典型。
C语言(Konrad提到的)或者.Net中的不安全代码(Eric提到)中的这种可能性确实意味着打字效果不佳。
我相信迄今为止最好的答案是埃里克的,因为这个概念的定义是非常理论的,当谈到一个特定的语言时,所有这些概念的解释可能会导致不同的有争议的结论。
弱types确实意味着高比例的types可以被隐式强制,试图猜测编码器的意图。
强types意味着types不被强制,或者至less被强制更less。
静态types意味着你的variables的types是在编译时确定的。
最近有很多人把“明显的types”和“强烈的types”混为一谈。 “显式input”意味着你明确地声明你的variables的types。
Python大部分是强types的,尽pipe你几乎可以在布尔上下文中使用任何东西,布尔值可以在整数上下文中使用,你可以在浮点上下文中使用整数。 它不是明显的types,因为你不需要声明你的types(Cython除外,尽pipe它不是完全的python,尽pipe很有趣)。 它也不是静态types的。
C和C ++是明显的types,静态types和强types,因为你声明你的types,types是在编译时确定的,你可以混合整数和指针,或者整数和双精度,甚至可以把一个指针types转换为指向另一种types的指针。
Haskell是一个有趣的例子,因为它没有明确的types,但它也是静态和强types的。
强的<=>弱types不仅是一个数据types对另一个数据types自动强制多less值或多less值的连续统一体,而且实际值的强弱有多大。 在Python和Java中,大多数情况下在C#中,这些值的types都是固定的。 在Perl中,不是那么多 – 实际上只有less数不同的值types存储在variables中。
让我们逐个打开这些案例。
python
在Python示例1 + "1"
, +
运算符为typesint
调用__add__
,并将string"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'
。 因此,这个例外并不多说强types,但是运算符+
不会自动将它的参数强制转换为相同的types,这是一个事实,即Python不是连续体中最弱types的语言。
另一方面,在Python中, 'a' * 5
被实现:
>>> 'a' * 5 'aaaaa'
那是,
>>> 'a'.__mul__(5) 'aaaaa'
操作是不同的事实需要一些强大的打字 – 然而,强制打字的价值数字相反,然后乘数仍然不一定使价值微弱的types。
Java的
Java示例, String result = "1" + 1;
仅仅是因为作为一个方便的事实,运算符+
对于string来说是过载的。 Java +
运算符用创build一个StringBuilder
replace序列(参见这个 ):
String result = a + b; // becomes something like String result = new StringBuilder().append(a).append(b).toString()
这是一个非常静态types的例子,没有实际的强制StringBuilder
有一个方法append(Object)
,在这里专门使用。 该文件说明如下:
附加
Object
参数的string表示forms。整体效果就好像参数通过
String.valueOf(Object)
方法转换为string,然后将该string的字符附加到此字符序列。
那么String.valueOf
在哪里
返回Object参数的string表示forms。 [返回]如果参数为
null
,则返回一个等于"null"
的string; 否则,返回obj.toString()
的值。
因此,这是一个绝对不会被语言强制的情况 – 把每一个关注点都放在对象本身上。
C#
根据Jon Skeet在这里的答案 ,operator +
甚至没有被重载的string
类 – 类似于Java,这只是由编译器生成的便利,这要归功于静态和强types。
Perl的
正如perldata所解释的那样,
Perl有三种内置的数据types:标量,标量数组和标量关联数组,称为“哈希”。 标量是一个单一的string(任何大小,只受限于可用内存),数字或对某事的引用(将在perlref中讨论)。 正常数组是按数字索引的标量的有序列表,从0开始。哈希是标量值的无序集合,由其相关的string键索引。
然而,Perl没有数字,布尔值,string,空值, undefined
s,对其他对象的引用等单独的数据types – 它只是一种types的标量types。 0是一个和“0”一样多的标量值。 被设置为string的标量variables实际上可以变成一个数字,从那里起,与“只是一个string”的行为不同, 如果在数字上下文中访问它 。 标量可以容纳Perl中的任何东西,它和系统中存在的一样多。 而在Python中,名称只是指对象,在Perl中,名称中的标量值是可更改的对象。 而且,面向对象的types系统是最重要的:perl – 标量,列表和哈希中只有3个数据types。 Perl中的一个用户定义的对象是一个引用(这是一个指向前面任何一个的指针)的bless
– 你可以获取任何这样的值,并在任何需要的时刻为任何类祝福。
Perl甚至允许你在奇思妙想中改变值的类别 – 这在Python中不可能创build一个类的值,你需要用object.__new__
或类似的方法显式地构造属于那个类的值。 在Python中,创build后不能真正改变对象的本质,在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";
因此,types标识与variables弱相关,并且可以通过任何参考而被改变。 事实上,如果你这样做
my $another = $val;
\$another
没有类标识,即使\$val
仍然会给予祝福的引用。
TL; DR
关于Perl的弱types,不仅仅是自动强制,还有更多关于这些值的types本身并没有被设置成石头,与dynamic而非强types语言的Python不同。 该python给1 + "1"
是一种说明,语言是强types的,即使相反做一些有用的东西,如在Java或C#并不排除他们是强types的语言。
我喜欢@Eric Lippert的回答 ,但是为了解决这个问题 – 强types语言通常对程序中每个点的variablestypes有明确的了解。 弱types的语言不会,所以他们可以尝试执行某种特定types的操作。 它认为最简单的方法来看这是一个函数。 C ++:
void func(string a) {...}
variablesa
已知为stringtypes,任何不兼容的操作将在编译时被捕获。
python:
def func(a) ...
The variable a
could be anything and we can have code that calls an invalid method, which will only get caught at runtime.
As many others have expressed, the entire notion of "strong" vs "weak" typing is problematic.
As a archetype, Smalltalk is very strongly typed — it will always raise an exception if an operation between two objects is incompatible. However, I suspect few on this list would call Smalltalk a strongly-typed language, because it is dynamically typed.
I find the notion of "static" versus "dynamic" typing more useful than "strong" versus "weak." A statically-typed language has all the types figured out at compile-time, and the programmer has to explicitly declare if otherwise.
Contrast with a dynamically-typed language, where typing is performed at run-time. This is typically a requirement for polymorphic languages, so that decisions about whether an operation between two objects is legal does not have to be decided by the programmer in advance.
In polymorphic, dynamically-typed languages (like Smalltalk and Ruby), it's more useful to think of a "type" as a "conformance to protocol." If an object obeys a protocol the same way another object does — even if the two objects do not share any inheritance or mixins or other voodoo — they are considered the same "type" by the run-time system. More correctly, an object in such systems is autonomous, and can decide if it makes sense to respond to any particular message referring to any particular argument.
Want an object that can make some meaningful response to the message "+" with an object argument that describes the colour blue? You can do that in dynamically-typed languages, but it is a pain in statically-typed languages.