Поиск разъяснений по явным противоречиям в отношении слабо типизированных языков

Я понимаю строгой типизации, но каждый раз, когда я ищу примеры того, что слабо набрав, я в конечном итоге нахожу примеры языков программирования, которые просто принудительно/автоматически преобразуют типы.

например, в этой статье под названием Typing: сильный против слабого, статический против динамического говорит, что 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);

в другой статье под названием Слабый Тип Языков автор говорит, что Perl слабо набирается просто потому, что я могу объединить строку с числом и viceversa без какого-либо явного преобразования.

Perl

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

так же пример делает Perl слабо типизированным, но не Java и C#?.

Боже, это сбивает с толку enter image description here

авторы, похоже, подразумевают, что язык, который предотвращает применение определенных операций над значениями разных типов, сильно типизирован, а наоборот-слабо типизирован.

поэтому в какой-то момент мне показалось, что если язык предоставляет много автоматических преобразований или принуждения между типами (как perl), это может быть рассмотрено слабо типизированные, в то время как другие языки, которые предоставляют только несколько преобразований, могут считаться строго типизированными.

Итак, мои вопросы:

  • что на самом деле означает, что язык действительно слабо типизирован?
  • не могли бы вы упомянуть какие-либо хорошие примеры слабого ввода, которые не связаны с автоматическое преобразование / автоматическое принуждение, выполняемое языком?
  • может ли язык быть слабо типизирован и сильно типизирован одновременно?

9 ответов


обновление: этот вопрос был предметом моего блога 15 октября 2012 года. Спасибо за отличный вопрос!


что на самом деле означает, что язык "слабо типизирован"?

Это означает ,что "этот язык использует систему типов, которую я нахожу неприятной". "Строго типизированный" язык, напротив, - это язык с системой типов, которая мне нравится.

термины по существу бессмысленны, и вы должны избегать их. Википедия списки одиннадцать различных значений для "строго типизированных", некоторые из которых противоречивы. Это указывает на то, что вероятность возникновения путаницы высока в любом разговоре, включающем термин "строго типизированный" или "слабо типизированный".

все, что вы можете с уверенностью сказать, это то, что обсуждаемый" строго типизированный " язык имеет некоторые дополнительные ограничения в системе типов во время выполнения или во время компиляции, что "слабо набранный" язык при обсуждении отсутствует. Каким может быть это ограничение, невозможно определить без дальнейшего контекста.

вместо того, чтобы использовать "сильно типизированные" и "слабо типизированные", вы должны подробно описать, какую безопасность типа Вы имеете в виду. Например, C# - это статически типизированный язык и введите safe язык и безопасная памяти язык по большей части. C# позволяет все три из этих форм " сильного" набрав нарушения. Оператор cast нарушает статическую типизацию; он говорит компилятору:"я знаю больше о типе выполнения этого выражения, чем вы". Если разработчик ошибается, среда выполнения выдаст исключение для защиты безопасности типов. Если разработчик хочет нарушить безопасность типа или безопасность памяти, они могут сделать это, отключив систему безопасности типа, сделав "небезопасный" блок. В небезопасном блоке вы можете использовать магию указателя для обработки int как float (тип нарушения безопасность) или писать на память вам не принадлежит. (Нарушение безопасности памяти.)

C# накладывает ограничения типа, которые проверяются как во время компиляции, так и во время выполнения, тем самым делая его "строго типизированным" языком по сравнению с языками, которые выполняют меньшую проверку во время компиляции или меньше проверки во время выполнения. C# также позволяет в особых случаях выполнять конечный прогон вокруг этих ограничений, что делает его "слабо типизированным" языком по сравнению с языками, которые не позволяют выполнять такой конечный прогон.

Что это на самом деле? Это невозможно сказать; это зависит от точки зрения говорящего и его отношения к различным языковым особенностям.


дело в том, что в Perl нет такой вещи, как "целочисленная переменная", "плавающая переменная", "строковая переменная" или "булева переменная". На самом деле, насколько пользователь может (обычно) сказать, там даже не целочисленные, float, string или boolean значения: все, что у вас есть, это "скаляры", которые являются всеми этими вещами одновременно. Так можно, например, написать:

