Динамическая типизация это когда тип объекта вычисляется или может меняться (не уверен только разговор только про рантайм или есть варианты). Например строка превратилась в json, или в объект добавился/удалился метод или свойство.
Утиная типизация, это когда сопоставление интерфейсов объектов выполняется в рантайме. Например если одному объекту требуется вызвать у другого методы read и write то вызывающему глубоко начхать на тип объекта пока у вызываемого есть такие методы.
Суть нормальных типов — в том, что их проверяют задолго до рантайма. В питоне все проверки — в рантайме.
Что-то не припомню ни одного языка, где все проверки типов были бы при компиляции. Разве что Haskell, но даже там есть fromDynamic.
Кроме того, нормальные типы технически невозможно проверить до рантайма. Например, тип функции map для типизированных списков должен описывать функцию, первым аргументом которой является n-арная функция, типы аргументов которой соответствуют типам переданных n списков. В большинстве языков программирования, требующих явного декларирования типов, такую функцию даже описать нельзя.
Что касается проверок при компиляции, то им почти не поддаются интервальные типы, типы значений «ненулевой указатель», «непустой список», «матрица с ненулевым определителем», …
Что-то не припомню ни одного языка, где все проверки типов были бы при компиляции.
все языки со статической типизацией. а их прилично.
ну скажем так..99 процентов всяких проверок на этапе компиляции. на рантайм, и то опционально выводится проверка выхода индекса границу аассива, проверка нахождения значения внутри интервала, правильный вариант в вариантном типе… да и все пожалуй. динамическую проверку типа для экземпляров классов опускаем, поскольку это средство языка по сути
Он, видимо, имел ввиду ситуации, при которых для одного и того же статического типа допустимы разные множества операций(тоже разыменование - nullptr vs valid pointer value)
Суть нормальных типов — в том, что их проверяют задолго до рантайма. В питоне все проверки — в рантайме
Мне в этом плане нравятся больше статьи из русской вики, которые говорят о типизации и не являются переводом английских, а английские говорят про эти вещи в единой статье «type checking», и в этой статье приводится деление «статическая проверка типов, динамическая проверка типов». Я же должен заметить, что тип - это не свойство данных, потому что все данные в компьютере - это одни и те же цифры. Тип - это правила работы с этими цифрами. Потому «type checking» - это проверка логических связей в правилах работы с числами, то есть, скорее «typed checking» или «типизованная проверка».
Учитывая то, что в хороших высокоуровневых языках есть REPL, а также существует метапрограммирование, вроде тьюринг полных шаблонов крестов — граница между этапом компиляции и выполнения стирается, поскольку мы можем компилировать после выполнения и выполнять после компиляции.
Естественно, проверять проверка работоспособности программы в условиях разработки более удобна, чем отлов этих ошибок в продакшене. Дальше уже нет разницы, динамическая у вас типизация или статическая, и вообще что эти термины значат — самое главное, что софтина не падает внезапно на презентации заказчику.
Утиная типизация, это когда сопоставление интерфейсов объектов выполняется в рантайме.
Рантайм тут не при чем. Утиная типизация и в компайл тайме прекрасно работает. Утиная типизация это когда тип определяется по свойствам, которыми этот тип обладает, а не по его буквальному типу. Если тип плавает как утка, крякает как утка, ходит как утка - то это утка. Отсюда и название.
Суть нормальных типов — в том, что их проверяют задолго до рантайма. В питоне все проверки — в рантайме.
Что-то не припомню ни одного языка, где все проверки типов были бы при компиляции. Разве что Haskell, но даже там есть fromDynamic
Да, это правда, я не знаю ни одного языка, который бы достаточно исчерпывающе проводил проверку логической корректности своего кода до выполнения. Даже Ada. которая превращает в ад процесс разработки, все равно оставляет множество лазеек.
граница между этапом компиляции и выполнения стирается
вы это очень сильно преувеличивается. граница есть и она не сотрется в принципе, она кардинальная. Во время компиляции еще AST даже не имеет конечной формы. А во время выполнения уже бинарный код выполняется. Это два кардинально разных этапа в жизни приложения.
Дальше уже нет разницы, динамическая у вас типизация или статическая, и вообще что эти термины значат
Очень большая разница между динамической и статической типизацией, очень большая. И разработчик должен различать эти моменты.
самое главное, что софтина не падает внезапно на презентации заказчику
Вот и выросло поколение, софт которого вместо того, чтобы выполнять свои задачи с качеством не хуже заданного - просто не падает на презентации заказчику. Получается, если если упало во время эксплуатации, то это нормально?
вы это очень сильно преувеличивается. граница есть и она не сотрется в принципе, она кардинальная. Во время компиляции еще AST даже не имеет конечной формы. А во время выполнения уже бинарный код выполняется. Это два кардинально разных этапа в жизни приложения
Есть REPL для C++. Питон компилируется в байткод и выполняет всегда только байткод. А что делать с кодом, сгенерированным уже во время выполнения, например, eval? И при этом всём питоний код можно AOT-скомпилировать через cython/numba/etc.
Вот и выросло поколение, софт которого вместо того, чтобы выполнять свои задачи с качеством не хуже заданного - просто не падает на презентации заказчику. Получается, если если упало во время эксплуатации, то это нормально?
Нет, это просто бойцы из моей помойки высрали какую-то софтину, которая прямо на презентации отвалилась, и в итоге демонстрацию проводили по моему модулю. Это при том, что эти же люди энное время назад меня пытались учить правильно писать код. Прям вспоминается https://ru.wikipedia.org/wiki/Катастрофа_Ан-148_в_Подмосковье , где второй пилот вопреки указаниям капитана начал толкать штурвал от себя и кричать что-то вроде «ара, мамой клюнусь ат сибя надо, я фсигда так дэлаю».
вы это очень сильно преувеличивается. граница есть и она не сотрется в принципе, она кардинальная.
Для достаточно мощных языков нету. Можно указать, что часть вычислений следует делать при компиляции, можно указать, что часть кода генерируется при выполнении. Для достаточно мощных компиляторов определение момента вычисления может даже выполняться автоматически.
Во время компиляции еще AST даже не имеет конечной формы.
Для лиспа, например, во время компиляции и AST уже есть и часть данных можно заранее вычислить.
А во время выполнения уже бинарный код выполняется.
Если в языке существует функция eval, то может и новый бинарный код появляться.
Очень большая разница между динамической и статической типизацией, очень большая.
Можно прикрутить «статическую» типизацию постфактум. Как раз питон как пример.
Можно реализовать динамическую типизацию поверх любой статической GVariant в C, std::variant в C++, Dynamic в Haskell.
Можно в программе их использовать, смешивая в любой пропорции.
Да, это правда, я не знаю ни одного языка, который бы достаточно исчерпывающе проводил проверку логической корректности своего кода до выполнения.
вы сначала дайте определение «логической корректности кода».
статическая типизация определяет инварианты или ограничения на то, то вы пишете, и проверяет эти инварианты во время компиляции, если это возможно. если же это невозможно, по причинам очевидным, предлагаются опциональные(или неопциональные) рантаймовые проверки этих инвариантов.
«динамическую типизацию», вообще сложно назвать типизацией. поскольку никто не собирается проверять простейших инвариантов - можно ли сложить a и б, или конкатенировать b и c. и есть ли у обьекта d метод toString(). и уж сколько обломов пожато на этой ниве, когда вдруг оказывается, что a и b сложить нельзя.
Ну так никто и не пытается в продакшне проверить вообще всё (потому, что для этого нужны как минимум зависимые типы, а все языки с ними — маргинальщина, да и скорость разработки так себе).
То, что уже есть в мейнстримных языках (хотя бы шарп/свифт/rust) всё же гораздо лучше, чем в питоне. Если добавить питоновскую практику «в любой непонятной ситуации бросай исключение» при том, что checked exceptions в принципе не планируются — даже аннотации типов + тот же mypy становятся заметно менее полезными. Хотя с ними и чуть легче, чем вообще без них.
все языки со статической типизацией. а их прилично. ну скажем так..99 процентов всяких проверок на этапе компиляции
только проверки типа отличить указатель от числа, а число от буквы.
И то: в Си число от буквы не отличается, в Паскале массивы с одинаковым количеством и типами элементов — разные типы, в Си++ невозможно указать, что параметр функции должен быть конкретного типа (подойдёт любой тип с заданным преобразованием).
Проблема статической титпизации в том, что она почти всегда загоняет доступные типы в прокрустово ложе некой системы, которую удобно реализовать в машинных кодах. И потом вместо типов предметной области программист выбирает самое близкое из доступных : ограниченное длиной double приближённое значение вместо точного ограниченного предметной областью для температуры и скорости, функция для работы с прямоугольниками не может работать с квадратами, списки и массивы не могут содержать значения разного типа, и т.д.
поскольку никто не собирается проверять простейших инвариантов - можно ли сложить a и б, или конкатенировать b и c. и есть ли у обьекта d метод toString(). и уж сколько обломов пожато на этой ниве, когда вдруг оказывается, что a и b сложить нельзя.
Если по этому куску кода хоть раз прошёл тест, то наличие операций и методов было проверено.
статическая типизация определяет инварианты или ограничения на то, то вы пишете
Очень малое подмножество инвариантов (нельзя указать ни «непустой список» ни «ненулевое число» ни «отсортированный массив»). И в обмен на это крайне ограничивает возможные типы данных (нельзя в статических типах выразить ни лисповый map ни compose ни curry. Даже статические типы Typed Racket типа «список из 4 элементов с типами: целое, int32, рациональное, строка» или «функция принимающая int8 или int16 или int32 и возвращающая int16, если аргумент был int8, int32 если аргумент был int16, целое число, если аргумент был int32» в большинстве статических языков невозможны.
Так-то оно всё фичи. Просто из-за этого существует множество алгоритмов, которые легко можно описать, например, на Лиспе и невозможно описать на языке со статической типизацией и приходится городить костыли, чтобы получить результат.
список из 4 элементов с типами: целое, int32, рациональное, строка
Это не список, а tuple. И это есть даже в сях, называется struct.
функция принимающая int8 или int16 или int32 и возвращающая int16, если аргумент был int8, int32 если аргумент был int16, целое число, если аргумент был int32
«список из 4 элементов с типами: целое, int32, рациональное, строка» или «функция принимающая int8 или int16 или int32 и возвращающая int16, если аргумент был int8, int32 если аргумент был int16, целое число, если аргумент был int32» в большинстве статических языков невозможны.
ну блин…
скажите пожалста, какой физсмысл имеет ваш список «целое, int32, рациональное, строка»… я сразу придам ему законченную форму в виде статического типа.
а чудаковатая функция, что также не имеет серьезного смысла, принимая на вход все что угодно, и выплевывающая наружу, все что ей внутри вздумывается, также можно запросто описать в статических типах, как функцию одного аргумента, принимающую на вход некий список, и выкидывающую новый список. и в таком виде можно описать любую функцию.
просто если у вас есть необходимость в таких списках и таких функциях - значит вы явно не понимаете как задачу решать.
Так в других языках слегка другие best practices. На лиспе это список потому, что так принято в лиспах. Но «список из элементов разного типа» — это не нормальный список. Юзкейс же — тупо контейнер для того, чтобы вернуть несколько значений из функции? В тот же map() такие вещи обычно не передают и оно даже не обязано быть iterable, если разобраться?
Да, это правда, я не знаю ни одного языка, который бы достаточно исчерпывающе проводил проверку логической корректности своего кода до выполнения.
Idris ?
Не знаком, но сомневаюсь, что там такое есть. Нужно ведь именно строить правила проверк инад правилами проверок до бесконечности, типа «вложений правил столько, сколько элементов в списке».
Если по этому куску кода хоть раз прошёл тест, то наличие операций и методов было проверено.
Самое интересно, что сколько ни пиши тестов на, скажем, a+b, все равно защиnbться от случая, когда в a и b попадет нечто несовместимое не получится. Просто потому что динамическая типизация по своей природе не накладывает никаких ограничений на то, какие значения могут попасть в a и b в ходе выполнения программы.
нельзя указать ни «непустой список» ни «ненулевое число» ни «отсортированный массив»
Омайгад, это все как раз тривиально делается: на уровень типов выносится понятие «непустой список/ненулевое число/отсортированный массив», а затем пишется функция, которая преобразует «сырой тип» в «обогащенный свойством» (хинт: функция sort может принимать «array of T» и возвращать «sorted_array of T»).
Приседания начинаются при попытке сделать «обогащенный» тип совместимым с исходным, т. е. чтобы тип «ненулевое число» можно было передать и в функцию, работающую с «просто числом».
Любопытно, что подобие этой проблемы присутствует в стандартной библиотеке C: strchr и аналоги принимают const char*, а возвращают char*, потому что в системе типов const присобачен абы как. Но уже даже в плюсах можно написать аналогичную фунцию, которая будет сохранять константность. Кажется, даже в C11 можно.
Самое интересно, что сколько ни пиши тестов на, скажем, a+b, все равно защиnbться от случая, когда в a и b попадет нечто несовместимое не получится. Просто потому что динамическая типизация по своей природе не накладывает никаких ограничений на то, какие значения могут попасть в a и b в ходе выполнения программы
Ну такой себе аргумент. В Си тоже можно сделать какое-нибудь
Я сейчас сходу не вспомню пример, который вскрывает гниль дженериков (они добавляют немного проверок в compile time, а затем идут как Object в runtime с cast'ом в последний момент и сопуствующим ClassCastException), но лучше спрошу вот что: о каком выводе типов может идти речь, если невозможно без очень грязных хаков с рефлексией даже создать объект дженерикового типа?
Ну такой себе аргумент. В Си тоже можно сделать какое-нибудь
Что ты хотел этим показать? Никто в этом треде не говорит, что в C или даже в C++ сильная статическая типизация. Но сложить структуру с числом компилятор не позволит. И проконтролирует, что нигде в аргументах не просунут структуру вместо числа или наоборот (считаем, что все конструкторы/операторы преобразования типов explicit).
Я сейчас сходу не вспомню пример, который вскрывает гниль дженериков (они добавляют немного проверок в compile time, а затем идут как Object в runtime с cast'ом в последний момент и сопуствующим ClassCastException)
String[] strings = new String[10];
Object[] objects = strings;
objects[0] = new Object();
но лучше спрошу вот что: о каком выводе типов может идти речь, если невозможно без очень грязных хаков с рефлексией даже создать объект дженерикового типа?
Чет я не понял сути проблемы.
Map<String, List<String>> myMap = new HashMap<String, List<String>>()
Самое интересно, что сколько ни пиши тестов на, скажем, a+b, все равно защиnbться от случая, когда в a и b попадет нечто несовместимое не получится. Просто потому что динамическая типизация по своей природе не накладывает никаких ограничений на то, какие значения могут попасть в a и b в ходе выполнения программы Что ты хотел этим показать? Никто в этом треде не говорит, что в C или даже в C++ сильная статическая типизация. Но сложить структуру с числом компилятор не позволит. И проконтролирует, что нигде в аргументах не просунут структуру вместо числа или наоборот (считаем, что все конструкторы/операторы преобразования типов explicit)
a = Int(arg1)
b = Int(arg2)
c = a+b
Вот и защитились. Заодно, считай, и типы объявили. В стандартной либе питона такое сплошь и рядом.
Еще раз: эта проверка будет выполнена только в runtime. Нет ничего, что помешало бы тебе передать строки в arg1/arg2. Это частично решается аннотациями типов (не ручаюсь за точность названия), но это пришито сбоку и не особо удобно в использовании — как минимум надо натравливать сторонний чекер.
Еще раз: эта проверка будет выполнена только в runtime. Нет ничего, что помешало бы тебе передать строки в arg1/arg2
В любых раскладах где-то в каком-то месте нужно проводить валидацию, потому что если и не передашь строку - можешь передать числовое некорректное значение и схватить переполнение/округление.
Вот и защитились. Заодно, считай, и типы объявили. В стандартной либе питона такое сплошь и рядом.
ну вот и пожалста. поняли что отсутствие типов(обычно тщательно выдаваемое за «динамическую типизацию») - это тупик, и перешли нормальным кондовым статическим типам в стиле паскаля аж 50 летней давности
В любых раскладах где-то в каком-то месте нужно проводить валидацию
Если в крестах ты в объявишь прототип функции
std::string doit(Foo &foo, Bar &bar);
То нигде в программе не сможешь написать int x = doit(5, 15); — компилятор не позволит. Никаких тестов, никаких преобразований. Просто на уровне типов нельзя передать 5 вместо Foo и 15 вместо Bar, а получившийся string запихнуть в int.
Если в крестах ты в объявишь прототип функции std::string doit(Foo &foo, Bar &bar); То нигде в программе не сможешь написать int x = doit(5, 15); — компилятор не позволит. Никаких тестов, никаких преобразований. Просто на уровне типов нельзя передать 5 вместо Foo и 15 вместо Bar, а получившийся string запихнуть в int
Смогу, и напишу. Просто программа не скомпилируется. С таким же успехом можно говорить про «программа не пройдет тесты» или «программа не пройдет статический анализатор» — последние, к слову, есть и для крестов, чтобы ловить то, что штатный компилятор не ловит.
ну блин… скажите пожалста, какой физсмысл имеет ваш список «целое, int32, рациональное, строка»
Аргументы для некой функции. К этим аргументам в голову будет добавлено ещё два аргумента и вызвана функция с этими аргументами.
просто если у вас есть необходимость в таких списках и таких функциях - значит вы явно не понимаете как задачу решать.
Умножение для аргументов int8 должно возвращать int16, для аргументов int16 возвращать int32, для int32 возвращать int64. На ассемблере примерно так: https://www.felixcloutier.com/x86/mul
Какой нафиг компиляции? Питон хорош в качестве обвязки, когда из нескольких готовых библиотек тебе нужно быстро соорудить программу. Поэтому он популярен в научных расчетах. Еще это неплохой вариант для прототипирования. Плюс, питон — хорошая замена башу для автоматизации и системных скриптов. Недаром он используется в портаже. Не нужно забивать гвозди микроскопом и изучать микромир с помощью молотка.