Почему динамически типизированные языки не позволяют разработчику указать тип?

14

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

Например, JavaScript не предоставляет никакого механизма для принудительного применения типов переменных, когда это удобно делать. PHP позволяют определить некоторые типы аргументов методы, но нет никакого способа , чтобы использовать собственные типы ( int, stringи т.д.) для аргументов, и нет никакого способа , чтобы обеспечить соблюдение типов для ничего, кроме аргументов.

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

Почему существует такое ограничение? Это связано с техническими / производительными причинами (я полагаю, что это в случае JavaScript), или только по политическим причинам (что, я полагаю, в случае с PHP)? Это относится к другим динамически типизированным языкам, с которыми я не знаком?


Отредактируйте: следуя ответам и комментариям, вот пример для пояснения: допустим, у нас есть следующий метод в простом PHP:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

С некоторыми усилиями это можно переписать так (см. Также Программирование с помощью контрактов в PHP ):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Но тот же метод был бы написан следующим образом, если бы PHP мог принимать нативные типы для аргументов:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Какой из них короче написать? Какой из них легче читать?

Арсений Мурзенко
источник
1
При желании вы можете указать типы в некоторых динамически типизированных языках - например, в Common Lisp.
SK-logic
Довольно много динамически типизированных языков используют приведение для принудительного ввода типа ...
Trezoid
Некоторые делают. Например, Objective-C имеет динамическую типизацию, но вы можете объявить тип для переменных, и компилятор выдаст предупреждения, если вы не получите ожидаемый тип.
Мипади
1
Clojure - это пример языка, который обычно динамически типизирован, но вы можете при желании указывать типы переменных через «подсказки типов» (обычно это делается только там, где это необходимо для получения преимуществ в производительности информации о типах во время компиляции)
mikera
1
Groovy - еще один пример динамически типизированного языка, который позволяет указывать тип.
Эрик Уилсон

Ответы:

17

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

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

Чтобы выразить информацию о типе, вам нужна часть языка, которая не может быть чрезмерно простой. Вскоре вы обнаружите, что такой информации intнедостаточно; вам понадобится что-то вроде List<Pair<Int, String>>, затем параметрические типы и т. д. Это может сбить с толку даже в довольно простом случае с Java.

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

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

Суть динамических языков в том, чтобы иметь очень маленькую и очень податливую среду, в которой вы можете легко делать вещи, которые гораздо более сложны при работе на статическом языке: различные формы мартышечных патчей, которые используются для метапрограммирования, насмешек и тестирование, динамическая замена кода и т. д. Smalltalk и Lisp, оба очень динамичные, довели его до такой степени, что отправляют образы среды вместо сборки из исходного кода. Но когда вы хотите убедиться, что конкретные пути данных безопасны для типов, добавьте утверждения и напишите больше модульных тестов.

9000
источник
1
+1, хотя тесты могут только показать, что ошибки не происходят в определенных ситуациях. Они плохая замена для доказательства того, что (тип) ошибки невозможны.
Инго
1
@ Инго: конечно. Но динамические языки отлично подходят для создания и быстрого создания прототипов, когда вы очень быстро выражаете относительно простые идеи. Если вам нужен пуленепробиваемый производственный код, вы можете позже обратиться к статическому языку, когда вы извлечете некоторые стабильные основные компоненты.
9000
1
@ 9000, я не сомневаюсь, что они великолепны. Хотел бы просто указать, что написание 3 или 4 неудачных тестов не является и не может гарантировать безопасность типов .
Инго
2
@ 9000, Правда, и плохие новости в том, что даже тогда это практически невозможно. Даже код на Haskell или Agda основывается на предположениях, например, о том, что библиотека, используемая во время выполнения, верна. Тем не менее, в проекте с примерно 1000 LOC, распределенными по нескольким дюжинам файлов исходного кода, очень здорово, когда вы можете что-то изменить, и вы знаете, что компилятор будет указывать на каждую строку, где изменение оказывает влияние.
Инго
4
Плохо написанные тесты не являются заменой статической проверки типов: они уступают. Хорошо написанные тесты также не являются заменой статической проверки типов: они превосходны.
Рейн Хенрикс
8

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

Кроме того, для некоторых динамических языков существуют статические средства определения типов, средства проверки и / или применения:

А Perl 6 будет поддерживать опциональную систему типов со статической типизацией.


Но я предполагаю, что суть в том, что многие люди используют динамические языки, потому что они динамически типизированы, и для них необязательная статическая типизация очень «хо-хо». И многие другие люди используют их, потому что они «просты в использовании для непрограммистов», в значительной степени вследствие прощающего характера динамической типизации. Для них необязательная типизация - это то, что они либо не поймут, либо не будут использовать.

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

