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

178

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

Например, в этой статье под названием « Ввод: сильный и слабый», «Статический и динамический» говорится, что 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 слабо типизирован просто потому, что я могу объединить строку в число и наоборот без какого-либо явного преобразования.

Perl

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

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

Ну и дела, это сбивает с толку введите описание изображения здесь

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

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

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

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

  • Что на самом деле означает, что язык действительно слабо типизирован?
  • Не могли бы вы привести какие-нибудь хорошие примеры слабой типизации, которые не связаны с автоматическим преобразованием / автоматическим принуждением, выполняемым языком?
  • Может ли язык быть типизирован слабо и одновременно строго типизирован?
Эдвин Далорсо
источник
8
Сильная или слабая типизация - все о преобразовании типов (о чем еще это может быть?). Если вам нужен пример «очень» слабого языка, посмотрите это: destroyallsoftware.com/talks/wat .
Wilduck
2
@Wildduck Все языки обеспечивают преобразования типов, но не все они считаются слабо типизированными. Мои примеры, показанные ниже, демонстрируют, как программисты считают язык слабо типизированным на основе тех же примеров, которые возможны на других языках, считающихся строго типизированными. Таким образом, мой вопрос все еще преобладает. В чем разница?
Эдвин Далорсо
1
Короткий ответ, я думаю, заключается в том, что «типизация» не является двоичным состоянием. Java и C # более строго типизированы, но не совсем.
Джодрелл
3
Я считаю, что это лучше подходит для разработки программного обеспечения .
zzzzBov
4
@Brendan Как насчет суммирования с плавающей точкой и целым числом? Разве целое число не приводится в число с плавающей точкой в ​​Python? Вы сказали бы сейчас, что Python не совсем строго типизирован?
Эдвин Далорсо

Ответы:

210

ОБНОВЛЕНИЕ: Этот вопрос был предметом моего блога 15 октября 2012 года. Спасибо за отличный вопрос!


Что на самом деле означает для языка быть «слабо типизированным»?

Это означает, что «этот язык использует систему типов, которая мне неприятна». В отличие от этого, «строго типизированный» язык - это язык с системой типов, которая мне нравится.

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

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

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

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

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

Эрик Липперт
источник
14
@edalorzo: Это основано на вкусе и личных мнениях относительно (1) того, какие аспекты теории типов актуальны, а какие нет, и (2) требуется ли язык для обеспечения или просто поощрения ограничений типа. Как я указывал, можно разумно сказать, что C # строго типизирован, потому что он допускает и поощряет статическую типизацию, и можно так же разумно сказать, что он слабо типизирован, потому что он позволяет нарушать безопасность типов.
Эрик Липперт
4
@edalorzo: Что касается сборки, опять же, это вопрос мнения. Компилятор на ассемблере не позволит вам переместить 64-битный дубль из стека в 32-битный регистр; это позволит вам переместить 32-битный указатель на 64-битный дубль из стека в 32-битный регистр. В этом смысле язык «типобезопасен» - он накладывает ограничение на легальность программы на основе классификации типов данных. Является ли это ограничение "сильным" или "слабым" - это вопрос мнения, но это, безусловно, ограничение.
Эрик Липперт
2
Мне кажется, я понимаю вашу точку зрения, по-настоящему слабо типизированный язык должен быть полностью нетипизирован или монотипирован, что в реальной жизни практически невозможно. Таким образом, любой язык имеет определенное определение типов, которые безопасны, и в зависимости от количества дыр, которые язык предоставляет для нарушения или манипулирования своими данными или типами данных, вы можете в конечном итоге считать его более или менее слабо типизированным, возможно, даже в только определенные контексты.
Эдвин Далорсо
7
@edalorzo: Верно. Например, нетипизированное лямбда-исчисление примерно настолько слабо типизировано, насколько это возможно. Каждая функция - это функция от функции к функции; любые данные могут быть переданы в любую функцию без ограничений, потому что все «одного типа». Достоверность выражения в нетипизированном лямбда-исчислении зависит только от его синтаксической формы, а не от семантического анализа, который классифицирует определенные выражения как имеющие определенные типы.
Эрик Липперт
3
@ Марк, я бы дал ему еще +1 за предсказание того, что каждый будет давать разные толкования по этому вопросу. Эта «слабая типизация» кажется «мифической концепцией» или «городской легендой», все видели это, но никто не может доказать, что она существует :-)
Эдвин Далорсо
64