$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" или в значительной степени бессмысленно, так как, Что касается 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). (Заметьте, конечно!--43-->другое языки, такие как JavaScript, попробуйте разместить Perl-подобный слабый ввод текста, пока и делаем перегрузку операторов. Это часто приводит к уродству, как потеря ассоциативности для +.)

все, что сказано, можно утверждать, что Perl тут есть сильные типы, они просто не такие типы вы могли бы ожидать. В частности, в дополнение к "скалярному" типу, рассмотренному выше, Perl также имеет два структурированных типа: "массив" и "хэш". Это очень отличается от скаляров, до точки, где переменные Perl имеют разные символы С указанием их типа ($ для скаляров, @ для массивов, % для хэшей)1. Там are правила принуждения между этими типами, так что вы can, например,%foo = @bar, но многие из них довольно потеряны: например,$foo = @bar назначение длина массива @bar to $foo, а не его содержание. (Кроме того, есть несколько других странных типов, таких как typeglobs и ручки ввода-вывода, которые вы не часто видеть открытыми.)

кроме того, небольшая щель в этом приятном дизайне-это существование ссылочных типов ,которые являются особым видом скаляров (и которые can отличить от обычных скаляров, используя ref оператор). Можно использовать ссылки как обычные скаляры, но их строковые/числовые значения не особенно полезны, и они, как правило, теряют свою особую ссылочность, если вы изменяете их с помощью обычных скалярных операций. Кроме того, любой Perl переменная2 может быть blessed к классу, превращая его в объект этого класса; система классов OO в Perl несколько ортогональна описанной выше системе примитивного типа (или безтипности), хотя она также "слаба" в смысле следования утиной типизацией парадигмы. Общее мнение таково: если вы обнаружите, что проверяете класс объекта в Perl, вы делаете что-то неправильно.


1 на самом деле, sigil обозначает тип доступного значения, так что, например, первый скаляр в массиве @foo обозначается $foo[0]. См.perlfaq4 для получения более подробной информации.

2 объекты в Perl (обычно) доступны через ссылки на них, но что на самом деле получает blessed-это (возможно, анонимная) переменная, на которую указывают ссылки. Однако благословение действительно является свойством переменной,не его значения, так, например, что назначение фактической благословенной переменной другому просто дает вам неглубокую, не благословленную копию. См.perlobj для получения более подробной информации.


В дополнение к тому, что сказал Эрик, рассмотрим следующий код C:

void f(void* x);

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

В отличие от таких языков, как Python, C#, Java или еще что-то, выше слабо типизировано, потому что мы сбросить информация тип. Эрик правильно указал, что в C# мы можем обойти компилятор, бросив, эффективно сказав ему: "я знаю больше о типе этой переменной, чем вы".

но даже тогда среда выполнения все равно будет проверять тип! Если приведение недопустимо, система выполнения поймает его и выдаст исключение.

при стирании типа этого не происходит-информация типа выбрасывается. Бросок в void* в C делает именно это. В этом отношении вышеизложенное принципиально отличается от объявления метода C#, такого как void f(Object x).

(технически, C# также позволяет стирать тип через небезопасный код или маршаллинг.)

этой настолько слабо набран, насколько это возможно. Все остальное-лишь вопрос статики. против динамической проверки типа, т. е. времени , когда тип.


идеальный пример из статья Википедии сильного набора текста:

обычно сильная типизация подразумевает, что язык программирования накладывает серьезные ограничения на смешивание, которое разрешено.

Слабая Типизация

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) преобразует целое число в строку) или Cast (int(b)).

все это зависит от интерпретации набора текста.


Я хотел бы внести свой вклад в обсуждение с моим собственным исследованием по этому вопросу, поскольку другие комментируют и вносят свой вклад, я читал их ответы и следил за их ссылками, и я нашел интересную информацию. Как и предполагалось, вероятно, что большая часть этого вопроса будет лучше обсуждаться на форуме программистов, поскольку она представляется скорее теоретической, чем практической.

С теоретической точки зрения, я думаю, что статья Луки Карделли и Питера Вегнера названа о понимании типов, абстракции данных и полиморфизма один из лучших аргументов я читал.

