Статьи

Динамическая типизация и объяснение динамического языка

Я только что закончил читать очень интересный пост о достоинствах статической типизации по сравнению с динамической типизацией . Мне потребовалось некоторое время, чтобы разобраться с примерами кода Ruby, Python, OCaml и Haskell. Тем не менее мне удалось сделать некоторые выводы, основанные на этой статье:

  • Ruby и Python являются динамически типизированными языками. Они не поддерживают статическую типизацию
  • OCaml и Haskell являются статически типизированными языками. Они не поддерживают динамическую типизацию
  • Многие люди смущены разницей между динамической типизацией и динамическими языками.

Разница достаточно очевидна, однако. VB Script (Visual Basic Script) — это язык с динамической типизацией, но не динамический язык. Код ниже действителен VB Script (работает на Windows с cscript.exe):

dim x
dim y

x = 1
y = 2

x = "ABC"
y = "XYZ"

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

class Dummy  
  def method_missing(m, *args)  
    args[0] + args[1]
  end  
end

raise "Error" unless Dummy.new.test(1, 2) == 3

test()Метод , названный по Dummyклассу в строке 7 отправляется Руби к method_missing()методу в строках 2 до 4. Python и Groovy также поддерживают динамическую отправку. В целом, динамические языки, такие как Ruby, Python и Groovy, имеют протокол мета-объектов или MOP.

Вернуться к посту, который я упоминал в начале. Автор пытается доказать, что статическая типизация превосходит динамическую типизацию. Чтобы выразить свою точку зрения, он использует этот код Ruby (есть также примеры на Python, OCaml и Haskell):

def test(a, b)
  a + b
end

def main()
  if ARGV.length > 3
    test(1, test)
  else
    test(1, 2)
  end
end

Process.exit(main())

Этот код отлично работает при передаче 0, 1, 2 или 3 аргументов в командной строке:

$ ruby -w -W2 t.rb; echo $?
3
$ ruby -w -W2 t.rb 0; echo $?
3
$ ruby -w -W2 t.rb 0 1; echo $?
3
$ ruby -w -W2 t.rb 0 1 2; echo $?
3

Однако при передаче 4 аргументов в командной строке скрипт Ruby завершается неудачно:

$ ruby -w -W2 t.rb 0 1 2 3; echo $?
t.rb:7:in `test': wrong number of arguments (0 for 2) (ArgumentError)
        from t.rb:7:in `main'
        from t.rb:13
1
$

На основании этого скрипта Ruby автор делает следующий вывод:

Как ожидается от динамически типизированного языка, такого как Ruby, ошибка не обнаруживалась до времени выполнения. […] Даже если бы использовались модульные тесты, вполне возможно, что дублирование такого сценария было бы пропущено, и озадаченный пользователь столкнулся бы с ошибкой, такой как приведенная выше.

Ну, с этим не поспоришь. Он продолжает с версиями того же скрипта для OCaml и Haskell. В заключение автор говорит:

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

И вот тут-то и возникла путаница, смешав динамическую типизацию с динамическим языком. Автор никогда не упоминает «динамический язык» в своем посте (только «динамически типизированный язык»), но утверждает, что статически типизированные языки обнаруживают путаницу типов во время компиляции, и, следовательно, безопаснее для разработчиков.

Увы, это не правда. Это верно для статически типизированных, не динамических языков, таких как Java, C #, OCaml и Haskell. Тем не менее, есть также статически типизированный, динамический язык. Это называется Groovy. Groovy поддерживает как динамическую, так и статическую типизацию.

Вот статически типизированный скрипт Groovy, который не выполняется во время выполнения:

int x = "test"

Это ошибка времени выполнения:

Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Невозможно привести объект «test» с классом «java.lang.String» к классу «java.lang.Integer»
в типе safe.run (тип safe.groovy: 1 )
у типа safe.main (тип safe.groovy)

Хм, Groovy, несомненно, безопасен для типов (однако учтите, что ошибки компиляции нет). Тем не менее, это также динамический язык. Вот предыдущий скрипт на Ruby, написанный на Groovy:

def test(int a, int b) {
    a + b
}

if (args.length > 3) {
    println test(1, "test")
} else {
    println test(1, 2)
}

Посмотрите на объявление test()метода в строках с 1 по 3 и его статически типизированные аргументы. Будет ли этот сценарий компилироваться? Да. Сбой этого сценария, если в командной строке передано не более 3 аргументов Нет. Вот вывод от 0 до 4 аргументов командной строки:

C:\>groovy type_safe
3
C:\>groovy type_safe 0
3
C:\>groovy type_safe 0 1
3
C:\>groovy type_safe 0 1 2
3
C:\>groovy type_safe 0 1 2 3
Caught: groovy.lang.MissingMethodException: No signature of method: type_safe.test() is applicable for argument types: (java.lang.Integer, java.lang.String) values: {1, "test"}
        at type_safe.run(type_safe.groovy:6)
        at type_safe.main(type_safe.groovy)
C:\>

Почему этот скрипт компилируется, когда test()метод набрал аргументы? Вызов test()метода в строке 6 явно некорректен!

Groovy — это динамический язык с мета-объектным протоколом. Компилятор Groovy не знает, как будет отправлен вызов метода в строке 6. Возможно, он отправляется test()методу, объявленному в строках с 1 по 3. Но динамическая конфигурация во время выполнения может также означать, что вызов метода отправляется в другое место.

Groovy поддерживает статическую типизацию, а Ruby и Python — нет. Поскольку они являются динамическими языками, их компиляторы или интерпретаторы не могут знать, как будут отправляться вызовы методов (эта информация доступна только во время выполнения). Их реализации мета-объектного протокола являются мощью этих динамических языков.

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

Отказ от Ruby, Python и Groovy, поскольку они не проверяют типы во время компиляции, не учитывает возможности динамических языков. Я понимаю, что нединамические языки желательны по многим причинам, но динамические языки тоже.

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

Удачного кодирования!