Как стать автором
Обновить

Комментарии 233

В переводе упущена отличнейшая сноска:
«BCPL later adding a floating point keyword. And when I say “later”, I mean in 2018.»
Просто отлично. Спасибо, добавил!
Напомнило историю про современные технические достижения и ширину крупа лошади.
Для тех, кто, как и я, не слышал эту историю раньше, приведу ее здесь.
Сама история
По бокам космического корабля «Кеннеди» размещаются два двигателя по 5 футов шириной. Конструкторы корабля хотели бы сделать эти двигатели еще шире, но не смогли. Почему? Дело в том, что двигатели эти доставлялись по железной дороге, которая проходит по узкому туннелю. Расстояние между рельсами стандартное: 4 фута 8.5 дюйма, поэтому конструкторы могли сделать двигатели только шириной 5 футов. Возникает вопрос: почему расстояние между рельсами 4 фута 8.5 дюйма? Откуда взялась эта цифра? Оказывается, что железную дорогу в Штатах делали такую же, как и в Англии, а в Англии делали железнодорожные вагоны по тому же принципу, что и трамвайные, а первые трамваи производились в Англии по образу и подобию конки. А длина оси конки составляла как раз 4 фута 8.5 дюйма! Но почему? Потому что конки делали с тем расчетом, чтобы их оси попадали в колеи на английских дорогах, чтобы колеса меньше изнашивались, а расстояние между колеями в Англии как раз 4 фута 8.5 дюйма! Отчего так? Да просто дороги в Великобритании стали делать римляне, подводя их под размер своих боевых колесниц, и длина оси стандартной римской колесницы равнялась… правильно, 4 футам 8.5 дюймам! Ну вот теперь мы докопались, откуда взялся этот размер, но все же почему римлянам вздумалось делать свои колесницы с осями именно такой длины? А вот почему: в такую колесницу запрягали обычно двух лошадей. А 4 фута 8.5 дюйма — это был как раз размер двух лошадиных задниц! Делать ось колесницы длиннее было неудобно, так как это нарушало бы равновесие колесницы. Следовательно, вот и ответ на самый первый вопрос: даже теперь, когда человек вышел в космос, его наивысшие технические достижения напрямую зависят от РАЗМЕРА ЛОШАДИНОЙ ЗАДНИЦЫ.
Эту историю уже развенчивали в разных местах, по пунктам.

Начиная с банального фактического несоответствия — космического корабля «Кеннеди» никогда не существовало, такое название носит космодром.

Прочие аргументы:

— ж/д габарит несколько шире ж/д колеи. Для «обычной» в два раза, для узкоколейки — аж в три, и в общем случае линейной зависимости нет.
— диаметр боковых ускорителей шаттла 3,7 метра, что равняется чуть более 12 футов, и в габарит вообще не вписывается:
«However, the diameter of the shuttle boosters (12 feet) exceeds the width of all loading gauge standards in the USA (which differ mostly in height, the width is generally 10 feet 8 inches).»
space.stackexchange.com/questions/4064/how-fair-is-the-claim-that-shuttle-boosters-diameter-is-dictated-by-the-railroad

Негабаритные грузы на мыс Каннаверал доставляются морским путём — так было со ступенями «Сатурна-V» диаметром 10,1 м и внешними топливными баки шаттлов восьмиметрового диаметра. Это больше десятка лошадиных задниц.

— если Англия шла по следам римских стандартов, почему это не сделали другие европейские страны? В Ирландии, например, существовало аж 6 стандартов колеи, да и в самой Англии встречались и 1600 мм, и даже 2135 мм то есть 7 футов (Большая Западная дорога ). В США тоже был полный раздрай, благо вместо единой сети каждая жд корпорация строила собственную.

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

— Расстояние между крупами лошадей зависит исключительно от типа и вида упряжи (дышловая и оглобельная) и количества впряжённых попарно лошадей (от 1,2 до 1,8 м). А ведь ещё и тройки лошадей запрягали...)))

