Почему динамическая типизация так часто ассоциируется с интерпретируемыми языками?


Простой вопрос, Ребята: я много программирую (профессионально и лично) на компилируемых языках, таких как C++/Java, и на интерпретируемых языках, таких как Python/Javascript. Я лично считаю, что мой код почти всегда более надежен, когда я программирую на статически типизированных языках. Однако почти каждый интерпретируемый язык, с которым я сталкиваюсь, использует динамическую типизацию (PHP, Perl, Python и т. д.). Я знаю, почему компилируемые языки используют статическую типизацию (большую часть времени), но я не могу понять отвращение к статической типизации. статическая типизация в интерпретируемом языковом дизайне.

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

7 39

7 ответов:

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

Я думаю, что здесь есть ошибочное предположение. Авторы PHP, Perl, Python, Ruby, Lua и т. д. не разрабатывали "интерпретируемые языки", они разрабатывали динамические языки и реализовывали их с помощью интерпретаторов. Они сделали это потому, что переводчиков гораздо легче написать, чем компиляторы. Первая реализация Java была интерпретирована, и это статически типизированный язык. Интерпретаторы существуют для статических языков: у Haskell и OCaml есть интерпретаторы, и раньше был популярный интерпретатор для C, но это было очень давно. Они популярны, потому что они позволяют REPL, что может облегчить разработку. Тем не менее, как и следовало ожидать, в динамическом языковом сообществе существует отвращение к статической типизации. Они считают, что системы статических типов, предоставляемые C, C++ и Java, многословны и не стоят усилий. Я думаю, что в какой-то степени согласен с этим. Программирование на Python гораздо интереснее, чем на C++.

Чтобы обратиться к точкам других:

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

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

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

Представьте себе следующий сценарий (в Python):

import random
foo = 1

def doSomeStuffWithFoo():
    global foo
    foo = random.randint(0, 1)

def asign():
    global foo
    if foo == 1:
        return 20
    else:
        return "Test"


def toBeStaticallyAnalyzed():
    myValue = asign()

    # A "Compiler" may throw an error here because foo == 0, but at runtime foo maybe 1, so the compiler would be wrong with its assumption
    myValue += 20


doSomeStuffWithFoo() # Foo could be 1 or 0 now... or 4 ;)
toBeStaticallyAnalyzed()

Как вы можете надеяться увидеть, компилятор не будет иметь никакого смысла в этой ситуации. Остро это может предупредить вас о возможности того, что "myValue" может быть чем-то другим, чем a Число. Но тогда в JavaScript это не сработает, потому что если "myValue" является строкой, 20 будет неявно преобразовано в строку, следовательно, ошибки не возникнет. Таким образом, вы можете получить тысячи бесполезных предупреждений повсюду, и я не думаю, что это намерение компилятора.

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

Так что ваш решение как компилятор? - Исправьте это с помощью "try: except":)

Компиляторы + статические типы = эффективный машинный код
Компиляторы + динамические типы = неэффективный машинный код

Рассмотрим следующий псевдокод:

function foo(a, b) {
    return a+b
}

Статический язык будет знать (путем объявления или вывода), что a и b являются целыми числами, и будет компилироваться до

%reg = addi a,b

Или что-то подобное, во всяком случае.

Компилятор для динамического языка должен был бы выдавать код в
1. Проверьте их типы a и b
2. обрабатывать каждый случай или комбинацию случаи

%reg1 = typeof a
beq %reg1, int, a_int_case
beq %reg1, float, a_float_case
beq %reg1, string, a_string_case

label a_int_case
%reg1 = typeof b
beq %reg1, int, a_int_b_int_case
beq %reg1, float, a_int_b_float_case
beq %reg1, string, a_int_b_string_case

label a_int_b_int_case
%out = addi a,b
goto done

label a_int_b_float_case
%tmp = mkfloat a
%out = addf %tmp,b
goto done

... Etc. I can't finish

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

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

(компиляторы Just-in-time на самом деле имеют информацию о типе и могут компилировать вплоть до одного оператора. Они на самом деле имеют больше информации, чем статические тип системы, и теоретически может сделать даже лучше. Все ассемблеры симулируются; любое сходство с реальным кодом, который мог бы работать на реальной машине, является чисто случайным.)

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

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

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

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

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

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

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

Интерпретируемые языки преуспейте в динамической среде. Если вы можете интерпретировать новый код/информацию во время выполнения, то почему бы и нет. Если вы действительно хороши в динамическом программировании, то вы можете создать код, который может создавать переменные и хэши, никогда не печатая все. Вы можете резко сократить количество строк, если работаете с огромными объемами данных. Вы можете использовать Дампер данных для распечатки всей вашей информации, потому что интерпретируемые языки обычно отслеживают тип переменных во время выполнения, позволяя это быть возможным. Вы не можете сделать это в barebones c++. Единственное время, когда c++ и c знают, что происходит, - это время компиляции. После этого вы сами по себе, если вы не реализуете что-то самостоятельно.

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

На пути стилей программирования: Статически типизированный код дает статические результаты. Динамически типизированный код дает динамические или статические результаты.

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

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

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

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

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

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

С другой стороны. Вы знаете какой-нибудь динамически типизированный компилируемый (статически, а не JIT) язык?