Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>питон широко использует динамическое связывание переменных, которое никуда не девается в откомпилированном коде
ЭФ>https://habr.com/ru/post/481782/
ЭФ>Правильно ли я понимаю, что тип переменной хранится в рантайме рядом с самой переменной, и используется для выбора одной из функций из нескольких одинаковых по назначению, отличающихся только типами (размерами) аргументов?
Нет. Связывание — это процесс, который связывает имя объекта с самим объектом.
Чаще всего этот термин употребляют в контексте поиска метода — к примеру,
Function(x) может ссылаться (в зависимости от языка и контекста) на десяток разных методов с именем Function.
Если мы говорим про переменные, то каждая переменная — это, в конце концов, некая "ячейка памяти", которая однозначно определяется её адресом.
Соответственно, связывание переменных — это сопоставление имени некоторого адреса.
Практически в любом языке программирования одно и то же имя может сопоставляться с разными адресами, даже в один и тот же момент времени:
int main() {
static int x = 5;
std::cout << x << endl; // это - x №1
{
static int x = 6;
std::cout << x << endl; // это - x №2
}
std::cout << x << endl; // а это - снова x №1
}
public int CountTreeLeaves<T>(T node)
where T: IEnumerable<T>
{
var c = 0;
foreach(var child in node)
c+= CountTreeLeaves(child); // когда мы обходим глубокое дерево, у нас одновременно существуют несколько экземпляров переменной с именем "c" - по одной на уровень вложенности вызова
return c;
}
Но в нормальных, некурящих языках, это сопоставление делается
статически — то есть при компиляции программы. В первом примере у нас получилось два разных x с двумя разными адресами. Но вопрос о том, куда обращаться при выводе x, решается один раз, при компиляции, а при исполнении уже нет никакой свободы выбора. Иногда, как в первом примере, связывание получается абсолютным — т.е. адрес ячейки однозначно известен; даже если мы вызовем main() рекурсивно, оба x будут ссылаться на те же адреса, что и во внешнем вызове. Иногда, как во втором примере (и вообще в большинстве случаев) адреса указываются относительно текущего фрейма стека (что и позволяет с иметь одновременно множество не мешающих друг другу значений).
Те места, где связывание делается динамически, в таких языках обозначаются специальным образом (и, как правило, это относится исключительно к связыванию методов, а не переменных).
А в языках типа JS и питона связывание переменных выполняется динамически. То есть
адрес ячейки памяти, к которой нужно обратиться при вычислении выражения с участием переменной, определяется в момент исполнения программы; заранее этого сделать нельзя из-за правил связывания, описанных в языке.
ЭФ>Ещё мне непонятно, зачем нужно ключевое слово var в C#. Да, исторические причины, привычки, то-сё.
ЭФ>Но если проектировать синтаксис языка начисто, то:
ЭФ>во-первых, тип переменных заранее объявлять не нужно (он выводится);
ЭФ>во-вторых, тип переменной можно сделать зависимым от контекста,
ЭФ>для того чтобы можно было в двух последовательных строчках написать:
ЭФ>data=1; // целое
ЭФ>data="lala"; // строка
И какова будет семантика этого обращения?
Что должно быть выведено, если третьей строкой мы напишем
System.Console.WriteLine(data)?
ЭФ>Язык при этом останется типизированным, потому что
ЭФ>компилятор будут генерировать разный код в зависимости от типа переменной в месте использования.
Это как раз понятно. Непонятно, как определить тип переменной "в месте использования".
ЭФ>Рантайм не будет хранить тип переменной, потому что об этом будет заботится компилятор.
Это и есть статическое связывание, которое уже и так используется в C#.
ЭФ>Что со мной не так?
Нехватка образования. Ничего страшного — это поправимо