тип можно рассматривать как набор одежды( или костюм брони), который защищает базовый нетипизированный представление от произвольного или непреднамеренное использование. Она обеспечивает защитное покрытие, которое скрывает базовое представление и ограничения способа взаимодействия объектов с другими объектами. В нетипизированной системе нетипизированные объекты голая в том, что лежащее в основе представление выставляется на всеобщее обозрение. Нарушение системы типа предполагает удаление защитного набора одежда и оперирование непосредственно на голом изображении.

статья продолжает...

языки, в которых все выражения тип-последовательный называются строго типизированные языки. Если язык строго типизирован, его компилятор может гарантировать, что программы, которые он принимает, будут выполняться без типа ошибки. В общем, мы должны стремиться к сильному набору текста и принять статическая типизация, когда это возможно. Обратите внимание, что каждый статически типизированный язык строго типизирован, но обратное не обязательно верно.

таким образом, сильная типизация означает отсутствие ошибок типа, я могу только предположить, что слабая типизация означает обратное: вероятное наличие ошибок типа. Во время выполнения или компиляции? Здесь это не имеет значения.

забавная вещь, согласно этому определению, язык с мощными принуждениями типа, такими как Perl, будет считаться строго типизированным, потому что система не терпит неудачу, но она имеет дело с типами, принуждая их к соответствующие и четко определенные эквивалентности.

С другой стороны, могу ли я сказать, чем пособие ClassCastException и ArrayStoreException (в Java) и InvalidCastException, ArrayTypeMismatchException (В C#) будет указывать на уровень слабого ввода, по крайней мере, во время компиляции? Ответ Эрика, кажется, согласен с этим.

во второй статье под названием Программирование Typeful предоставленный в одной из ссылок, предоставленных в одном из ответов на этот вопрос, лука Карделли углубляется в концепцию типа нарушения:

большинство языков системного программирования допускают нарушения произвольного типа, некоторые без разбора, некоторые только в ограниченных частях программы. Операции, которые связаны с нарушениями типа, называются необоснованными. Тип нарушения делятся на несколько классов [среди которых можно отметить]:

базовое значение принуждения: они включают преобразования между целыми числами, булевыми, символами, наборами и т. д. Нет необходимости в нарушениях типа здесь, потому что встроенные интерфейсы можно обеспечить для того чтобы унести принуждение в ТиПО-звуковом смысле.

таким образом, принуждения типа, подобные тем, которые предоставляются операторами, могут рассматриваться как нарушения типа, но если они не нарушают согласованность системы типов, мы можем сказать, что они не приводят к слабо типизированной системе.

на основе этого ни Python, Perl, Java или C# слабо типизированы.

Cardelli упоминает два типа vilations, которые я очень хорошо считаю случаи действительно слабого набора текста:

адресная арифметика. при необходимости должен быть встроенный (нездоровый) интерфейс, обеспечивающий адекватные операции по адресам и преобразования типов. Различные ситуации включают указатели на куча (очень опасная при перемещении коллекторов), указатели на стек, указатели на статические области и указатели на другой адрес пространства. Иногда индексация массива может заменить арифметику адресов. память отображение. это включает в себя рассмотрение области памяти как неструктурированного массива, хотя он содержит структурированные данные. Это типично для распределителей памяти и коллекторов.

такого рода вещи, возможные в таких языках, как C (упомянутый Конрадом) или через небезопасный код в .Net (упомянутый Эриком), действительно подразумевают слабую типизацию.

Я считаю, что лучший ответ до сих пор Эрика, потому что определение этого понятия является очень теоретическим, и когда это приходит к определенному языку, интерпретации всех этих понятий могут привести к различным спорным выводам.


слабая типизация действительно означает, что высокий процент типов может быть неявно принужден, пытаясь угадать, что кодер намеревался.

сильная типизация означает, что типы не принуждаются или, по крайней мере, принуждаются меньше.

статическая типизация означает, что типы переменных определены во время компиляции.

многие люди в последнее время путают "явно типизированные" с "сильно типизированными". "Явно типизированный" означает, что вы объявляете типы своих переменных явно.

Python в основном строго типизирован, хотя вы можете использовать почти все в булевом контексте, а булевы могут использоваться в целочисленном контексте, и вы можете использовать целое число в контексте float. Он не явно типизирован, потому что вам не нужно объявлять свои типы (за исключением Cython, который не совсем python, хотя и интересен). Он также не является статически типизированным.

C и c++ явно типизированы, статически типизированы и несколько сильно типизированы, потому что вы объявляете свои типы, типы определяются во время компиляции, и вы можете смешивать целые числа и указатели, или целые числа и удвоения, или даже привести указатель на один тип в указатель на другой тип.

Haskell-интересный пример, потому что он не явно типизирован, но он также статически и сильно типизирован.


сильная слабая типизация не только о континууме о том, сколько или как мало значений принудительно автоматически языком для одного типа данных к другому, но как сильно или слабо фактическое значения набираются. В Python и Java, и в основном в C#, значения имеют свои типы, установленные в stone. В Perl не так много - на самом деле существует только несколько разных типов значений для хранения в переменной.

давайте откроем дела по одному один.


Python

в примере Python 1 + "1", + оператор называет __add__ тип 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

пример 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) это специально используется здесь. В документации говорится следующее:

