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

2020/11/14 17:32 · python ·  · 0评论

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

例如,在这篇名为“打字:强vs.弱”,“静态vs.动态”的文章中,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),最终可能会被认为是弱类型,而其他仅提供少量转换的语言可能最终会被视为弱类型。被认为是强类型的。

但是,我倾向于相信,在这种相互交流中我一定是错的,我只是不知道为什么或如何解释它。

因此,我的问题是:

  • 语言真正弱键入到底意味着什么?
  • 您能否提及与该语言完成的自动转换/自动强制无关的弱类型的任何好例子?
  • 语言可以同时弱输入和强输入吗?

更新:这个问题是我在2012年10月15日发表的博客的主题。感谢您提出的伟大问题!


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

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

这些术语本质上是没有意义的,您应该避免使用它们。维基百科列出了“强类型”的十一种不同含义,其中几种是矛盾的。这表明在涉及术语“强类型”或“弱类型”的任何对话中,造成混乱的可能性很高。

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

不应使用“强类型”和“弱类型”,而应详细描述您所指的类型安全。例如,C#在大多数情况下静态类型的语言,类型安全的语言和内存安全的语言C#允许违反所有三种“强”类型的输入形式。强制转换运算符违反了静态类型;它对编译器说:“我比您更了解此表达式的运行时类型”。如果开发人员错误,则运行时将抛出异常以保护类型安全。如果开发人员希望破坏类型安全性或内存安全性,则可以通过制作“不安全”块来关闭类型安全性系统来做到这一点。在不安全的块中,您可以使用指针魔术将int视为浮点型(违反类型安全性)或写入您不拥有的内存。(破坏内存安全。)

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

到底是哪一个?很难说。这取决于说话者的观点及其对各种语言功能的态度。

正如其他人指出的那样,术语“强类型”和“弱类型”具有许多不同的含义,因此您的问题没有一个答案。但是,由于您在问题中特别提到了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很好同样,数字比较操作符==!=<><=>=<=>比较它们的参数的数值,而字符串比较操作符eqneltgtlegecmp字典顺序比较它们为字符串。所以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都可以被bless编入一个类,将其变成该类的对象。Perl中的OO类系统在某种程度上与上述原始类型(或无类型性)系统正交,尽管它在遵循鸭子类型的意义上也是“弱”的范例。通常的意见是,如果您发现自己在Perl中检查了对象的类,则说明您做错了什么。


1实际上,印记表示被访问的值的类型,以使得例如在阵列中的第一标@foo$foo[0]有关更多详细信息,请参见perlfaq4

2(通常)通过引用访问Perl中的对象,但是实际上得到的bless是引用所指向的(可能是匿名的)变量。但是,祝福实际上是变量的属性,而不是变量的值,因此,例如,将实际的祝福变量分配给另一个变量,只会给您一个浅浅的,没有祝福的副本。有关更多详细信息,请参见perlobj

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

void f(void* x);

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

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

但是即使那样,运行时仍会检查类型!如果强制转换无效,则运行时系统将对其进行捕获并引发异常。

使用类型擦除不会发生这种情况–类型信息将被丢弃。强制转换void*为C可以做到这一点。在这方面,以上内容与C#方法声明(例如)从根本上有所不同void f(Object x)

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

是尽可能弱的类型。其他的一切只是一个静态还是动态类型检查,即时间的事一个类型被选中。

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))。

这一切都取决于键入的解释。

我想通过自己对这个问题的研究来为讨论做出贡献,正如其他人发表评论并做出贡献一样,我一直在阅读他们的答案并关注他们的参考文献,并且发现了有趣的信息。如建议的那样,在程序员论坛中可能会更好地讨论其中的大部分内容,因为它似乎是理论上的而非实际的。

从理论的角度来看,我认为Luca Cardelli和Peter Wegner撰写的关于理解类型,数据抽象和多态性的文章是我所读过的最好的论据之一。

类型可以看作是保护底层的无类型表示形式免受任意使用或非预期使用的一组衣服(或盔甲套装)它提供了一个保护性遮盖物,该遮盖物隐藏了底层表示并限制了对象与其他对象交互的方式。在无类型的系统中,无类型的对象是裸露
的,其基础表示形式公开给所有人看。
违反字体系统需要脱下防护服并直接在裸露的衣服上操作。

这句话似乎表明,弱类型输入将使我们能够访问类型的内部结构并像对待其他类型(另一种类型)一样对其进行操作。也许我们可以用不安全的代码(由Eric提及)或由Konrad提及的用c类型擦除的指针来做。

文章继续...