И римская армия не использовала колесницы (не очень подходящий транспорт для тех ТВД, где оперировали легионы), у них и конница-то была или союзническая, или наемники.
For starters, the Roman army did not use chariots for warfare. Chariots were technologically obsolete by 600 BCE, centuries before the rise of Rome.…
The other aspect of this standardization urban legend that is pure fiction is the suggestion that the standard track gauge in the U.S. has always been 4 feet 8-1/2 inches.
At the beginning of the American Civil War in 1861, there were more than 20 different railroad track gauges in the U.S. ranging from 3 feet to 6 feet.…
Everyone seems to agree that this odd track size did originate in England from a railway pioneer named George Stephenson who used the 4 feet 8-1/2 inch track gauge when building the first public rail line, the Liverpool & Manchester Railway, in 1830.…
How ever the 4 foot 8-1/2 inch track gauge happened, it’s clear that the Roman military specification for “Chariots, War, Two-Horse” had nothing to do with it.
(это из выступления собственно NASA по поводу этой байки — https://standards.nasa.gov/documents/RomanChariots.pdf )

еще подробности: krazzzer.livejournal.com/35884.html

А вот российская космонавтика в большей степени ограничена железнодорожным габаритом: habr.com/post/211054
Самая логичная запись присваивания — в ранних версиях Рапиры:
Х+Y -> Х
она отражает тот факт, что сначала вычисляется выражение и уже потом результат присваивается переменной.
С одной стороны — да, но тогда будет сложнее искать инициализированные переменные в коде, ведь конец строки везде разный. Куда проще пробежаться глазами по одной линии.
Тоже верно

В R и сейчас так писать можно.

вот именно так, в постфиксной записи оператора присваивания?
В R вроде ж присваивание префиксное:
X <- X+Y
а, ясно. Просто в базовых «учебниках для чайников» такого варианта не находится.
Да, в любую сторону, и иногда, например в конвейерных пайпах, логичнее и «красивее» записать результат справа (dt%>%filter()%>%group_by()%>%summarize()->res)
Но ради всего святого, никогда так не делайте!

Те, кто пользуются dplyr, знают, что еще есть pipe операторы %<>%, которые избавляют от необходимости писать каждый раз что-то вроде df < — df %>%… По моему опыту, на практике их стоит избегать в кусках кода, которые часто могут меняться.
:= из той же оперы, этот аналогично БНФ записи, т.е. это не присвоение как таковое, а указание, что под терминалом, который слева, понимается то, что справа.

Куда проще, как по мне, использовать нотацию обычного отображения x -> w = x + 1

если воспринимать "a" как букву/название то действительно может показаться что a=a+1 — странно, но если сместиться на парадигму что "a" это у нас грубо говоря именнованый акцесс к памяти то
a=1 //записать в область памяти с меткой а значение 1,
a=a+1 //записать в область памяти с меткой а увеличнное на 1 предыдущее значение…

Так, в том то и дело, что нужно приложить некоторое количество усилий, чтобы "сместиться на парадигму", однако, не совсем понятно, что этими усилиями оплачено.

Тут есть большой парадокс: вроде как все работали с переменными на уроках математики и вроде как должно быть проще именно такие переменные использовать (наиболее близки они к «школьно-математическим» в Prolog'е)… ан нет: на практике «сместиться на парадигму» и начать воспринимать переменную как «имя коробки с содержимым» людям легко, а вот как раз «функциональщина» — воспринимается с преогромным трудом.

Интересно — почему так…
На уроках математики все же в одной формуле одна переменная по разные стороны знака равенства (или неравенства) подразумевает одинаковое значение.

а, ниже разжевали лучше: habrahabr.ru/post/353292/#comment_10750626
Нифига там не разжевали. «Математические» переменные — это Prolog. Вот там как раз можно написать «x = 2y + 1» и это даст нам как возможность исходя из «x = 1» получить «y = 0» и, наоборот, из «y = 1» получить «x = 3».

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

Вот как-то так получается, что чем дальше от «математики» и ближе к «железу», тем проще и понятней…
Пошаговое решение проблемы легче обозревается, потому что разбивается на малые, за раз обозримые части. А при функциональном/логическом подходе, напротив, надо всю запись целиком воспринимать одномоментно.
Интересно — почему так…

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


Ну вот простейший пример на haskell:


-- Example ha.1:
primes = map head $ iterate (\(x:xs) -> [y | y<-xs, y `mod` x /= 0 ]) [2..]

Или такой:


-- Example ha.2:
primes = 2 : [x | x <- [3, 5..], 
  (
    [
      y | y <- [2..(div x 2)],
      mod x y == 0
    ] == []
  )
]

И сравните это по читабельности ну например с python:


# Example py.1:
(x for x in range(2,n) if x == 2 or (x & 1 and not any(y for y in range(2,x) if not x%y)))

Я не про эффективность тут, а про читабельность, но…
даже если что-то на сите ...


# Example py.2:
def primes():
  '''Generator for primes via the sieve of eratosthenes'''
  mSE = {}
  x = 2
  while 1:
    if x not in mSE:
      yield x
      mSE[x*x] = [x]
    else:
      for y in mSE[x]:
        mSE.setdefault(y+x, []).append(y)
      del mSE[x]
    x += 1

Вторая причина: не все языки, на которых легко писать код, легко затем и читать.


Но зато "функциональщина" позволяет перекладывать многия математические формулы практически один в один, см. Функциональное программирование на языке Haskell, стр 109.


Ну и тут нас догоняет третья причина: как раз про эффективность — не многие представляют себе во что весь этот "сахар" и "соль" в результате развернутся, а хочется же понимать это сразу без утомительного переделывания/оптимизации потом (а мы помним иногда код плохо читается ;).


У традиционных императивных языков это, как правило, не так, ну или как минимум много очевиднее (и даже при наличии некоторой "магии").
Т.е. в примере py.1 выше сразу очевидно что критерий not any(y for y in range(2,x) if not x%y) абсолютно не эффективен (уже на относительно небольших числах).
По крайней мере пока python не научится оптимизировать суб-генераторы.

И сравните это по читабельности ну например с python:
И на то и на другое будут жаловаться. А вот заведите массивчик в BASIC и заполните его — и всё «станет ясно»…

Хотя, вроде как и Python и Haskel ближе к оригинальному математическому определению… и короче…

P.S. Тут как бы надо сказать что речь идёт о моём и, в то же время, не моём опыте. Об опыте помощи людям с программированием. Отсюда и такие странные выражения. Потому что вот мне лично вариант на Haskell кажется чуть ли не самым понятным и читаемым… но долгий опыт показал мне что я — скорее исключение, чем правило…

P.P.S. Не многие представляют себе во что весь этот «сахар» и «соль» в результате развернутся, а хочется же понимать это сразу без утомительного переделывания/оптимизации потом — это уже совсем другая история. Вопросы эффективности программы на Haskell — это отдельная песня, но для очень большого процента людей даже понять вот что вы там понаписали — проблема. Безотностительно к тому, во что оно потом транслируется…
… но долгий опыт показал мне что я — скорее исключение, чем правило…

Аналогично,… однако мне мой долгий опыт ещё подсказывает, что я видимо не в состоянии привить кому-либо что-нибудь функциональное (я тут не про азы).
Однако самостоятельно те же люди много позже смогли осилить ну например тот же haskell.
При том, что с императивными языками любой группы (включая скриптовые, типа питон) никогда особых проблем не наблюдалось (ну если человек в принципе обучаем программированию).

Интересно — почему так…

Да просто потому, что в математике это утверждение, информация, исходные данные для преобразований, а в программе — предписание для компьютера (разумеется, при посредничестве интерпретатора или компилятора)
И? Почему вдруг «предписание для компьютера» вам стало вдруг проще написать, чем простое описание «исходных данных для преобразований» — примерно как тут?
А когда я утверждал что-то подобное?
Потому что шаг алгоритма легче объять разумом, чем формулу/абстракцию целиком.
1. в школьной математике нет «переменных»
2. есть разные «на практике» — я знаю кучу людей, которым как раз таки сложно понимать «коробочки». Потому что они математики, а не программисты.
3. я получал традиционное IT образование, и мне функциональщина гораздо более понятна и естественна, чем императивщина, то есть опять же, не стоит думать, что если в твоём окружении что-то происходит, то это происходит везде и со всеми.
в школьной математике нет «переменных»

А что такое x, y и т.п. в курсе дифференциального исчисления?
В алгебраических доказательствах?
И вообще, слово «переменная» прямым текстом в учебниках есть.
Например, М.Я.Выгодский. Справочник по элементарной математике — М., 1966 г., глава VI:
параграф 1. «Постоянные и переменные величины»
параграф 2. «Функциональная зависимость между двумя переменными»

Тоже думаю, что проблема преувеличена, и иммутабельность здесь не при чем.

Проблема есть на самом деле...


Я вот как-то поправил одну ошибку в коде калибрования некоторого устройства, измеряющего радиационный фон:


- if ((R = NominalR) || (Rq <= EstimatedMinEnvR)) {
+ if ((R == NominalR) || (Rq <= EstimatedMinEnvR)) {
    R = (R + Rq)/2;
    return;
  }

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

Проблема высосана из мизинца левой ноги.
x = 2y + 1 в школе ни у кого не вызывало вопросов.
Не можешь запомнить нотацию языка, не программируй на нём.

Проблема, может, и невелика, но ваш комментарий, как раз, свидетельствует о её существовании.


x = 2y + 1 в школе ни у кого не вызывало вопросов.

Именно потому, что это равенство, и оно не обладает описанным в статье противоречием. Оно, например, обладает свойством коммутативности, в отличии от оператора присваивания.


Очевидно, что вы эту разницу не видите, а, значит, проблема есть.


А ваш пассаж в духе "А чего добился ты?", вообще, не самым удачным образом вас характеризует.

image
Проблем восприятия не вызывает, это формула расчета, но x = 2y+1 это оказывается равенство, а ни зависимость x от y.

Проблема действительно высосана из пальца.
а почему равенство не может быть частным случаем зависимости?

Формула расчета и есть равенство, никакого противоречия тут нет. Но равенство это не то же самое, что и присвоение.

Верно. Равенство (операция сравнения) не тоже самое, что присвоение. Но когда в физике/математике нужно «посчитать», то мы используем = как присвоение. Алгоритмы какраз «считают», поэтому = должно быть присвоением. = как равенство используется только в решении уравнений и преобразованиях формул, чем алгоритмы не занимаются.
Как вы не замечаете очевидной разницы между:
1. x = y + 1 в математике и
2. x = y + 1 в программировании?

Попробуйте изменить значение переменной y и все станет ясно. В математике вам еще придется «извращнуться» и ввести множества значений и элементы множества, чтобы «зафиксировать» состояние на тот или иной момент. В программировании итерационная модель состояний (последовательно сменная) уже в голове разработчика.

о_О У вас очень странное представление о том, что такое алгоритм.

Ничего мы в физике никуда не присваиваем. Там это такое же равенство как условие, не более того. Например, F = mg — закон тяготения. Где мы тут что-то чему-то присваиваем? А вычисление — это всего лишь редуцирование к простейшим уровнениям типа x = число или x in {числа}. И тут тоже нет никаких присвоений.

Верно. Вычисления в физике — это просто определение частного случая равенства на основе известных значений переменных. Никаких присваиваний.

Где мы тут что-то чему-то присваиваем?

В случае вычислений по данной формуле как раз-таки присваиваем.
Верно. Равенство (операция сравнения) не тоже самое, что присвоение. Но когда в физике/математике нужно «посчитать», то мы используем = как присвоение

откровенно говоря, в мат. контексте x = y + 1 "=" — это не «логическое равно», а объявление функциональной зависимости между x и y.
Всегда не любил паскаль именно за ":=" для присваивания. Просто потому что это дольше печатать, чем "=".
Конечно, зато в паскалье "=" — это сравнение, но как-то так получается, что в коде я на порядок чаще делаю присвоение.

Дольше печатать? ORLY? Теперь, когда вы пишете на каком-нибудь С#, печатать нужно меньше? Куда вы деваете освободившееся время?

На отладку ошибок этим вызванных </troll-mode>

Таки да, два символа печатать дольше, чем один.
Я не говорю, что суммарно в коде нужно печатать меньше… хотя благодаря современным IDE, пожалуй что и вправду меньше. Если я пишу на C#, то в основном нажимаю ctrl+space :)
НЛО прилетело и опубликовало эту надпись здесь

Минус у .equals(T o) ровно один — возможность NPE.
А всё остальное — специфика языка, где из-за одних компромиссов возникают некоторые минусы и некоторые плюсы. В частности запрет переопределения операторов — имхо скорее благо.

Равенство — это довольно размытое понятие (и для строк в особенности), чтобы уложить его в один оператор. Равны ли друг другу строки «Елка», «елка», «ёлка», u«ёлка» и U«ёлка»? Всё зависит от контекста — считаем ли бы эквивалентными строки, записанные одинаковыми символами, но в разной кодировке, или в разном регистре, или если символы одинаковые, но записаны разными codepoints, или символы разные, но для читателя оба написания эквивалентны (е/ё)? В одной и той же программе, но в разных местах, запросто может понадобиться разное поведение, и никакого поведения «по-умолчанию» может и не быть. А если вспомнить ещё и равенство ссылок vs равенство значений?

Так что я всегда с большой опаской гляжу на места в коде, где строки (или другие сложные структуры) сравниваются операторами "==", "<", ">", а не функциями вроде Еquals() или Compare(), или другими специализированными предикатами. Нереально впихнуть в простые операторы все мыслимые группы эквивалентности и способы упорядочивания — это годится только для простейших типов вроде enum-ов и чисел, и даже там оператор сравнения моментально ломается с переходом к плавающей точке.
НЛО прилетело и опубликовало эту надпись здесь
Я согласен, что это наиболее частый случай, может даже и подавляюще частый. Но случаев, когда тупое бинарное сравнение строк не подходит, но его бездумно используют ибо привыкли к встроенному — тоже очень много, и багов от этого я видел немало. Именно поэтому я не отрицаю оператор "==", а гляжу на него с подозрением: каждый раз, как он попадается, приходится делать паузу и думать — действительно ли его использовали как надо, или просто по привычке воткнули?
НЛО прилетело и опубликовало эту надпись здесь

В Java есть интерфейсы Comparable и Comparator. Именно они предназначены для функционального, подходящего к конкретной ситуации сравнения.
метод equals в среднем даёт понимание — является ли предложенный элемент эквивалентным текущему. Не равным, а эквивалентныым и это уже зависит только от класса и заложенного в него алгоритма эквивалентности.
Почему там не стали вешать этот метод на ==:


  • в Java сознательно отказались от перегрузки операторов. Решение может кому-то нравиться, кому-то не нравится, это банальная особенность языка. Но это не даёт возможности переопределения функции равенства
  • оператор == когда-то определил как оператор строгого равенства — равенства по ссылке. Он отвечает только на вопрос — тот же это объект или нет. Он никогда не отвечает на вопрос, похож ли этот объект на тот.
А на каком ЯП вы сейчас программируете? уверен что есть ЯП где надо печатать меньше
Malbolge.
This Malbolge program displays «Hello World!», with both words capitalized and exclamation mark at the end.
(=<`#9]~6ZY32Vx/4Rs+0No-&Jk)«Fh}|Bcy?`=*z]Kw%oG4UUS0/@-ejc(:'8dc

print("Hello World!")
Из современных языков — присваивание через ":" есть в maxmia, а через "<-" — в R

я в скале иногда определяю := для присвоения по значению. Поведение = там изменить нельзя — оно всегда по ссылке для ссылочных типов.
Ещё забавно: в go есть :=, причём там используется наоборот — для объявления переменной с присвоением значения, a = для присвоения!

Go

Особенно хорошо, когда пытаются сделать res, err := ..., а одна из переменных уже была объявлена..

Всегда любил Паскаль именно за ":=" для присваивания. Да, чуть больше писать, зато никакой неоднозначности. И куча времени и нервов сэкономлена на глупых ошибках, и не надо потом мучительно гадать, что имел в виду писатель: if (a = fn(x))... — то ли это ошибка, то ли автор ленив или эстет.

Справидливости ради, проблему с if (a = fn(x))... можно решить просто убрав возвращаемый результат у оператора присваивания.

Этак у нас какой-нибудь ABAP останется, если все прикольные и удобные, хотя и немного опасные штуки убрать.

Конкретно это штуку и так, не редко, убирают. В Python нет такой возможности, потому что присваивание это statement, а не expression (как раз, в комментирии ниже упомянули). В Swift, та же самая петрушка.

И в котлине тоже.

И в Бейсике — т.е. это очень давняя идея.
Удивительно, что она не прижилась в мейнстримовых языках, из-за чего даже в Swift и в Kotlin, где присваивание не может быть частью выражения — всё равно для равенства используют не знак равенства, а сишный костыль.

Тогда нельзя будет писать a = b = c, что иногда используется.


А проблему с if можно решить явным требованием типа bool у выражения.

При условии, что вы не пытаетесь присваивать значение типа bool.

Почему нельзя? Просто сделайте такую грамматику, что a = b = c — это не assign("a", assign("b", "c")), а assign(["a", "b"], "c"). В Python присваивание — это может и statement, но писать a = b = c всё равно можно:


>>> ast.dump(ast.parse("a = b = c"))
"Module(body=[Assign(targets=[Name(id='a', ctx=Store()), Name(id='b', ctx=Store())], value=Name(id='c', ctx=Load()))])"
Или потребовав явного слова на это, например

    if (take a = fn(x))


польза — такие случаи (нечастые, но полезные) будут явно выделяться в коде.
в питоне потому присваивание внутри if и запретили
while, for?
Конструкция вида while(r = next()) удобна и позволяет обойтись без дублирования кода.

В такой ситуации предполагается использовать for:


for r in iterable:
    pass

Здесь аналог next является частью контракта итератора.

в питоне нет классического for, только для итератора
про while уже написали

Переписывается в while(true) .. break. Да, на 1 строчку больше писать, но выгоды на мой взгляд намного больше.

While (true)… break — это костыль и изнасилование здравого смысла.

Да, в правильном сценарии у вас есть нормальный итератор, где вы просто пишете for value in mystream.filter_map(|x| x.read()) { ... }. Для меня изнасилованием является попытка присовокупить и еще что-то посчитать. Тот же Липперт считает это одним из главных косяков при проектировании C#. Также интересной информацией может быть тот факт, что во всех новых языках присваивание это таки statement.

НЛО прилетело и опубликовало эту надпись здесь

Да, конечно, и я тоже пользуюсь такой конструкцией. Но же просто замаскированный goto.

Он не плох сам по себе (тихо-тихо, положите помидоры и тапки).
В данном случае мы получаем более-менее структурированный goto, который не создает спагетти-кода (ну ладно, почти, одно перекрещивание).
На самом деле страсти вокруг goto — это карго-культ какой-то.

Проблемы с goto начинаются, когда вы начинаете использовать его для перезода «вверх» по коду. Пока все переходы ведут «вниз» — на читабельность это влияет относительно слабо.
Не обязательно строго «вниз», местами приходилось делать конструкции вида

if (плохо1) {
  cleanup: почистить;
  return;
}
...
if (плохо2) {
  goto cleanup;
}


С другой стороны, вход куда-то внутрь тела цикла даже ниже по ходу может быть жуткой диверсией.

Но тем не менее правила в следующем духе могут быть вполне приемлемыми для 99% случаев:
Допускаются переходы:
  1. Как реализация отсутствующих в C break, continue с меткой.
  2. В точку кода, из которой единственный выход — итоговый выход из функции. (Это для таких cleanup.)
  3. Вниз по коду, если происходит переход на тот же уровень вложенности или меньший.

обычно в таких случаях cleanup блок делают внизу функции:
void foo(...) {
    resource1 = ...
    if (плохо1)
        goto cleanup1;
    //...
    resource2 = ...
    if (плохо2)
        goto cleanup2;
    //...
cleanup2:
    (очистка ресурса 2);
cleanup1:
    (очистка ресурса 1);
}

такой early return не противоречит позитивному сценарию и в принципе используется достаточно часто.
На самом деле подобные cleanup легко переносятся в конец функции, где им, собственно, самое место, а пункт 1 является частным случаем пункта 3.
В Delphi обычная практика:

создали
try
 выполнили
finally
 разрушили
end;
гарантированно всё разрушится, безо всяких goto.

Только теперь вы уже не можете быть уверены в том, как будет исполняться программа. Наличие исключений — это как замаскированный (и ограниченный до «только вверх по стёку») longjump практически из любого места программы. Если есть checked exceptions то у вас хотя бы есть идея о том, что может быть выброшено. Но обычно их нет. Отлично понимаю авторов Rust и Go, у которых нет исключений в традиционном виде, а то что есть не предполагается для использования таким образом. Часто механизм удобен, но идея их использования в сложноотлаживаемых приложениях (у меня в основном embedded) мне не нравится.

Только теперь вы уже не можете быть уверены в том, как будет исполняться программа

Стоит расценивать исключения как один из вариантов исполнения программы, хоть и необычный. Все исключения можно достаточно хорошо запротоколировать и разобраться, что не так. Возможно где-то со временем.
не очень хорошие примеры вы привели. Обработку ошибок в Go не пинает только ленивый. А в rust best practice — функция возвращает Option/Result и использует макро try! для быстрого возврата ошибки. Грубо говоря, это более явная и менее удобная эмуляция тех самых исключений.

Сами по себе исключения — максимально лаконичный и гибкий инструмент. Не всегда уместный и неявный, и от того сильно недооцененный, на мой взгляд. Именно посредством исключений можно «прокидывать» максимум любой отладочной информации. Разве что в языках с поддержкой исключений я бы предпочел вариант «функция может кидать исключения только если это указано» вместо «функция может кидать исключения если не указано обратное».
Разве что в языках с поддержкой исключений я бы предпочел вариант «функция может кидать исключения только если это указано» вместо «функция может кидать исключения если не указано обратное».
Не работает. Либо у вас ошибки типа переполнения счётчика бросают исключения и их нужно обрабатывать, чтобы программа скомпилировалась, то есть на каждую полезную строчку у вас оброауется пять строк к обработкой ошибок (а вроде как мы и заводили-то исключения, чтобы от этого избавится), либо у вас получаются «исключительные исключения», которые вроде как бросаются — но не ловятся. Как только вы завели «исключительные исключения» — в них тут же начинают заворачивать всё подряд, так как согласно API ваша функция бросать ислючение типа «файл не читается» не может, а программу как-то скомпилировать всё-таки надо.

Обработка ошибок в Go и Rust'е, может быть, и не идеальна, но я не могу сказать, что то, что предлагает C#/Java наёжнее, увы.

Сами по себе исключения — максимально лаконичный и гибкий инструмент. Не всегда уместный и неявный, и от того сильно недооцененный, на мой взгляд.
То же самое можно сказать про GoTo. Слава богу в современных языках исключения ходят только в одну сторону, это не call/cc, но за счёт того, что это не просто GoTo, а нелокальный GoTo, переходящий из одного модуля в другой разрушения он создаёт те ещё…
Либо у вас ошибки типа переполнения счётчика…
… что предлагает C#/Java ...

я говорил скорее про исключения в с++, которые (по идее) являются не следствием ошибки программиста, а сигналом о некорректном состоянии программы/окружения для выполнения операции. А ошибки типа выхода за границы — просто UB.
То же самое можно сказать про GoTo

исключения могут быть вложенными. Типа «Измерение не удалось» потому что «Нет соединения с датчиком» потому что «не установлен драйвер ...».
исключения могут быть вложенными. Типа «Измерение не удалось» потому что «Нет соединения с датчиком» потому что «не установлен драйвер ...».
Прекрасно. Кто всё это будет раскручивать?

Если ответ «программист, когда увидит логи» — то это и без исключений делается, а если вы думаете что у вас программа измерения пульса будет со всем этим разбираться, то вы явно не видели кода этих программ… они и куда более простые ошибки не обрабатывают.

Rust не ограничен try!.. И с таким подходом я не могу не знать, есть ли у функции «исключения». В go могли бы сделать и лучше. Но, тем не менее, порядок исполнения там весьма ясен. Мне вполне нравится работать в Python, в некоторых случаях оставляя длинные traceback’и как сообщения об ошибках, а в некоторых ловя всё подряд и запихивая в лог. Но я никогда бы не выбрал ни [Micro]Python (ни C++) для embedded, даже если бы мог (если для конкретной ИС есть нужный компилятор) (хотя C++ далеко не за исключения, тем более что их можно отключить): прокидывание «максимума полезной информации» — это последняя вещь, что мне здесь нужна, потому что её ещё надо как‐то достать. А вот однозначный порядок исполнения (хотя и нарушаемый иногда прерываниями) весьма полезен. И Rust мне весьма понравился (хотя я делал на нём один‐единственный проект, только проект сразу был прошивкой для stm’ки).


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

По исключениям можно поспорить, но важно также то, что finally блок выполняется даже если ты нормальным образом выпрыгиваешь из его тела, даже не при исключении. Исключение можно ловить по типам отдельно.
гарантированно всё разрушится, безо всяких goto.
Исключения — это как раз и есть «goto на стероидах». Даже хуже longjump'а.

Да, с ними можно жить и даже писать достаточно надёжные программы, но я до сих пор не уверен, что их наличие упрощает, а не усложняет жизнь.
Все как всегда: есть задача, есть инструменты для нее. Беда начинается, когда инструмент превращается в догмат.
Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получается когда пытаешься писать на 2х языках с разным подходом к этому вопросу, т.к. постоянно путаешь — где какой вариант. Ну и как правильно кто-то заметил — присваивание обычно более частая операция, так что на неё логичнее иметь более короткий вариант.
Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получается
Проблема получается, когда забываешь написать второй символ "=". Очень частая и сложнонаходимая ошибка.

Именно в C компилятор такое часто находит, если вы не злоупотребляете скобками и придерживаетесь правила, что при компиляции не должны вылезать никакие предупреждения вообще. И есть много статических анализаторов, та же рекламируемая здесь PVS-Studio.

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

Да по-моему "в 2 раза чаще писать -> должно быть в 2 раза короче" — это бред полный, а не аргументация… Можно подумать, что пара тысяч сэкономленных символов сделает написание нетривиальной программы хотя бы на 0.01% быстрее…
На отладку 1 ошибки забытого = в == всё это "сэкономленное" время и уйдёт.

Просто раньше время было другое… Написание программы занимало много времени, и это считалось нормальным, чтобы программист держал программу в голове. Гораздо проблемнее всегда была проблема с местом, памятью и мегагерцами процессоров. Считалось, что нужно экономить память (и место) везде, где это в целом возможно.

Да, есть такое. Однако она решается относительно просто. В каком-то из компиляторов (gcc вроде) для использования = в if требовалось добавлять вторые скобки. Т.е. нельзя написать if(a=b), надо if((a=b)). Т.е. просто так опечататься уже не получится.
А с учётом системы возвращения последнего результата действий замена = и == ничего не даст. У меня были случаи, когда на автомате писал == вместо = и долго пытался понять, почему значение не меняется.
GCC не запрещает if(a=b), но предупреждает о возможной некорректности. И да, за такими предупреждениями надо следить.
Возможно просто привык уже считать это предупреждение ошибкой…

GCC и clang позволяют превратить предупреждения в ошибки компиляции. Хорошая идея так делать, как минимум, на CI сервере.

Это не всегда ошибка. Иногда это способ избежать дублирования кода, которое тоже часто ведёт к ошибкам.

Например?

if (a = fn(x))
Это исправили… в Java и некоторых других языках. Там такое выражение сработает исключительно если функция возвращает булево значение. А тот же C о булевом значении как самостоятельном явлении и не подозревает вроде до сих пор (раньше-то вообще не подозревал, а сейчас, если я правильно помню — всё равно считает числом)

Безусловно, остроту проблемы сняли, но до конца, как вы сами заметили, не решили. Речь же шла о том, что проблему можно было вообще не создавать.

if (a = fn(x)) ... — оочень плохой code-style.

Во избежание ошибок и неоднозначности можно поменять местами операнды внутри if:
if (fn(x) == a) ...

А ещё лучше не выпендриваться:
a = fn(x);
if (a == ...)


Бывают ситуации, когда в целях оптимизации намеренно пишут присваивание внутри while (или внутри if, который внутри while, или внутри if, который внутри while, который внутри for… нувыпонели), чтобы, например, выжать максимум пользы из каждого такта микроконтроллера, но такие исключения должны быть явно прокомментированы в коде/задокументированы с пометкой «НЕ ВЛЕЗАЙ! УБЬЁТ!».

Понты из разряда «глянь чо могу, хыы!» лучше не выносить за пределы личных проектов :)

Как бы, на микроконтроллерах не все могут себе позволить обработку ошибок с помощью C++ исключений. А обработка ошибок с помощью кодов возврата, как раз и приводит к конструкциями вида: if(EOF != (c = getchar())){.... Стандартная идиома языка C.

В ABAP "=" это и присваивание, и сравнение, а в новых версиях и инициализация. И ничего, вполне нормально воспринимается, без какого-либо неудобства, из контекста всегда однозначно понятно о чем речь.
Если в ЯП возникают проблемы вроде описанной в статье, то это либо запутанный синтаксис, либо "одаренный" программист

Автор не прав. Сначала было:
let a = 5
А потом упразднили let :)
Более того, автор допускает ошибку предпосылки:

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

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

касательно самого графического глифа "=" — следует рассматривать визуальную первичность восприятия выражений

в математике, из которой «наследуется смысл символа равно» — знак равенства по сути является не «сравнением», а «декларированием»

аналогом декларирования в функциональном программирования является именно декларирование, а в императивном — не сравнение, а присвоение.

так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»

есть и более короткое и логичное объяснение. то, что пишется чаще, должно писаться короче.

в импративном коде основа алгоритма — переменные, и гораздо чаще происходит присвоение, чем сравнение. соответственно короткий знак всегда будет побеждать и вытеснять громоздкое и неудобное в процессе эволюции кода
так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»

В бейсике и присваивание и сравнение делается через "=", и при этом их не спутать.

Правильнее сказать, что там контекст не спутать (особенно в диалектах с LET). А так это три разные операции: императивное присваивание, декларативное утверждение равенства, и сравнение.

Можно пример как это на бейсике?
let a = b = c

можно и спутать
Нет, нельзя. Эта конструкция допустима только и только если a — булево и первый знак равенства — присвоение, а второй — сравнение.
Другое дело, что человек мог описаться, но это ничем не лучше и не хуже путаницы в С типа if (a = b)

Мне кажется что я привык к = так же как и к десятичной системе счисления.

Кажется люди просто путают арифметический синтаксис и синтаксис языков программирования.


Это типа, как сравнивать английские буквы и латинские. Выглядят одинаково, но при этом правила использования абсолютно другие

И тем не менее языки программирования в той или иной форме используют обычные арифметические конструкции и конструкции из естественных языков.
Почему бы не использовать их правила и ограничения?
Для программирования же не придумали свой знак + и -.
= и == действительно так себе конструкция, от того, что к ней все привыкли, не делает ее логичнее.

Сейчас оператору := нашли идеальное применение в Go — для объявления переменной, совмещенной с инициализацией (и выводом типа). С точки зрения краткости это самое то. Но следующий шаг не сделали: можно было разрешить этот оператор внутри выражений:
``` x := (123 + (y:= a*b))


ИМХО это было бы весьма в сишном хакерском стиле :)

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

Задело про Лисп и переменные. Мы в институте учили Лисп как строго функциональный язык, есть (lambda ...) и восемь стартовых функций, и всё, и вертись в параболе, никаких переменных и присваиваний, никаких циклов, только (хвостовая по возможности) рекурсия — круть была неимоверная. А потом я увидел, как на Лиспе пишут как на сях.
Мы в институте учили Лисп как строго функциональный язык,

Но это не делает его строго функциональным. Он может реализовывать разные парадигмы. Тем наверное и ценен.

никаких переменных и присваиваний, никаких циклов, только (хвостовая по возможности) рекурсия

При том, что в Common Lisp, например, нет tail call optimization (из-за динамических областей видимости, насколько я понимаю). Без циклов на нём никак.

Ну, студенческие проекты обычно не выбирали даже той кучи, которая была доступна в mulisp (один 16-битный сегмент), разве что автор затеял в какой-то функции в одном выражении дважды вызывать её саму. Да и честно, о хвостовой рекурсии я узнал только спустя несколько лет после окончания института. Так что только рекурсия, только хардкор. И ведь решали!
Программист на Фортране может написать программу на Фортране на любом языке программирования
Можно объяснить, в чем в той цитате юмор?
:= на череп похож

Мне кажется, с x = x + 1 проблема в том, что математика не рассматривает понятие "состояние". Любая динамика раскладывается по оси времени в статичный график, и все утверждения делаются относительно статичных моментов или их последовательности. Понятно, что в один и тот же момент x не может быть равно x + 1. Ближе всего понятие суммы по i от 1 до n, но в математике не рассматривают детали реализации, как i принимает свои значения.

ещё как рассматривается.
есть куча методов основанных на итерациях и т.д.
В «методах, основанных на итерациях», как правило, заводится целый выводок переменных, но каждая из них имеет только одно какое-то значение…
Напишите математическим языком, как на некоторой итерации вычислить следующее значение i.
в формулах k = 1..N например.
но чаще словами пишут.
в методах ньютона для диффуров все вполне себе понятно написано
Так это не состояние, это список возможных значений. А как k их принимает, как одно значение меняется на другое, это не рассматривается.
вот, например, в методе гауса есть состояние.
там даже полноценные циклы :)
Я боюсь вы плохо помните как это описывается в математике. Посмотрите хотя бы в Википедию. Да, шагов там много, но все переменные получают значение один раз.

Вот все эти "а с крышечкой", "a со штрихом", "a с кришечкой и n штрихами" — они как раз оттуда.

Максимум, что бывает в математике — это пролог-переменные, которые могут в одной «ветке рассуждений» получать значение, но при обнаружении противоречий — терять его.

Причём в отличие от пролога, где при необнаружении противоерчия переменные могут-таки получить серию «решений» с разными значениями в момент получения неопределённости в математике возникают все эти штрихи и x1, x2, etc.
ну да. в математике выглядит иначе.
чаще в стиле:
на следующем шаге примем S равным f(x_n) ну и в таком духе
Всем, кто говорит, что в математике "=" означает равенство «навсегда», вот вам простой пример:

Пусть X = 2, Y = 2. Если X + Y = 5, то 2 + 2 = 5, что неверно. Из этого следует, что X+Y ≠ 5.

Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?
2 + 2 = 5
А что тогда "=" означает здесь?
Предположение. Если мы считаем, что X+Y=5, то оказывается, что 2+2=5, а второе утверждение ложное, значит и первое утверждение так же ложное.

Два равно, и оба ложные, и вселенная всё ещё не взорвалась (повторно).
Предположение
Тогда X = 2 это тоже предположение, т.е. уже не верно что X + Y = 5 и 2 + 2 = 5 это одно и тоже
Окей. Но если мы становимся на эту позицию, то что остаётся от математики?

Если X=2, и Y=2, то X+Y=4. Эти знаки равно что обозначают?
Равенство.
Если мы знаем что X = 2, Y = 2, то X + Y = 5 это утверждение не истинно, также как не истинно и 4=5.
Почему не валидно? Есть куча доказательств от противного, и в них всегда приходят к противоречию, это означает, что часть утверждений (со знаком равно) не являются истинными.
часть утверждений (со знаком равно) не являются истинными
это я и имел ввиду :)
Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?
Вы уж скажите что вы тут понимаете под «странным» смыслом.

Как я уже писал «математическое» присваивание у нас есть только в Prolog'е и близких к нему языках. И вот как раз на нём мало кто пишет — даже из тех, кто, вроде как, предпочитает «функциональщину».
Ни одного. Сделали неверную предпосылку, только и всего.
Ни разу. «Пусть X = 2» — это не присваивание, это логическое утверждение «считаем истинным, что X = 2». Проверка: поменяем все операнды местами:

Пусть 2 = X, и 2 = Y. Если 5 = X + Y, то 5 = 2 + 2, что неверно. Из этого следует, что 5 ≠ X+Y.

Хоть и запись непривычна, смысл выражения и вывод не поменялись. С присваиванием такое бы не прошло.

Ой, сразу вспоминается задачка (из реального пособия для начальных классов), отменяющая привычное "от перестановки мест… не меняется"


Был ошарашен в своё время

image


… а вы про синтаксис в языках программирования...

Справедливости ради, будучи записанным словами, выражения "дважды пять" и "пятью два" отличаются не только синтаксически, но и концептуально, так что некая логика в этой рекомендации есть. Правда в описанной задаче правильно было бы записать 5*2 ("пятью два", "пять раз по два", "five times two", пять пар чашек), т.е. ровно противоположное тому, что написал автор методички. Безразмерный масштабный коэффициент принято ставить перед размерным, например 0.1C или 2πr. Численно результат, конечно, не меняется, но вот такая конвенция действительно есть, и даже отражена в языке.

НЛО прилетело и опубликовало эту надпись здесь

Автор методички всё же не до конца прав — как по мне, более корректной трактовкой было бы "2 куска/чашку" (дозировка на порцию) и "5 чашек" (количество порций) — в таком случае мы имеем вполне разумное решение с сокращением размерности порций, при любом порядке перемножения. И решаем в удобном для себя порядке — в зависимости, от чего отталкиваемся (дозировки или количества порций).

НЛО прилетело и опубликовало эту надпись здесь
То же самое. Вместо объяснения самой концепции (размерностей, хотя бы в сокращенном виде — что есть безразмерный коэффициент и есть величина с размерностью) ради якобы того, чтобы потом не пришлось переучиваться, вводят суррогат, который иначе, как странным спорным, назвать нельзя. Никто не мешал ввести размерность у одного из множителей и сохранить при этом коммутативность (да, деление изучают позже, но и подобные задачи можно ввести с учетом размерности после изучения обеих операций). Или просто отложить ввод размерности результата на потом, когда обе операции — деление и умножение — изучены, как это и делалось в наше время.
Не дай бог моих детей так учить будут…
ИМХО если ограничиться исходным кодом для компилятора, то обозначение присваивания дело привычки. А вот как быть, если нужно написать статью (нпр., на Хабр) с описанием алгоритма, мат.доказательством его корректности и теоретической оценкой сложности для наихудшего случая? В математике знак "=" не присваивание, а равенство. И тут возможна путаница между псевдокодом описания алгоритма, листингом его реализации на конкретном ЯП и мат. формулами. Кроме того, даже в исходном коде для компилятора бывает нужно добавить комментарий с мат.формулой. Конечно, опытный программист всегда по контексту поймет, что в данном месте подразумевается под "=". А вот ученик не всегда. Поэтому ИМХО для присваивания удобнее использовать символ/комбинацию символов, отличный от "=".
> В математике знак "=" не присваивание, а равенство.

В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть» («обозначим» и т.п.), и ещё надо уточнять по контексту, какого из знаков — x или y — ещё не было). В том и дело, что в математике роль конкретного '=' определяется окружающими словами.

И даже в конструкциях вида «x=x+1» надо отличать, а не идёт ли тут поиск того x, при котором это справедливо (в обычной арифметике — бесконечность любого вида, в булевской — 1, и ещё есть вариантов).
В предложениях типа «пусть y = 2x» это именно присвоение
Если это присвоение то «x» уже должен быть определён, а мы можем определить его позже
> Если это присвоение то «x» уже должен быть определён

Нет, без контекста нельзя уточнить, что определено — x или y. Один определён, второй определяется через него и получает значение. Оба направления присвоения активно используются.

(Кстати, именно под влиянием языков программирования я начал в своих конспектах в подобных случаях явно писать направление назначения — в данном случае x := 2y или x =: 2y. Очень помогало потом разбирать.)

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

> Программирование описывает перемещение (копирование) значений из одних участков памяти в другие.

Вы знаете только процедурные языки, не так ли?
Возьмём Erlang:

{ok, V} = getV()

Что происходит с V? Это присвоение? В каком-то смысле да, но в терминах языка это matching, при котором происходит binding (намеренно тут не перевожу термины). Такой же, как в случае математики: «пусть x=2y» это binding. Только разница в том, что в Erlang может происходить binding только слева от '=', а в математике — с любой стороны (и очень часто пользуются этим, если x уже известен, а y только вводится).

А при втором таком же {ok, V} = getV(), если V уже связано (bound), matching может привести к успеху или ошибке. Вот это вариант вполне «математический» в вашем понимании. (Но и он — только в области жизни этого V! Это существенно, «за углом» будет совсем другое V.)

> В математике ничего никуда не копируется и не перемещается. Все равенства истинны или ложны одновременно в любом порядке их чтения.

Это грубо неверно.

Все равенства это только рабочий материал для логики изложения. А логика имеет направление. Любое «из A следует B» прямо означает, что у нас есть A, и вследствие этого мы имеем B. Мы не имеем права развернуть её и считать, что если B, то A.
Если мы имеем равенство a = b, мы можем написать a² = b². Но если у нас a² = b², мы должны ещё доказать правомочность перехода к a = b. Иначе см. школьное доказательство, что 2*2 == 5.

Точно так же, если мы ввели x = α*y, где x было до того, а y вводится, то
  1. Это может быть такое y, которое отличается от y в другой части.
  2. Мы должны иметь право умножить на α для обратного перехода (y->x) и разделить — для прямого (x->y). Это или доказывается, или считается очевидным, но в любом случае может быть подвергнуто сомнению. На незаконности таких переходов горят >90% самодеятельных доказательств великих теорем, потому что их очень тяжело отслеживать.


И вот при таком доказанном введении (y в моём примере) появляется новая сущность. Она не была раньше, как вы утверждаете — она возникла в ходе изложения. Она может быть вре́менной (на одну часть изложения), пространственно локальной или условийно локальной (например, при x > 1).

И вот для того, чтобы сделать изложение корректным и читаемым, и надо вводить метки типа «этот знак появляется (вот тут) и теряет смысл (вот тут)». Когда я пишу «x = 2y», этой метки нет. Когда я пишу «x =: 2y», я ввожу новое y (и отменяю старое, если было). И с этим реально удобнее. Конечно, не все случаи этим можно покрыть (например, я могу сделать x = 2y + 3z, где отношение между y и z оформляется отдельным уравнением), но всё равно уже значительно проще.
Я так понимаю что человек намеренно не хочет рассматривать разные немейнстриймовские языки. Hint: у Пролога binding и matching — почти один-в-один как в математике.

Пока до отсечений не дойдёте и не обнаружите что спрятать тот факт, что CPU — всё-таки императивная штукенция не удаётся ни на одном языке.
В математике: «имеет значение».
В программировании: «присвоить значение».
Имеет значение, вычисляемое как… (а как она его получила, неважно).
Присвоить значение, вычисленное как… (в результате чего станет верным выражение «имеет значение»).
В предложениях типа «пусть y = 2x» это именно присвоение


ИМХО нет!: «пусть y = 2x» можно перевести в слова (что часто и делают, нпр., на лекциях и читая доклады): пусть y равен 2x. Попробуйте перевести «пусть y присвоить 2x» — как-то не по-русски получается ;)
В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть»

Не согласен. Присвоение некоммутативно, в отличие от равенства. Утверждение типа "пусть" или "предположим" накладывает ограничение на x и y. Если что-то из них задано, то ограничение позволяет вычислить вторую переменную.
Если же неизвестно ни одного ни другого, то утверждение вида "… = ..." всё равно имеет смысл, в отличие от присваивания.


Вот реальный пример: надо найти массу по весу тела.
Я напишу: "по закону Ньютона P = mg, поэтому m = P/g" Присвоением можно разве что вторую формулу назвать (но я её всё равно считаю частным случаем равенства)

Если же неизвестно ни одного ни другого, то утверждение вида "… = ..." всё равно имеет смысл,


Да, равенство с несколькими неизвестными описывает некое множество точек в пространстве с размерностью, равной количеству переменных.
И кстати, зафиксировав значения нескольких переменных кроме одной — значение оставшейся не всегда определяется однозначно (вспоминаем квадратные уравнения, например). Вот только не помню, может ли полученное множество решений уравнения быть бесконечным, если уравнение не вырожденное (содержит все переменные). Мне такого примера в голову не приходит.
может ли полученное множество решений уравнения быть бесконечным, если уравнение не вырожденное (содержит все переменные)

Для двух переменных: (x,y)=(x,y). Вообще, любые тождественные выражения.
Другой вариант для двух переменных: sin(x)+sin(y)=(1/x)+(1/y)
Ну, тождества не выделяют подмножество, а заметают плоскость целиком.

И если говорить о решениях — для двух неизвестных переменных надо два уравнения задавать, причём не тождественных друг другу и не включающих друг друга (описывающих разные подмножества). Если ограничиться одним уравнением, то и банальное y=x уже бесконечное несчетное множество.

И я же писал:
зафиксировав значения нескольких переменных кроме одной
— то есть про решение уравнения с одним неизвестным (для остальных переменных значение задано). касательно того же квадратного уравнения — по сути, мы имеем дело с функцией, описывающей на плоскости параболу y = A*x2 + B*x + С0.
Говоря же о решении этого уравнения — мы задаемся неким значением y = C1, и приводим уравнение к каноническому виду A*x2 + B*x + С = 0, где С = С0 — С1.

Ок, я осознал что для тригонометрических функций множество решений может быть и бесконечным (да и для банального «y=sin(x), y=0,5»). И мой вопрос несколько сдвигается — может ли оно быть несчетным?

Использовали бы запятую, как в нормальных языках:
mov ax, dx

Толсто, movw %dx, %ax

ИП0 ИП1 + П0
БЗ-34? В моем 61 это (П->X)0, (П->X)1, +, (X->П)0
Ага, Б3-34 — у МК (54, 56, 52, 61) нотация отличалась, поскольку производитель другой (киевский «Кристалл» vs светловодский «Калькулятор»).
Для записи же на бумаге часто использовалась смешанная нотация — смотря какое обозначение короче и проще записывать (для ИП / П->X выигрывает светловодская, а для arcsin / sin-1 — киевская).

После этих калькуляторов ассемблер — плёвое дело.
Есть еще одна аналогичная холиварная тема — с чего начинать индексацию в массиве с 0 или 1. Вот страница википедии, где можно увидеть ЯП, у которых начинается с 1. И если в С/С++ это естественно из-за адресной арифметки, то в Java/Javascript/C# это было сделано из соображений «похожести» синтаксиса на С++. То есть это просто историческое наследие, а ведь могло бы быть и так:
string[] students = new string[3];

students[1] = "John";
students[2] = "Bob";
students[3] = "Alex";

Console.WriteLine("Third student is " + students[3]);

Вообще‐то «под капотом» у Java/JavaScript/C# тоже адресная арифметика. И, как минимум, первые реализации языков никогда не пишутся на самом языке — скорее на чём‐то вроде C, с индексацией с 0. Не вижу ничего странного в том, что во многих языках решили сэкономить на декременте, тем более что в списке «кто заплатит за декремент индексов» процессор далеко не на первом месте. Ещё, к примеру, в lua индексы как бы начинаются с 1. Но в luajit есть модуль ffi для работы с динамическими библиотеками и там они с нуля. А при написании модулей для lua с использованием C API у вас неизбежно часть кода будет иметь 0 первым индексом, а часть — 1. Учитывая, что существует только один широко поддерживаемый ffi (точнее, один на двойку процессор—ОС), и он происходит от C, а без ffi язык не может быть чем‐то большим, чем DSL, я не вижу ничего удивительного в том, что индексацию начинают с 0.

И если в С/С++ это естественно из-за адресной арифметки, то в Java/Javascript/C# это было сделано из соображений «похожести» синтаксиса на С++. То есть это просто историческое наследие, а ведь могло бы быть и так:

Начинал в школе с паскаля (и изначально привык к индексации с 1), но мне кажется, что индексация с нуля намного удобнее. Иначе вылезают проблемы с тем, что при умножении индекса на число первый индекс становится уже не первым.
Например, если хранить "двухмерные" данные в одномерном массиве:
arr[y * width + x] vs arr[(y-1) * width + x]
Или пропустить каждый второй элемент:
arr[2*i] vs arr[2*i-1]
А если полезть в кастование указателей, это же вообще жесть получится. Допустим, есть массив int32_t, чтобы прочитать первый байт числа ints[i] как у массива byte, нам придётся залезть по индексу bytes[(i-1)*4 + 1] = bytes[4*i - 3].


Какая-то логика в этом есть — остальные байтики будут лежать по адресам 4*i-2,4*i-1 и 4*i, но вычитать из индекса чиселки просто ради того, чтобы начало осталось началом — неудобно.
В выскороуровневых языках уже не важно, там берётся какой-нибудь foreach, который сам бегает по любым коллекциям.

Я считаю, что типичная работа с массивами и их индексами (>97% случаев) в языках типа Java/Javascript/C# не обладает большим удобством из-за выбора 0, а не 1. В будущем будут создаваться новые высокороуровневые языки программирования. И одним из основных аргументов в пользу 1 будет именно большее удобство изучения программирования с нуля школьниками (студентами), у которых еще нет привычки считать индекс с 0.
Если эти самые новые языки не будут предназначены непосредственно для изучения школьниками, то аргумент «удобство изучения с нуля непрофессионалами» как-то слабоват. Компромиссов в дизайне языков и так слишком много, чтобы добавлять лишние ограничения, которые нужно учитывать (местами за счёт ухудшения других важных параметров).
1. вы считаете так, а подавляющее большинство программистов иначе. Как минимум наглядно видно что яп с индексацией с 0 в среднем многократно популярнее.
2. у школьников и студентов в принципе нет привычки считать индекс, хоть с нуля хоть с единицы. В математике они не работают с явной индексацией совсем, лишь с диапазонамии i-ми элементами.
3. вам наглядно продемонстрировали почему индексация с 0 приводит к более простому коду в любом нетривиальном случае.
4. индексация с 1 требует постоянных пересчетов в рантайме — небольшой, но заметный удар по производительности
вам наглядно продемонстрировали почему индексация с 0 приводит к более простому коду в любом нетривиальном случае.
Как получить последний элемент? с индексацией с 1 это будет
array[length]

индексация с 1 требует постоянных пересчетов в рантайме — небольшой, но заметный удар по производительности
не требует, надо хранить указатель на начало — 1
Как получить последний элемент? с индексацией с 1 это будет
array[length]

array[-1]. Работает во многих высокоуровневых языках, там где не работает пишется православное array[length - 1]. Кстати, если вы хотите ввести отрицательные индексы в язык с индексацией с единицы, то вам придётся их объяснять как array[length + i + 1], а не как array[length + i]. И при этом вы получите непонятную дырку в числовой прямой: при индексах с нуля у вас непрерывный диапазон целых чисел [-length; length), за пределами будет IndexError. А с единицей у вас дырка в нуле, потому что возвращение последнего значения по нулевому индексу гарантированно принесёт очень много ошибок от программистов, которые переходят с других языков.


не требует, надо хранить указатель на начало — 1

Этого я не понял. Чем указатель на начало поможет? Что бы вы не делали, если под массив array выделена память по адресу a, то первый элемент будет по адресу a + 0. А программист‐то запрашивает array[1]. Правда, будет ли какой‐либо удар по производительности зависит от целевого процессора, окружающего кода, способности компилятора к оптимизации, …

не требует, надо хранить указатель на начало — 1
Этого я не понял. Чем указатель на начало поможет?
Это не вы не поняли. Это парсер не понял. Имелось в виду «хранить указатель на начало-1». Если «внутри» в регистре (или в соответствующем поле структуры) у вас в качестве указателя на массив хранится не указатель на его первый элемент, а указатель на его [несуществующий] нулевой элемент, то в рантайме никаких дополнительных пересчётов не будет.

Это тоже не всегда поможет. Если вы работаете с неизменяемыми структурами (кортеж или в вашем ЯП просто есть такой тип как «неизменяемый список»), то под них более чем логично выделять память одним куском вида struct { size_t size; Type elements[]; }. И вот здесь вы либо выделяете больше памяти, чем нужно, либо делаете декремент.


В принципе, чем больше вы будете думать об оптимизации ВМ для вашего языка, тем меньше вам будет хотеться сделать индексы, начинающиеся с единицы. Добавьте к этому то, что на начальном этапе у вас будут только программисты, пришедшие с других языков, а из популярных такие индексы только у Mathematic/MATLAB/Wolfram, lua и R, получите, что если вы не хотите откусить кусок от gamedev или различного рода научных вычислений, то вашей идее будут сопротивляться и вы либо‐таки сделаете индексы с нуля, либо получите отток программистов, недовольных своими частыми ошибками из‐за использования более привычных индексов.


(И, кстати, чтобы хранить указатель на “начало−1” вам всё равно придётся сделать как минимум две дополнительные операции: при сохранении указателя в структуре и при освобождении. Хотя, конечно, в это время вас гораздо больше будет волновать проблема «как бы делать обе операции пореже», т.к. работа с кучей — это долго.)

Если вы работаете с неизменяемыми структурами (кортеж или в вашем ЯП просто есть такой тип как «неизменяемый список»), то под них более чем логично выделять память одним куском вида struct { size_t size; Type elements[]; }. И вот здесь вы либо выделяете больше памяти, чем нужно, либо делаете декремент.
Ничего не понял. Если у вас подобная структура, то как раз логично иметь индексы с 1, а нулевой элемент будет содержать размер если размеры Type — такие же, как у size_t (здравствуй MacOS и Turbo Pascal!), а если нет — то всё равно нужен пересчёт, что для индексов с нуля, что для индексов с единицы.

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

«Индексы с нуля» — это вопрос удобства, но никак не скорости: на практике почти всегда скорость одинакова, хотя в редких случаях вариант с индексацией от единицы будет выигравывать (здравствуй MacOS и Turbo Pascal ещё раз!).

Ничего не поняли, потому что я много пишу на C — обращение к элементам что со структурой struct { Type *elements; } data;, что со структурой struct { Type elements[]; } data; будет выглядеть как data.elements[i]. Вы правы, тут будет пересчёт, но C это успешно скрывает.


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


С тем, что с современными десктопными системами пересчёт индекса будет незаметен я согласен. Но в нишу C вы не пролезете. И, главное, не только пользователи будут допускать ошибки с непривычки. Но и у вас будут ошибки — как я говорил выше, первая реализация языка не пишется на самом языке, а мало что из списка ЯП с первым индексом‐единицей подходит для создания нового языка.

Ничего не поняли, потому что я много пишу на C — обращение к элементам что со структурой struct { Type *elements; } data;, что со структурой struct { Type elements[]; } data; будет выглядеть как data.elements[i].
Да — но это только потому что «массив» и «указатель на массив» в C — это один тип. Если в языке это разные типы, то в случае с указателем ничто не мешает хранить «смещённый» указатель, а в случае со структурой… тут да — если массив это первый элемент структуры, то будет выигрыш… но так ли он часто встречается на практике?

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

И, кстати, все ли аллокаторы хранят служебные данные перед выделенной памятью?
А где ещё? Можно как в классическом Pascal'е — передавать в деаллокатор размер обьекта (по большому счёту это всё, что нужно деаллокатору). Но в большинстве языков испольщются данные перед выделенным участком памяти…

С тем, что с современными десктопными системами пересчёт индекса будет незаметен я согласен. Но в нишу C вы не пролезете.
Не из-за проблем с индексами, уверяю вас. MacOS до MacOS X — это Pascal и индексы с 1. И ничего, в определённые моменты до 40% рынка лаптопов на рынке США были PowerBookами… пока их Dell и китайцы не вытеснили…

Но и у вас будут ошибки — как я говорил выше, первая реализация языка не пишется на самом языке, а мало что из списка ЯП с первым индексом‐единицей подходит для создания нового языка.
Вы очень сильно преувеличиваете сложность написания компилятора нового языка, уверяю ваc. Вот хороший оптимизирующий компилятор написать — это не один десяток человеко-лет. А просто «какой-нибудь»… нет, это задачка на пару месяцев скорее.
А где ещё?

В отдельном списке — чтобы минимизировать вред от heap buffer overrun. Во времена Паскаля это не было актуально, но уже да :)

Кстати, интересный факт: в zsh есть настройка (KSH_ARRAYS), позволяющая использовать либо 0, либо 1 в качестве первого индекса. Конечно, как можно легко предположить из названия, предназначена для режима эмуляции ksh.

array[-1]. Работает во многих высокоуровневых языках, там где не работает пишется православное array[length — 1].

Кстати, я подумал, что отрицательные индексы могут считаться без оверхеда только если они константные. Т.е. для них компилятор будет считать length + i, где i — такой отрицательный индекс. Для все остальных случаев компилятор не будет ничего дополнительно рассчитывать (положительная константа или неопределенное значение).

array[-1]
Отрицательные индексы могут быть источником багов

Каких например? В данном случае компилятор преобразует это выражение в array[length - 1]. А с выражением array[ind], где ind приходит извне компилятор не будет производить никаких трансформаций. Это просто укорочение записи для отрицательных индексов.

Каких например?
При вычислении индекса мы получили -1, но не хотели получить последний элемент, так мы получим исключение, а с -1 можем и не узнать что приложение работает неправельно
Вы внимательно прочитали что я написал? Я писал что это должно работать только для константных индексов, а с ними все прозрачно.

Я такого, кстати, не предлагал. Компиляторы сейчас умные, могут часто что‐то вывести. Rust вот вставил проверку на выход за пределы массива, с расчётом на то, что компилятор во многих случаях её обратно уберёт. (Оверхед на декремент на фоне этого выглядит такой мелочью…)


Другим безопасным вариантом отрицательной индексации тогда будет использование разных типов для положительных и отрицательных индексов (предполагая статически типизированный язык, в динамически типизированных предложение имеет мало смысла). Т.е., к примеру, у вас положительные индексы типа usize, отрицательные типа isize и всё остальное индексом быть не может. Константам компилятор пропишет правильный тип автоматически, но никаких (в Rust) автоматических приведений типа нет.

Rust вот вставил проверку на выход за пределы массива, с расчётом на то, что компилятор во многих случаях её обратно уберёт.
Pascal имел эти проверки полвека назад. И был весьма популярен пока Вирт не понял, что времена «изменились» (да, я до сих пор считаю, что главная проблема всей этой истории с цепочкой Паскаль/Модула-2/Оберон — это потеря разработчиков на каждой стадии… у разработчиков C/C++ хватило ума устрановиться и не заставлять всё переписывать с нуля три раза после ~1980го года).

Гм. Я сейчас посмотрел на то, где эти проверки есть. Получилось, что


  • Их нет в C, C++ или если вы решили написать на ассемблере (или ещё на каком языке, где массивов попросту нет).
  • В D можно приказать компилятору их убрать.
  • В Ada можно посоветовать компилятору их убрать. Правда, он не обязан вас слушать.
  • В Rust их нельзя убрать. Если очень хочется, то можно написать unsafe код с функцией доступа к массиву по индексу без проверки.

Собственно, я думал будет больше языков без проверок для системного программирования. Оказывается, нет. Все делают проверки.

В том паскале, который Delphi, проверки можно отключить, как в свойствах проекта, так и директивой в коде.
Точнее: можно включить. По умолчанию отключены.
а действительно — проверил в BDS2006, в новом проекте галочка «Range checking» снята.
Давно не пользовался созданием проекта «с нуля», забыл уже. Всё с шаблонных заготовок с настройками под себя.
А с единицей у вас дырка в нуле, потому что возвращение последнего значения по нулевому индексу гарантированно принесёт очень много ошибок от программистов, которые переходят с других языков.

А можно по a[0] обращаться к длине массива, как в Паскале со строками ;)
И получается очень приятная симметрия: a[n] — n-ый элемент с начала, a[-n] — n-ый элемент с конца.

Это плюс одно сравнение, если n не константа, и, что намного важнее, тип выражения a[0] для массива [T] внезапно становится usize | T вместо просто T. Это абсолютно никому не нужно, даже выдавать «значение по‐умолчанию» как делает LabVIEW со всеми выходами за границы массива и то лучше.


Поправьте меня если я неправ, но зная про Pascal и его строки только разные слухи я могу предположить, что


  1. Это относится только к строкам.
  2. Если в Pascal есть тип строки, позволяющий строки длиннее 255 байт, то у этого типа длину строки таким образом получить не получится.

В классическом паскале длина строки вроде действительно до 255 байт, в делфях, насколько помню — 65к была (и кодировка — двухбайтовая). Но могу ошибиться — они для первого элемента и все 4 байта выделить могли.

  1. В Паскале — да, потому что для массивов обе границы задаются явно, и могут быть произвольными: хоть 0..10, хоть -5..15, хоть 'a'..'z'
  2. webkumo прав: для «широких строк» можно было бы хранить двухбайтную длину в нулевом элементе, но для Borland совместимость с OLE/COM оказалась важнее совместимости с Паскалем, так что WideString в Delphi — это BSTR
  3. Для динамически типизированных языков расширение типа выражения не представляет проблемы.

Для динамически типизированных языков расширение типа не представляет только технической проблемы. Вам всё ещё нужно понять, как это отразится на реальных программах: будет ли при этом легче/сложнее программисту допустить ошибку, будет ли легче/сложнее понимать программу, …


Расширение типа на usize | T вместо чего‐то вроде бросания исключения или завершения программы означает, что если программист ошибочно пропустил 0 в индекс, то ошибка обнаружится не в месте возникновения, а там, где вы попытаетесь использовать usize как T и не сможете. Более того, большинство новичков будут с непривычки читать array[0] как получение первого элемента. Хуже пожалуй только LabVIEW’шная выдача значения по‐умолчанию при выходе за границы: тут, скорее всего, при ошибке программа просто станет работать в режиме «мусор на входе — мусор на выходе», а вы будете долго думать, откуда на входе появился мусор.

4. индексация с 1 требует постоянных пересчетов в рантайме — небольшой, но заметный удар по производительности

Чепуха же — просто базой для индексации служит «минус первый» элемент перед началом массива. Пересчёты в рантайме — только при кастах указателей в массивы и обратно.
Например, если хранить «двухмерные» данные в одномерном массиве:

Но зачем, если язык позволяет иметь двумерные массивы?

Или пропустить каждый второй элемент:
arr[2*i] vs arr[2*i-1]

В вашем языке arr[2*i] выбирает все нечётные элементы, в моём — все чётные элементы. Чем ваш лучше чем мой?

Тут уже дело вкуса, но при индексации с нуля и каких-то вычислениях на основе других индексов достаточно использовать только сложение, без вычитания.


arr[i * 3] // каждый третий элемент, начиная с первого.
arr[i * 3 + 1] 
arr[i * 3 + 2] // сдвиг только добавляется

в случае с индексами с единицы придётся использовать вычитание либо переводить индексы в нулевые и обратно:


arr[i * 3 - 2]
arr[(i - 1) * 3 + 1]

Если все индексы и константы >=0, то при использовании операций сложения и умножения результат тоже будет неотрицательным и достаточно проследить за тем, чтобы не превысить верхнюю грань — размер массива.
В случае индексации с единицы приходится использовать вычитание и дополнительно следить, чтобы вычисленный индекс был >= 1.


Но зачем, если язык позволяет иметь двумерные массивы?

Из-за ограничений языка или требований к производительности или простоте кода может быть по-разному. Что-нибудь тривиальное типа коэффициентов матриц 2*2 может быть проще хранить в массиве из четырёх чисел, чем использовать абстракции.


P.S. Рассуждения носят скорее теоретический интерес, понятно что можно и так и так.

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

— if ((R = NominalR) || (Rq <= EstimatedMinEnvR)) {
+ if ((R == NominalR) || (Rq <= EstimatedMinEnvR)) {
R = (R + Rq)/2;
return;
}

Это особенности С, а не = как присваивания. Посмотрите на любой язык где = это statement и все эти проблемы уйдут. Особенно в языках типа Rust, где эстеты могут пользоваться буквенным обозначением вместо оператора.

Но ведь


since the assignment operator (=) has no backing trait, there is no way of overloading its semantics.

А ещё присваивание там не statement, просто оно возвращает (), а не присвоенное значение. Playground

«Abraham begat Isaac; and Isaac begat Jacob; and Jacob begat Judas and his brothers».
В тексте статьи есть библейская отсылка, и, может быть, стоило переводить её именно так, как переводят Библию: «Авраам родил Исаака; Исаак родил Иакова; Иаков родил Иуду и братьев его». То есть «CPL родил BCPL», «BCPL родил B», «B родил C».
Это так, в порядке занудства :). Спасибо за перевод!
Что-то, как-то, с момента «В начале 1960-ых… FORTRAN II» и инициализатора «=» в Фортран, вера к автору исчезла. В самом деле, в момент появления языка C в начале 70-х, т.е. до Fortran 77, там даже констант не было, а инициализация осуществлялась только оператором DATA (типа `DATA I /'00'/,N/4Hs12t/,CR/13/’). А инициализация «=» это ж Fortran 90, это уже после C++.

По-моему, в статье, артефакт на артефакте, и все времена для всех языков полностью перепутаны.
Так и где? Номер пункта, быть может, я просмотрел. Но, как бы, операторы вида:
10      CONTINUE
        X = 10.
        VZERO=SZERO=AZERO=0
        T=T1=T2=T3=60
        P=P0=4*ATM-K
...
        GOTO 10 

Трудно назвать «инициализацией», а не «присваиванием». Разве что, в духе: инициализация переменной цикла или переменной суммирования путём присваивания начального значения. Вот:

          DATA Y/1.0/
          INTEGER :: Z = 2 

Это «инициализации», и, ни разу, не присваивания.
В чём же разница между «инициализацией» и «заданием начального значения»?
Инициализация (конструктор) выполняется однократно в начале времени жизни переменной. В Фортран для «save» переменной, грубо говоря, до первого оператора программы, для «automatic» переменной до первого оператора процедуры или функции.

Собственно автор примерно по этому признаку их (присваивание/инициализацию) и разделяет: "… В ALGOL не было отдельного оператора для инициализации — вместо этого вы создавали переменную определенного типа и затем использовали оператор для присвоения ей чего-либо. Вы могли написать integer x; x := 5;..."

А для Фортран он, похоже, путается, то ли в версиях Фортран (начиная с Fortran 90 такая инициализация действительно появилась), то ли в его семантике. Да, в виду варианта типизации по первой букве идентификатора, можно просто написать:
        X = 5.

Но, ввиду, возможности точно так же просто написать:
10      CONTINUE
        X = 5.
        ...
        GOTO 10

Это совершенно не соответствует столбцу «Инициализация».
Собственно автор примерно по этому признаку их (присваивание/инициализацию) и разделяет:

Очевидно, что нет, потому что в начале статьи он приводит пример на Питоне:
a = 1 # Инициализация
a = a + 1 # Переприсвоение
print(a)

В вашей терминологии — это два присваивания, верно?
Семантика Питона иная, отличающаяся от Алгола и Фортрана. Время жизни переменной `a' указующей на объект Питона содержащий `1' начинается с этого оператора. И, наверное, его можно считать инициализацией, но, наверное, не сам оператор присваивания '=', а неявный конструктор этого объекта в правой части присваивания. Хотя это неточно.

Но в Фортран и Алгол времена жизни переменных начинаются и заканчиваются на границах соответствующих блоков или всей программы в целом. Я же приводил его ж цитату за Алгол-60, в котором ':=', и с его точки зрения, является только оператором присваивания, но не является оператором инициализации.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации