Есть ли реальное преимущество для динамических языков? [закрыто]

29

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

Динамически типизированные языки позволяют вам помещать любое значение в любую переменную. Так, например, вы можете написать следующую функцию (psuedocode):

void makeItBark(dog){
    dog.bark();
}

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

По-видимому, это дает вам гибкость.

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

Например, когда вы пишете makeItBark()функцию, вы намереваетесь принять только «вещи, которые могут лаять», и вам все равно нужно убедиться, что вы пропускаете в нее только такие вещи. Разница лишь в том, что теперь компилятор не сообщит вам, когда вы допустили ошибку.

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

Я что-то пропустил? Что я на самом деле получаю, используя динамически типизированный язык?

Авив Кон
источник
6
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof")), Этот аргумент даже не класс , это аноним с именем кортеж. Утиная печать («если она крякает как ...») позволяет создавать специальные интерфейсы с практически нулевыми ограничениями и без синтаксических накладных расходов. Вы можете сделать это на таком языке, как Java, но в итоге вы получите много грязных размышлений. Если функция в Java требует ArrayList и вы хотите присвоить ей другой тип коллекции, вы SOL. В питоне это даже не может всплыть.
Фоши
2
Этот вопрос задавался раньше: здесь , здесь и здесь . Именно первый пример, кажется, отвечает на ваш вопрос. Может быть, вы можете перефразировать свой, чтобы выделить его?
Журнал
3
Обратите внимание, что, например, в C ++ у вас может быть функция шаблона, которая работает с любым типом T, у которого есть bark()метод, при этом компилятор жалуется, когда вы передаете что-то неправильно, но без необходимости фактически объявлять интерфейс, содержащий bark ().
Уилберт
2
@Phoshi Аргумент в Python по-прежнему должен быть определенного типа - например, он не может быть числом. Если у вас есть собственная реализация объектов ad-hoc, которая извлекает своих членов с помощью какой-то пользовательской getMemberфункции, она makeItBarkвзрывается из-за того, что вы вызвали ее dog.barkвместо dog.getMember("bark"). То, что заставляет код работать, - то, что все неявно соглашаются использовать нативный тип объекта Python.
Довал
2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Как указано в моем ответе, это не так в целом . Это относится к Java и C #, но эти языки имеют ограниченные системы типов и модулей, поэтому они не представляют, что может делать статическая типизация. Я могу написать совершенно универсальный язык makeItBarkдля нескольких статически типизированных языков, даже нефункциональных, таких как C ++ или D.
Doval

Ответы:

35

Динамически типизированные языки не типизированы

Сравнивая системы типов , в динамической типизации нет преимуществ. Динамическая типизация - это особый случай статической типизации - это язык статической типизации, где каждая переменная имеет один и тот же тип. Вы можете добиться того же в Java (за исключением краткости), сделав каждую переменную типовой Object, а значения «объект» - типом Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

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

Изготовление утиной коры на языке статической типизации

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

class Barkable a where
    bark :: a -> unit

Это выражает ограничение, что для того, чтобы некоторый тип aсчитался Barkable, должна существовать barkфункция, которая принимает значение этого типа и ничего не возвращает.

Затем вы можете написать универсальные функции в терминах Barkableограничения:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Это говорит о том, что makeItBarkбудет работать для любого типа, удовлетворяющего Barkableтребованиям. Это может показаться похожим на interfaceJava или C #, но у него есть одно большое преимущество - типы не должны заранее указывать , какие классы типов они удовлетворяют. Я могу сказать , что тип Duckявляется Barkableв любое время, даже если Duckэто тип третьей стороны , я не писал. На самом деле, не имеет значения, что автор Duckне написал barkфункцию - я могу предоставить ее после факта, когда я говорю на языке, который Duckудовлетворяет Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Это говорит о том, что Ducks может лаять, и их функция лая реализуется путем удара по утке перед тем, как ее крякнуть. С этим из пути мы можем призвать makeItBarkуток.

