В комментарии к Разработка типовой системы автор комментария использовал оборот «жёстко-типизованные языки». Видимо, автор имеет ввиду, что есть языки с разной «жесткостью» типизирования.
На мой взгляд, типизация не бывает разной жесткости — типизация, это всего лишь классификация данных. И она неизбежно должна быть во время выполнения. Речь может идти только в времени когда происходит типизация.
В условном языке с одним типом данных, типизация есть всегда. В классических языках со статической типизацией, типизация большей частью происходит во время компиляции. Не всегда, потому что для x.foo() в OOP языке компилятор проверяет только то, что в типе ‘x’ есть ‘foo’, но не может определить (в большинстве случаев) какая именно функция будет вызвана. В языках с утиной типизацией еще большая часть типизации сдвигается в сторону времени исполнения. И так до условного Javascript, в котором только во время исполнения становится известно есть ли в ‘foo’ в ‘x’, и можно ли его вызвать.
Если посмотреть на развитие языков, то в последнее время статические языки добавляют динамику (типизация во время исполнения), а динамические языки — статику, см. например, gradual typing.
Противопоставление статической и динамической типизации ушло в прошлое, вопрос стоит в правильном балансе. Вместо бинарного противопоставления есть шкала, с более-менее плавными переходами между вариантами типизации:
- Разработчик явно указал тип переменной — типизация во время написания текста (до компиляции).
- Вывод типов (type inference) — компилятор вывел тип переменной во время компиляции.
- Интерпретатор или среда выполнения (run-time) сделал типизацию в конкретной точке исполнения, определил/построил нужный тип (не только для динамических языков, но и для утиных интерфейсов, как в Go).
- JIT компилятор вывел множество допустимых типов , то есть построил тип объединения, в точке исполнения и породил код для этого типа.
- JIT компилятор использовал данные профилировщика и выполнил типизацию во время N-го выполнения (собирая данные во время первых N-1 выполнений).
А еще могут быть промежуточные точки, например, статический или динамический линкер провел анализ и определил типы. Или частичная типизация выполнена при конкретизации компоненты. Собственно, именно поэтому, я говорю о более-менее непрерывной шкале.
Вывод:
- Типизация всегда есть, только выполняется в разное время, что влияет на эффективность/стабильность/полиморфность/гибкость кода.
- При проектировании языка программирования нужно думать о балансе между временем типизации в соответствии с предполагаемой областью применения.
- Семейство языков должно закрывать разные области применения, то есть иметь в составе языки с разным балансом времени типизации.
Постоянная ссылка
«И она неизбежно должна быть во время выполнения.»
Вовсе нет, Алексей. Типизация этапа исполнения — это полезная фича (я в вяло текущем режиме пытаюсь создать ВМ такого ЦП). Но современные подходы, недооценивающие важность типизации этапа исполнения имеют лишь зачаточную поддержку (например, секция кода для чтения и секция данных для чтения/записи, запрет исполнения данных и стека).
Го, являясь с языком с утиной типизацией — на самом деле на этапе исполнения проверяет соответствие интерфейсов только при первом обращении. Динамическая типизация реализуема, но программным способом ,а не аппаратным ( а хотелось бы именно так для критичных задач).
Да, аппаратная типизация дорого, и даже программный контроль типов на этапе исполнения может быть избыточен. Но типизация должна быть строгой с явным приведением типа как минимум на этапе компиляции. В противном случае будет ситуация как сейчас у меня на работе — хорошо, что тесты 96% код покрывают (сбойная память в кластере ВМ с высокой вероятностью) — по другому невозможно выявить, что за чертовщина происходит. А приложение про деньги. Потом бы в меня пальцем тыкали, что я не могу код писать без глюков.
Постоянная ссылка
«типизация должна быть строгой с явным приведением типа как минимум на этапе компиляции» — для вашей задачи, готов согласится — должна быть на этапе компиляции. Но есть другие задачи, от одноразового «скрипта», который будет выполнен один раз прямо сейчас, и до задач, в которых важен полиморфизм.
Руслан Богатырев как-то писал о необходимости разного «материала» для разных программ — для каких-то домиков достаточно соломы, а другие надо строить из железобетона, да еще и погрузить на 100 метров под землю.
Необходимость семейство языков как раз и вытекает из того, что задачи очень разные и решать их надо с помощью разных инструментов. При этом, желательно чтобы часть инструментов все же была одинаковой или заменяемой. Помните эмблему Комкона — семигранную гайку?
Хочется избежать того, что постоянно нужно сейчас — переходников с семигранной гайки на девятигранную.
Постоянная ссылка
Я сначала возрадовался что столкнулся с эффективной реализацией duck typing , но концептуально она не вмещается в следующую схему
type A
foo ()
precondition predicate1
postcondition predicate2
end // foo
invariant
predicate3
end // type A
type B
foo ()
precondition predicate4
postcondition predicate5
invariant
predicate6
end // type B
foo (x: A) do
x.foo ()
end // foo
foo (new B())
Да мы можем на статике проверить что тип B имеет метод с именем foo и с нужной сигнатурой, а вот как быть с пред,пост условиями и инвариантом. Т.е. я не знаю способа как доказывать эквивалентность двух логичких утверждений (предиктов) на статике по сути по их тектсовому описанию. Т.е. передаваемый аргументы котрые валидны для foo из A вполен могут быть не валидны для foo и В котрое в резульатет мы будем пытаться вызывать.
Да наследование implies coupling но оно увязывает опредленным образом пред,пост условия и инварианты — чего мне непонятно как достичь в случае с duck typing …
Как всегда я не говорю нет, а просто ‘особое мнение’