Как уже отмечали другие, термины «строго типизированный» и «слабо типизированный» имеют так много разных значений, что нет единого ответа на ваш вопрос. Однако, поскольку вы специально упомянули 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, а не его содержание. (Также есть несколько других странных типов, таких как typeglobs и дескрипторы ввода / вывода, которые вы не часто видите открытыми.)

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


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

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

Илмари Каронен
источник
19

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

void f(void* x);

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

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

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

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

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

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

Конрад Рудольф
источник
1
+1 Хороший вопрос, вы теперь заставили меня думать о стирании типов как о функции, которая также может подразумевать «слабую типизацию». В Java также есть стирание типов, и во время выполнения система типов позволит вам нарушать ограничения, которые компилятор никогда не утвердит. Пример C отлично иллюстрирует это.
Эдвин Далорсо
1
Согласитесь, есть слои с луком или адом. Это кажется более значимым определением слабости типа.
Джодрелл
1
@edalorzo Я не думаю , что это совершенно то же самое , так как даже если Java позволяет обойти компилятор, система типа выполнения будет по- прежнему поймать нарушение. Таким образом, система типов времени исполнения Java строго типизирована в этом отношении (есть исключения, например, где отражение может использоваться для обхода контроля доступа).
Конрад Рудольф
1
@edalorzo Таким способом можно обойти только компилятор, а не систему времени исполнения. Важно понимать, что языки, такие как Java и C # (и в определенной степени также C ++), имеют систему типов, которая обеспечивается дважды: один раз во время компиляции и один раз во время выполнения. void*пробивает обе проверки типов. Стирание универсального типа - нет, оно только обходит проверки во время компиляции. Это похоже на явное приведение (упомянутое Эриком) в этом отношении.
Конрад Рудольф
1
@edalorzo Re ваше замешательство: мы не должны. Различие свободно. И да, стирание типов делает Java слабо типизированным в этом отношении. Моя точка зрения заключалась в том, что даже при стирании универсального типа вы все равно не сможете обойти проверки типов во время выполнения, если также не используете отражение .
Конрад Рудольф
14

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

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

Слабый набор текста

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

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

SaulBack
источник
3
Но это приводит к противоречивым примерам, приведенным в вопросе. Язык со строгой типизацией может включать в себя неявное принуждение, которое означает, что любой (или оба) из двух ваших примеров «Ошибка типа» автоматически преобразуются в релевантные из вторых двух примеров, но обычно этот язык по-прежнему строго типизирован.
Марк Херд
3
Правда. Я думаю, вы могли бы сказать, что есть разные степени строгой типизации и слабой типизации. Неявное преобразование может означать, что язык менее строго типизирован, чем язык, который не выполняет неявное преобразование.
SaulBack
4

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

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

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

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

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

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

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

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

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

Во второй статье под названием « Типичное программирование», приведенной в одной из ссылок, приведенных в одном из ответов на этот вопрос, Лука Карделли углубляется в концепцию нарушений типов:

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

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

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

Исходя из этого, Python, Perl, Java или C # не являются слабо типизированными.

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

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

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

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

Эдвин Далорсо
источник
4

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

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

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

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

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

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

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

user1277476
источник
+1 Потому что мне нравится придуманный термин «явно типизированный», который классифицирует языки, такие как Java и C #, где вы должны явно объявить типы и отличить их от других языков статического типа, таких как Haskell и Scala, где вывод типов играет важную роль, и это обычно как вы говорите, сбивает с толку людей и заставляет их верить, что эти языки динамически типизированы.
Эдвин Далорсо
3

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

Давайте открывать дела по одному.


питон

В примере 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 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 #

Согласно ответу Джона Скита , оператор +даже не перегружен для stringкласса - сродни Java, это просто удобство, генерируемое компилятором благодаря статической и строгой типизации.


Perl

Как объясняет perldata ,

В Perl есть три встроенных типа данных: скаляры, массивы скаляров и ассоциативные массивы скаляров, известные как «хэши». Скаляр - это отдельная строка (любого размера, ограниченная только доступной памятью), число или ссылка на что-либо (что будет обсуждаться в perlref). Обычные массивы - это упорядоченные списки скаляров, проиндексированные по номеру, начиная с 0. Хэши - это неупорядоченные наборы скалярных значений, проиндексированных по соответствующему строковому ключу.