Standard MLи OCamlеще более гибки в том, что вы можете удовлетворить один и тот же класс типов несколькими способами. В этих языках я могу сказать, что целые числа можно упорядочить, используя обычное упорядочение, а затем развернуться и сказать, что они также можно упорядочить по делимости (например, 10 > 5потому что 10 делится на 5). В Haskell вы можете создать экземпляр класса типов только один раз. (Это позволяет Haskell автоматически знать, что можно вызывать barkутку; в SML или OCaml вы должны четко указывать, какую bark функцию вы хотите, потому что их может быть несколько).

сжатость

Конечно, есть синтаксические различия. Код Python, который вы представили, гораздо более лаконичен, чем Java-эквивалент, который я написал. На практике эта краткость является большой частью привлекательности динамически типизированных языков. Но вывод типов позволяет вам писать код, который так же лаконичен для языков со статической типизацией, избавляя вас от необходимости явно писать типы каждой переменной. Язык со статической типизацией может также обеспечить встроенную поддержку динамической типизации, удаляя многословие всех операций приведения и преобразования карт (например, C # dynamic).

Правильные, но плохо набранные программы

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

if this_variable_is_always_true:
    return "some string"
else:
    return 6

В большинстве языков со статической типизацией это ifутверждение будет отклонено , хотя ветвь else никогда не появится. На практике кажется, что никто не использует этот тип кода - что-либо слишком умное для проверки типов, вероятно, заставит будущих сопровождающих вашего кода проклинать вас и ваших ближайших родственников. Например, кто-то успешно перевел 4 проекта с открытым исходным кодом на Python на Haskell, что означает, что они не делали ничего, что не смог бы скомпилировать хороший статически типизированный язык. Более того, компилятор обнаружил пару ошибок, связанных с типами, которые модульные тесты не улавливали.

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

Яблоки и апельсины

Наконец, не забывайте, что различия в языках намного больше, чем просто в системе типов. До Java 8 любое функциональное программирование на Java было практически невозможно; простая лямбда потребует 4 строки стандартного кода анонимного класса. Java также не поддерживает литералы коллекций (например [1, 2, 3]). Также могут быть различия в качестве и доступности инструментов (IDE, отладчиков), библиотек и поддержки сообщества. Когда кто-то заявляет, что он более продуктивен в Python или Ruby, чем в Java, необходимо учитывать это несоответствие функций. Существует разница между сравнением языков со всеми включенными батареями , языковыми ядрами и системами типов .

Doval
источник
2
Вы забыли приписать свой источник для первого абзаца - existentialtype.wordpress.com/2011/03/19/…
2
@Matt Re: 1, я не предполагал, что это не важно; Я обратился к этому под Краткостью. Re: 2, хотя я никогда не говорил об этом явно, под «хорошим» я подразумеваю «имеет полный вывод типов» и «имеет систему модулей, которая позволяет сопоставлять код с типом подписей после факта », а не заранее, как Java / Интерфейсы C #. В отношении 3, бремя доказывания лежит на вас, чтобы объяснить мне, как при наличии двух языков с эквивалентным синтаксисом и функциями, одного с динамическим типом, а другого с полным выводом типа, вы не сможете написать код одинаковой длины на обоих языках. ,
Довал
3
@MattFenwick Я уже оправдал это - учитывая два языка с одинаковыми функциями, один динамически типизированный, а другой статически типизированный, основным отличием между ними будет наличие аннотаций типов, а вывод типов убирает это. Любые другие различия в синтаксисе являются поверхностными, и любые различия в особенностях превращают сравнение в яблоки против апельсинов. Это на вас, чтобы показать, как эта логика неверна.
Довал
1
Вы должны взглянуть на Бу. Он статически типизирован с выводом типа и содержит макросы, позволяющие расширить синтаксис языка.
Мейсон Уилер
1
@Doval: правда. Кстати, лямбда-нотация не используется исключительно в функциональном программировании: насколько я знаю, Smalltalk имеет анонимные блоки, а Smalltalk настолько объектно-ориентирован, насколько это возможно. Поэтому часто решение заключается в передаче анонимного блока кода с некоторыми параметрами, независимо от того, является ли это анонимной функцией или анонимным объектом с одним анонимным методом. Я думаю, что эти две конструкции выражают по существу одну и ту же идею с двух разных точек зрения (функциональной и объектно-ориентированной).
Джорджио
11

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

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

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

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

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

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

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

Килиан Фот
источник
3
Спасибо за ответ. Вы сказали, что некоторым людям не нравится, когда компилятор говорит им, что делать. [..] Они предпочитают стиль исследовательского программирования, в котором вы пишете реальный бизнес-код без плана, который бы точно указывал, какие типы и аргументы использовать где ». Это то, чего я не понимаю: программирование не похоже на музыкальную импровизацию. В музыке, если вы нажмете не ту ноту, это может звучать круто. В программировании, если вы передадите что-то в функцию, которой там не должно быть, вы, скорее всего, получите неприятные ошибки. (продолжение в следующем комментарии).
Авив Кон
3
Я согласен, но многие люди не согласны. И люди весьма притягательны к своим умственным предубеждениям, особенно потому, что они часто не знают о них. Вот почему дебаты о стиле программирования обычно перерастают в споры или драки, и редко полезно начинать их со случайных незнакомцев в Интернете.
Килиан Фот
1
Вот почему, судя по тому, что я читал, люди, использующие динамические языки, учитывают типы так же, как люди, использующие статические языки. Потому что, когда вы пишете функцию, она должна принимать аргументы определенного вида. Не имеет значения, если компилятор обеспечивает это или нет. Так что в этом вам помогает статическая типизация, а динамическая типизация - нет. В обоих случаях функция должна принимать определенный вид ввода. Так что я не вижу преимущества динамической типизации. Даже если вы предпочитаете «исследовательский стиль программирования», вы все равно не можете передать все, что хотите, в функцию.
Авив Кон
1
Люди часто говорят об очень разных типах проектов (особенно в отношении размера). Бизнес-логика для веб-сайта будет очень простой по сравнению с полной ERP-системой. Существует меньший риск, что вы ошибаетесь, и преимущество более простого повторного использования некоторого кода является более актуальным. Скажем, у меня есть некоторый код, который генерирует Pdf (или немного HTML) из структуры данных. Теперь у меня другой источник данных (сначала это был JSON из REST API, теперь это импортер Excel). На таком языке, как Ruby, может быть очень легко «смоделировать» первую структуру, «заставить ее лаять» и повторно использовать код PDF.
Торстен Мюллер
@Prog: реальное преимущество динамических языков в том, что речь идет об описании вещей, которые действительно сложны для статической системы типов. Например, функция в python может быть ссылкой на функцию, лямбда-выражением, функциональным объектом, или бог знает что, и все будет работать одинаково. Вы можете создать объект, который упаковывает другой объект и автоматически отправляет методы с нулевыми синтаксическими издержками, и каждая функция по существу волшебным образом имеет параметризованные типы. Динамические языки удивительны для быстрого выполнения работы.
Фоши
5

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

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


источник
2
Разве люди не склонны в конечном итоге заново внедрять базовую систему статических типов с помощью своих модульных тестов (при нацеливании на хорошее тестовое покрытие)?
Ден
И что вы подразумеваете под «связью» здесь? Как бы это проявилось, например, в архитектуре микросервисов?
Ден
@ Ден 1) хороший вопрос, однако, я чувствую, что это выходит за рамки ОП и моего ответа. 2) я имею в виду связь в этом смысле ; вкратце, системы разных типов накладывают разные (несовместимые) ограничения на код, написанный на этом языке. Извините, я не могу ответить на последний вопрос - я не понимаю, что особенного в микросервисах в этом отношении.
2
@Den: Очень хороший момент: я часто наблюдаю, что модульные тесты, которые я пишу на Python, покрывают ошибки, которые будут обнаружены компилятором на статически типизированном языке.
Джорджио
@MattFenwick: Вы писали, что это преимущество в том, что «... для динамического языка не требуется разрабатывать, внедрять, тестировать и поддерживать статическую систему типов». Ден заметил, что вам часто приходится разрабатывать и тестировать ваши типы непосредственно в вашем коде. Таким образом, усилия не устраняются, а переносятся с языкового дизайна на код приложения.
Джорджио