добавляет строковое представление переменная это было установлено поскольку строка действительно может измениться в число, и оттуда вести себя иначе, чем "просто строка",если он доступен в числовом контексте. Скаляр может содержать что угодно в Perl, это такой же объект, как он существует в системе. в то время как в Python имена просто относятся к объектам, в Perl скалярные значения в именах являются изменяемыми объектами. Кроме того, объектно-ориентированная система типов склеена поверх этого: в perl-скалярах, списках есть только 3 типа данных и хэши. Определяемый пользователем объект в Perl является ссылкой (то есть указателем на любой из 3 предыдущих) blessed в пакет-вы можете взять любое такое значение и благословить его в любой класс в любой момент, когда захотите.

Perl даже позволяет изменять классы значений по прихоти - это невозможно в 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, который является динамически, но очень сильно типизированным языком. Этот питон дает TypeError on 1 + "1" является признаком того, что язык сильно типизирован, хотя, наоборот, что-то полезное, как в Java или C#, не исключает, что они являются сильно типизированными языками.


мне нравится @Эрик Липперт это, но для решения вопроса-строго типизированные языки обычно имеют явное знание типов переменных в каждой точке программы. Слабо типизированные языки этого не делают, поэтому они могут попытаться выполнить операцию, которая может быть невозможна для определенного типа. Он думает, что самый простой способ увидеть это-в функции. C++:

void func(string a) {...}

переменная имеет тип string и любую несовместимую операцию будет пойман во время компиляции.

Python:

def func(a)
  ...

переменная a может быть что угодно, и у нас может быть код, который вызывает недопустимый метод, который будет пойман только во время выполнения.


как выражались многие другие, все понятие "сильного" и "слабого" ввода проблематично.

как архетип, Smalltalk очень сильно типизирован - он будет всегда поднять исключение, если операция между двумя объектами несовместимо. Однако я подозреваю, что немногие в этом списке назовут Smalltalk строго типизированным языком, потому что это динамической типизацией.

Я считаю, что понятие " статический "по сравнению с" динамическим " типом более полезно, чем "сильный" против "слабого"."Статически типизированный язык имеет все типы, вычисленные во время компиляции, и программист должен явно объявить, если иначе.

контраст с динамически типизированным языком, где ввод выполняется во время выполнения. Это, как правило, требование для полиморфных языков, так что решения о том, является ли операция между двумя объектами законной, не должны решаться программистом заранее.

в полиморфных, динамически типизированные языки (например, Smalltalk и Ruby), более полезно думать о "типе" как о "соответствии протоколу."Если объект подчиняется протоколу так же, как и другой объект-даже если эти два объекта не разделяют никакого наследования или миксины или другие вуду-они считаются одним и тем же "типом" системой времени выполнения. Более правильно, объект в таких системах является автономным и может решить, имеет ли смысл отвечать на любое конкретное сообщение, относящееся к любому конкретному аргумент.

хотите объект, который может сделать какой-то значимый ответ на сообщение "+" с аргументом объекта, который описывает синий цвет? Вы можете сделать это на динамически типизированных языках, но это боль в статически типизированных языках.