Стивен С
источник
2
Следует отметить, что проверка большинства типов в большинстве случаев недовольна большинством сообществ. Используйте полиморфизм (в частности, «типирование утки») при работе с объектами иерархии, приведите к ожидаемому типу, если это возможно / разумно. Остается несколько случаев, когда просто не имеет смысла разрешать какой-либо тип, но во многих языках вы получаете исключение в большинстве случаев, так что проверка типов редко бывает полезна.
4
«Люди обычно используют динамические языки, потому что они динамически типизированы» : JavaScript используется, потому что это единственный язык, который поддерживается большинством браузеров. PHP используется потому, что он популярен.
Арсений Мурзенко
2

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

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

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

Маке
источник
Я не знаю об этих планах включить дополнительную статическую типизацию в JavaScript; но надеюсь, что они не были такими ужасными, как в ActiveScript. худшее из JavaScript и Java.
Хавьер
Это было запланировано для JS 4 (или ECMAscript 4), но эта версия была отброшена из-за противоречий. Я уверен, что нечто подобное появится в будущем, на каком-то языке. (В Python вы можете сделать это с помощью декораторов, кстати.)
Macke
1
Декораторы добавляют динамическую проверку типов, своего рода утверждения. Вы не можете получить исчерпывающую статическую проверку типов в Python, как бы вы ни старались, из-за крайней динамичности языка.
9000
@ 9000: это правильно. Тем не менее, я не думаю, что динамическая проверка типов является плохой (но я бы предпочел сравнения типа утки, например, JS4), особенно в сочетании с модульными тестами, и декораторы для типизации могли бы быть более полезными для поддержки IDE / lint-checkers, если стандартизированы.
Мак
конечно это не плохо! Это не вопрос морали. В какой-то момент тип должен быть «проверен» тем или иным способом. Если вы напишите * ((double *) 0x98765E) в C, CPU сделает это и проверит, действительно ли 0x98765E является указателем на double.
Инго
2

Объекты Python сделать имеют тип.

Вы указываете тип при создании объекта.

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

На самом деле, ручная проверка типов в Python - это почти всегда пустая трата времени и кода.

Это просто плохая практика - писать код проверки типа в Python.

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

Вы не пишете код, ваша программа по-прежнему не работает с TypeError.

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

Почему существует такое ограничение?

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

С. Лотт
источник
2
«У объектов Python есть тип». - правда? Так же, как объекты perl, объекты PHP и любой другой элемент данных в мире. Разница между статической и динамической типизацией заключается только в том, когда тип будет проверяться, т.е. когда ошибки типа проявляются. Если они появляются как ошибки компилятора, это статическая типизация, если они появляются как ошибки времени выполнения, это динамично.
Инго
@Ingo: Спасибо за разъяснения. Проблема заключается в том, что объекты C ++ и Java могут быть преобразованы из одного типа в другой, что делает тип объекта несколько мутным и, следовательно, делает "проверку типов" в этих компиляторах также немного мутным. Где проверка типа Python - даже если она выполняется во время выполнения - гораздо менее мутна. Кроме того, этот вопрос близок к тому, что у динамически типизированных языков нет типов. Хорошей новостью является то, что он не совершает такой распространенной ошибки.
С.Лотт
1
Вы правы, приведение типов (в отличие от преобразования типов, т. Е. ((Double) 42)) подрывает статическую типизацию. Они нужны, когда система типов недостаточно мощная. До Java 5 у Java не было параметров с параметризацией, тогда вы не могли жить без приведения типов. Сегодня это намного лучше, но система типов все еще испытывает недостаток в типах с более высоким родом, не говоря уже о полиморфизме более высокого ранга. Я думаю, вполне возможно, что динамически типизированные языки пользуются таким большим количеством последователей именно потому, что они освобождают одного от слишком узких систем типов.
Инго
2

Большую часть времени вам не нужно, по крайней мере, не на уровне детализации, который вы предлагаете. В PHP операторы, которые вы используете, делают совершенно ясным, какие аргументы вы ожидаете; Это немного упущение при проектировании, хотя PHP будет приводить ваши значения, если это вообще возможно, даже когда вы передаете массив операции, которая ожидает строку, и поскольку приведение не всегда имеет смысл, вы иногда получаете странные результаты ( и именно здесь полезны проверки типов ). Кроме этого, не имеет значения, если вы добавите целые числа 1и / 5или строки "1"и "5"- сам факт того, что вы используете+оператор сообщает PHP, что вы хотите рассматривать аргументы как числа, и PHP будет подчиняться. Интересная ситуация возникает, когда вы получаете результаты запроса из MySQL: многие числовые значения просто возвращаются в виде строк, но вы не заметите, так как PHP приводит их к вам всякий раз, когда вы рассматриваете их как числа.

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