Perl, однако, не имеет отдельного типа данных для чисел, логических значений, строк, нулей, undefineds, ссылок на другие объекты и т. Д. - у него есть только один тип для всех этих, скалярный тип; 0 - это скалярное значение, равно как и «0». Скалярная переменная, которая была задана как строка, может действительно измениться на число, и с этого момента вести себя иначе, чем «просто строка», если к ней обращаются в числовом контексте, Скаляр может содержать что угодно в Perl, это столько же, сколько объект в системе. тогда как в Python имена просто ссылаются на объекты, в Perl скалярные значения в именах являются изменяемыми объектами. Более того, система объектно-ориентированного типа приклеена поверх этого: в perl - всего 3 типа данных - скаляры, списки и хэши. Пользовательский объект в Perl - это ссылка (то есть указатель на любой из 3 предыдущих),blessВы можете принять любое такое значение и благословить его для любого класса в любой момент, когда захотите.

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

Антти Хаапала
источник
Это полностью запутано. То, что переменные Perl 5 не имеют типов, не имеет никакого отношения к значениям , которые всегда имеют тип.
Джим Балтер
@JimBalter хорошо, да, значение имеет тип в том смысле, что оно является строкой или числом, и оно может вести себя по-разному в некотором контексте в зависимости от того, содержит ли скалярная переменная строку или число; но значение, содержащееся в переменной, может изменить тип, просто получая доступ к переменной, и поскольку само значение находится в переменной, сами значения можно считать изменяемыми между типами.
Антти Хаапала
Значения не меняют типы - это бессвязно; значение всегда имеет тип . Значение, которое содержит переменная, может измениться. Изменение от 1 до «1» - это такое же изменение стоимости, как и от 1 до 2.
Джим Балтер
Язык со слабой типизацией, такой как Perl, допускает неявное изменение значений первого типа в зависимости от контекста. Но даже C ++ допускает такие неявные преобразования через определения операторов. Как отметил Эрик Липперт, слабая типизация является очень неформальным свойством и на самом деле не является полезным способом описания языков.
Джим Балтер
PS Можно показать, что даже в Perl <digits> и "<digits>" имеют разные значения, а не просто разные типы. Perl делает <цифры> и «<цифры>» появляются , чтобы иметь одинаковое значение в большинстве случаев с помощью неявных преобразований , но иллюзия не является полным; например, "12" | «34» - это 36, тогда как 12 | 34 - 46. Другой пример состоит в том, что «00» численно равен 00 в большинстве контекстов, но не в логическом контексте, где «00» является истинным, а 00 - ложным.
Джим Балтер
1

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

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

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

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

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

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

Ян Стейнман
источник
3
Я думаю, что концепция динамической и статической типизации не обсуждается. Хотя я должен сказать, что я не верю, что полиморфизм в любом случае ограничен в языках статического типа. В конечном итоге система типов проверяет, применима ли данная операция к заданным операндам во время выполнения или во время компиляции. Кроме того, другие формы полиморфизма, такие как параметрические функции и классы, позволяют комбинировать типы в языках статического типа способами, которые вы описали как очень трудные по сравнению с динамически типизированными, еще более приятными, если предоставляется вывод типов.
Эдвин Далорсо
0

Мне нравится ответ @Eric Lippert , но для ответа на этот вопрос - языки со строгой типизацией, как правило, четко знают типы переменных в каждой точке программы. Языки со слабой типизацией этого не делают, поэтому они могут пытаться выполнить операцию, которая может быть невозможна для определенного типа. Думаю, что самый простой способ увидеть это в функции. C ++:

void func(string a) {...}

Известно, что переменная aимеет тип string, и любая несовместимая операция будет обнаружена во время компиляции.

Python:

def func(a)
  ...

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

Любо Антонов
источник
12
Я думаю, вы можете путать динамическую типизацию с статической типизацией с сильной типизацией и слабой типизацией. В обеих версиях вашего кода системы типов времени выполнения прекрасно знают, что a является строкой. Просто в первом случае компилятор может вам сказать, во втором - нет. Но это не делает ни один из этих языков слабо типизированным.
Эдвин Далорсо