所有表达式都是类型一致的语言称为强类型语言。如果一种语言是强类型的,则其编译器可以保证所接受的程序将在没有类型错误的情况下执行。通常,我们应该努力进行强类型化,并尽可能采用静态类型。请注意,每种静态类型的语言都是强类型的,但相反不一定是正确的。

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

有趣的是,按照该定义,具有强大类型强制性的语言(如Perl)将被视为强类型化的,因​​为系统不会失败,但是它通过将类型强制为适当的和定义良好的对等来处理类型。

另一方面,我是否可以说ClassCastExceptionArrayStoreException(在Java中)和InvalidCastExceptionArrayTypeMismatchException(在C#中)的允许表示至少在编译时处于弱类型的水平?埃里克的答案似乎同意这一点。

此问题的答案之一提供的参考文献之一中提供的第二篇名为“类型化编程”的文章中,Luca Cardelli研究了类型冲突的概念:

大多数系统编程语言都允许任意类型的冲突,有些是不加选择的,有些仅在程序的受限部分中。涉及类型冲突的操作称为不健全。类型违规分为几类[我们可以提到]:

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

这样,像操作员提供的那样的类型强制可以被认为是类型冲突,但是除非它们破坏了类型系统的一致性,否则我们可以说它们不会导致弱类型系统。

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

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

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

诸如C(由Konrad提及)或.Net中不安全代码(由Eric提及)之类的语言中可能发生的这类事情实际上暗示着弱键入。

我相信到目前为止,最好的答案是埃里克(Eric),因为这个概念的定义是非常理论性的,并且当涉及到特定语言时,对所有这些概念的解释可能会得出不同的结论。

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

强类型意味着没有强制类型,或者至少没有强制类型。

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

最近,许多人将“明显键入”与“强烈键入”混淆。“清单式输入”是指您明确声明变量的类型。

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

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

Haskell是一个有趣的示例,因为它没有明显类型化,但也具有静态和强类型性。

强<=>弱类型不仅是关于一种数据类型的语言将语言自动将多少值强制转换为另一种数据的连续性,还涉及到对实际的强弱程度。在Python和Java中,大多数情况下在C#中,值的类型设置为固定。在Perl中,并没有那么多-实际上只有少数几种不同的值类型可存储在变量中。

让我们一一打开案例。


蟒蛇

在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),然后将该字符串的字符附加到此字符序列。

String.valueOf

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

因此,这种情况绝对不会被语言强制-将所有问题都委派给对象本身。


C#

根据此处Jon Skeet回答该类+甚至都不会重载运算符string-类似于Java,这归功于静态和强类型,这是编译器生成的便利。


佩尔

正如perldata解释的那样,

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

但是,Perl没有用于数字,布尔值,字符串,空值,undefineds,对其他对象的引用等的单独数据类型-它仅具有一种用于所有这些的类型,即标量类型。0是与“ 0”相同的标量值。设置为字符串的标量变量实际上可以变为数字,并且从此起与“只是字符串”不同如果在数字上下文中访问则其标量可以在Perl中容纳任何内容,它与系统中存在的对象一样多。而在Python中,名称仅指对象,而在Perl中,名称中的标量值是可变对象。此外,基于对象的类型系统还基于此: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不同。这蟒蛇给人TypeError1 + "1"是一种迹象表明,语言是强类型,即使做一些有用的一个相反,如Java或C#不排除他们是强类型语言。

正如许多其他人所表示的那样,“强”键入与“弱”键入的整个概念都是有问题的。

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

我发现“静态”与“动态”打字的概念比“强”与“弱”的打字更有用。静态类型的语言具有在编译时确定的所有类型,否则程序员必须明确声明。

与动态类型的语言相反,后者是在运行时执行键入的。这通常是多态语言的要求,因此程序员不必事先确定关于两个对象之间的操作是否合法的决定。

在多态,动态类型的语言(如Smalltalk和Ruby)中,将“类型”视为“符合协议”更为有用。如果一个对象遵循与另一个对象相同的协议(即使两个对象不共享任何继承或混合或其他伏都教),则运行时系统会将它们视为相同的“类型”。更正确地讲,此类系统中的对象是自治的,并且可以决定响应引用任何特定参数的任何特定消息是否有意义。

是否需要一个对象,该对象可以使用描述蓝色的对象参数对消息“ +”做出有意义的响应?您可以在动态类型的语言中执行此操作,但是在静态类型的语言中则很麻烦。

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

void func(string a) {...}

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

蟒蛇:

def func(a)
  ...

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

本文地址:http://python.askforanswer.com/xunqiuchengqingyouguanruoleixingyuyandemingxianmaodun.html
文章标签: ,   ,   ,   ,  
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!