И есть еще одна причина: динамические языки не имеют стадии компиляции. Даже если у вас есть ограничения типа, они могут срабатывать только во время выполнения, просто потому, что нет времени компиляции . Если ваши проверки в любом случае приводят к ошибкам во время выполнения, гораздо проще соответствующим образом смоделировать их: как явные проверки (например, is_XXX()в PHP или typeofв javascript) или путем генерирования исключений (как это делает Python). Функционально у вас есть тот же эффект (ошибка сигнализирует во время выполнения, когда проверка типа не проходит), но она лучше интегрируется с остальной семантикой языка. Просто не имеет смысла трактовать ошибки типа, принципиально отличающиеся от других ошибок времени выполнения в динамическом языке.

tdammers
источник
0

Вас может заинтересовать Haskell - это система типов, которая выводит типы из кода, и вы также можете указывать типы.

daven11
источник
5
Хаскель - отличный язык. Это как-то противоположно динамическим языкам: вы тратите много времени на описание типов, и обычно, как только вы выяснили свои типы, программа работает :)
9000
@ 9000: Действительно. Как только он компилируется, он обычно работает. :)
Маке
@Macke - для разных значений обычно , конечно. :-) Для меня самое большое преимущество системы типов и функциональной парадигмы, как я уже отмечал в другом месте, заключается в том, что не нужно заботиться о том, влияет ли изменение где-либо молча на некоторый зависимый код в другом месте - компилятор будет указывать на ошибки типа и изменчивое состояние просто не существует.
Инго
0

Как упоминалось в других ответах, существует два подхода к типизации при реализации языка программирования.

  1. Попросите программиста рассказать, что все переменные и функции используют для типов. В идеале вы также должны убедиться, что спецификации типа являются точными. Затем, поскольку вы знаете, какой тип вещи будет в каждом месте, вы можете написать код, который предполагает, что соответствующая вещь будет там, и использует любую структуру данных, которую вы используете для непосредственной реализации этого типа.
  2. Прикрепите индикатор типа к значениям, которые будут храниться в переменных и передаваться и возвращаться из функций. Это означает, что программисту не нужно будет указывать какие-либо типы для переменных или функций, потому что типы фактически принадлежат объектам, к которым относятся переменные и функции.

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

Ларри Коулман
источник
0

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

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

Следует отметить, что существуют и другие динамические языки, которые позволяют программисту указывать тип. Например, Groovy - один из известных динамических языков, который работает на JVM. И это было очень известным в последние дни даже. Обратите внимание, что производительность Groovy такая же, как у Java.

Надеюсь, это поможет вам.

муравьев
источник
-1

Это просто бессмысленно.

Почему?

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

Инго
источник
1
Почему? Имеет смысл подсказать компилятору, какие типы ожидать. Это не будет противоречить ни одному из ограничений системы типов.
SK-logic
1
SK-логика: если динамически типизированный означает, что каждая функция / метод / операция принимает объекты типа «Динамический», «Любой» или что-либо еще и возвращает «Динамический», «Любой», что угодно, в общем, нет никакого способа сказать, что определенное значение например, всегда будет целым числом. Следовательно, во время выполнения код должен проверять не целые числа в любом случае, как если бы тип был «Динамический». Это то, что делает статическая система типов: она позволяет доказать, что определенная переменная, поле или возвращаемый метод всегда будут иметь определенный тип.
Инго
@ Инго, нет, есть способ. Посмотрите, как это реализовано в Common Lisp, например. Это особенно полезно для локальных переменных - вы можете значительно повысить производительность, введя все эти подсказки при печати.
SK-logic
@ SK-logic: Возможно, вы можете сказать мне, когда и как ошибки типа обнаруживаются в CL? В любом случае, @MainMa довольно хорошо подытожил статус-кво в своем вопросе: это именно то, что можно ожидать от «чисто» динамических языков.
Инго
@ Инго, что заставляет вас думать, что типы полезны только для статической проверки правильности? Это неверно даже для таких языков, как C, где у вас есть непроверенное приведение типов. Аннотации типов в динамических языках в основном полезны в качестве подсказок компилятора, которые повышают производительность или указывают конкретное числовое представление. Я бы согласился, что в большинстве случаев аннотации не должны изменять семантику кода.
SK-logic
-1

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

dan_waterworth
источник