Комментарии 233
«BCPL later adding a floating point keyword. And when I say “later”, I mean in 2018.»
Начиная с банального фактического несоответствия — космического корабля «Кеннеди» никогда не существовало, такое название носит космодром.
Прочие аргументы:
— ж/д габарит несколько шире ж/д колеи. Для «обычной» в два раза, для узкоколейки — аж в три, и в общем случае линейной зависимости нет.
— диаметр боковых ускорителей шаттла 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
Да, именно в постфиксной. Можете посмотреть в документации: https://cran.r-project.org/doc/manuals/r-release/R-intro.html#Vectors-and-assignment.
Те, кто пользуются dplyr, знают, что еще есть pipe операторы %<>%, которые избавляют от необходимости писать каждый раз что-то вроде df < — df %>%… По моему опыту, на практике их стоит избегать в кусках кода, которые часто могут меняться.
Куда проще, как по мне, использовать нотацию обычного отображения x -> w = x + 1
если воспринимать "a" как букву/название то действительно может показаться что a=a+1 — странно, но если сместиться на парадигму что "a" это у нас грубо говоря именнованый акцесс к памяти то
a=1 //записать в область памяти с меткой а значение 1,
a=a+1 //записать в область памяти с меткой а увеличнное на 1 предыдущее значение…
Так, в том то и дело, что нужно приложить некоторое количество усилий, чтобы "сместиться на парадигму", однако, не совсем понятно, что этими усилиями оплачено.
Интересно — почему так…
а, ниже разжевали лучше: habrahabr.ru/post/353292/#comment_10750626
Вот только язык этот считается эзотерическим и что-то там на нём посчитать — сложнее для большинства программистов, чем на большинстве других функциональных языках.
Вот как-то так получается, что чем дальше от «математики» и ближе к «железу», тем проще и понятней…
Интересно — почему так…
Ну тут на самом деле множество причин, и в первую очередь потому что для многих типичный "сахар", "соль" и "магия" некоторых функциональных языков трудночитаемы.
Ну вот простейший пример на 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.
При том, что с императивными языками любой группы (включая скриптовые, типа питон) никогда особых проблем не наблюдалось (ну если человек в принципе обучаем программированию).
Интересно — почему так…
Да просто потому, что в математике это утверждение, информация, исходные данные для преобразований, а в программе — предписание для компьютера (разумеется, при посредничестве интерпретатора или компилятора)
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 в школе ни у кого не вызывало вопросов.
Не можешь запомнить нотацию языка, не программируй на нём.
deleted
Проблема, может, и невелика, но ваш комментарий, как раз, свидетельствует о её существовании.
x = 2y + 1 в школе ни у кого не вызывало вопросов.
Именно потому, что это равенство, и оно не обладает описанным в статье противоречием. Оно, например, обладает свойством коммутативности, в отличии от оператора присваивания.
Очевидно, что вы эту разницу не видите, а, значит, проблема есть.
А ваш пассаж в духе "А чего добился ты?", вообще, не самым удачным образом вас характеризует.
Проблем восприятия не вызывает, это формула расчета, но 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.
А всё остальное — специфика языка, где из-за одних компромиссов возникают некоторые минусы и некоторые плюсы. В частности запрет переопределения операторов — имхо скорее благо.
Так что я всегда с большой опаской гляжу на места в коде, где строки (или другие сложные структуры) сравниваются операторами "==", "<", ">", а не функциями вроде Еquals() или Compare(), или другими специализированными предикатами. Нереально впихнуть в простые операторы все мыслимые группы эквивалентности и способы упорядочивания — это годится только для простейших типов вроде enum-ов и чисел, и даже там оператор сравнения моментально ломается с переходом к плавающей точке.
В Java есть интерфейсы Comparable и Comparator. Именно они предназначены для функционального, подходящего к конкретной ситуации сравнения.
метод equals в среднем даёт понимание — является ли предложенный элемент эквивалентным текущему. Не равным, а эквивалентныым и это уже зависит только от класса и заложенного в него алгоритма эквивалентности.
Почему там не стали вешать этот метод на ==:
- в Java сознательно отказались от перегрузки операторов. Решение может кому-то нравиться, кому-то не нравится, это банальная особенность языка. Но это не даёт возможности переопределения функции равенства
- оператор == когда-то определил как оператор строгого равенства — равенства по ссылке. Он отвечает только на вопрос — тот же это объект или нет. Он никогда не отвечает на вопрос, похож ли этот объект на тот.
Всегда любил Паскаль именно за ":=" для присваивания. Да, чуть больше писать, зато никакой неоднозначности. И куча времени и нервов сэкономлена на глупых ошибках, и не надо потом мучительно гадать, что имел в виду писатель: if (a = fn(x))...
— то ли это ошибка, то ли автор ленив или эстет.
Справидливости ради, проблему с if (a = fn(x))...
можно решить просто убрав возвращаемый результат у оператора присваивания.
Конкретно это штуку и так, не редко, убирают. В Python нет такой возможности, потому что присваивание это statement, а не expression (как раз, в комментирии ниже упомянули). В Swift, та же самая петрушка.
Тогда нельзя будет писать 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))
польза — такие случаи (нечастые, но полезные) будут явно выделяться в коде.
Конструкция вида
while(r = next())
удобна и позволяет обойтись без дублирования кода.В такой ситуации предполагается использовать for
:
for r in iterable:
pass
Здесь аналог next
является частью контракта итератора.
про while уже написали
Переписывается в while(true) .. break
. Да, на 1 строчку больше писать, но выгоды на мой взгляд намного больше.
While (true)… break — это костыль и изнасилование здравого смысла.
Да, в правильном сценарии у вас есть нормальный итератор, где вы просто пишете for value in mystream.filter_map(|x| x.read()) { ... }
. Для меня изнасилованием является попытка присовокупить и еще что-то посчитать. Тот же Липперт считает это одним из главных косяков при проектировании C#. Также интересной информацией может быть тот факт, что во всех новых языках присваивание это таки statement.
Да, конечно, и я тоже пользуюсь такой конструкцией. Но же просто замаскированный goto.
В данном случае мы получаем более-менее структурированный goto, который не создает спагетти-кода (ну ладно, почти, одно перекрещивание).
Проблемы с goto начинаются, когда вы начинаете использовать его для перезода «вверх» по коду. Пока все переходы ведут «вниз» — на читабельность это влияет относительно слабо.
if (плохо1) {
cleanup: почистить;
return;
}
...
if (плохо2) {
goto cleanup;
}
С другой стороны, вход куда-то внутрь тела цикла даже ниже по ходу может быть жуткой диверсией.
Но тем не менее правила в следующем духе могут быть вполне приемлемыми для 99% случаев:
Допускаются переходы:
- Как реализация отсутствующих в C break, continue с меткой.
- В точку кода, из которой единственный выход — итоговый выход из функции. (Это для таких cleanup.)
- Вниз по коду, если происходит переход на тот же уровень вложенности или меньший.
void foo(...) {
resource1 = ...
if (плохо1)
goto cleanup1;
//...
resource2 = ...
if (плохо2)
goto cleanup2;
//...
cleanup2:
(очистка ресурса 2);
cleanup1:
(очистка ресурса 1);
}
такой early return не противоречит позитивному сценарию и в принципе используется достаточно часто.
создали
try
выполнили
finally
разрушили
end;
гарантированно всё разрушится, безо всяких goto.Только теперь вы уже не можете быть уверены в том, как будет исполняться программа. Наличие исключений — это как замаскированный (и ограниченный до «только вверх по стёку») longjump практически из любого места программы. Если есть checked exceptions то у вас хотя бы есть идея о том, что может быть выброшено. Но обычно их нет. Отлично понимаю авторов Rust и Go, у которых нет исключений в традиционном виде, а то что есть не предполагается для использования таким образом. Часто механизм удобен, но идея их использования в сложноотлаживаемых приложениях (у меня в основном embedded) мне не нравится.
Только теперь вы уже не можете быть уверены в том, как будет исполняться программа
Стоит расценивать исключения как один из вариантов исполнения программы, хоть и необычный. Все исключения можно достаточно хорошо запротоколировать и разобраться, что не так. Возможно где-то со временем.
Сами по себе исключения — максимально лаконичный и гибкий инструмент. Не всегда уместный и неявный, и от того сильно недооцененный, на мой взгляд. Именно посредством исключений можно «прокидывать» максимум любой отладочной информации. Разве что в языках с поддержкой исключений я бы предпочел вариант «функция может кидать исключения только если это указано» вместо «функция может кидать исключения если не указано обратное».
Разве что в языках с поддержкой исключений я бы предпочел вариант «функция может кидать исключения только если это указано» вместо «функция может кидать исключения если не указано обратное».Не работает. Либо у вас ошибки типа переполнения счётчика бросают исключения и их нужно обрабатывать, чтобы программа скомпилировалась, то есть на каждую полезную строчку у вас оброауется пять строк к обработкой ошибок (а вроде как мы и заводили-то исключения, чтобы от этого избавится), либо у вас получаются «исключительные исключения», которые вроде как бросаются — но не ловятся. Как только вы завели «исключительные исключения» — в них тут же начинают заворачивать всё подряд, так как согласно 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, особенно если отладочную информацию в них не положить.
гарантированно всё разрушится, безо всяких goto.Исключения — это как раз и есть «goto на стероидах». Даже хуже longjump'а.
Да, с ними можно жить и даже писать достаточно надёжные программы, но я до сих пор не уверен, что их наличие упрощает, а не усложняет жизнь.
Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получаетсяПроблема получается, когда забываешь написать второй символ "=". Очень частая и сложнонаходимая ошибка.
Именно в C компилятор такое часто находит, если вы не злоупотребляете скобками и придерживаетесь правила, что при компиляции не должны вылезать никакие предупреждения вообще. И есть много статических анализаторов, та же рекламируемая здесь PVS-Studio.
В том то и дело, сэкономили один символ на каждое присвоение при печати, и, вот, кто-то вынужден писать инструменты преодоления проблемы, которую можно было не создавать. А вам, мало того, что их нужно использовать, за некоторые нужно и платить.
Да по-моему "в 2 раза чаще писать -> должно быть в 2 раза короче" — это бред полный, а не аргументация… Можно подумать, что пара тысяч сэкономленных символов сделает написание нетривиальной программы хотя бы на 0.01% быстрее…
На отладку 1 ошибки забытого = в == всё это "сэкономленное" время и уйдёт.
Просто раньше время было другое… Написание программы занимало много времени, и это считалось нормальным, чтобы программист держал программу в голове. Гораздо проблемнее всегда была проблема с местом, памятью и мегагерцами процессоров. Считалось, что нужно экономить память (и место) везде, где это в целом возможно.
А с учётом системы возвращения последнего результата действий замена = и == ничего не даст. У меня были случаи, когда на автомате писал == вместо = и долго пытался понять, почему значение не меняется.
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
… нувыпонели), чтобы, например, выжать максимум пользы из каждого такта микроконтроллера, но такие исключения должны быть явно прокомментированы в коде/задокументированы с пометкой «НЕ ВЛЕЗАЙ! УБЬЁТ!».Понты из разряда «глянь чо могу, хыы!» лучше не выносить за пределы личных проектов :)
В ABAP "=" это и присваивание, и сравнение, а в новых версиях и инициализация. И ничего, вполне нормально воспринимается, без какого-либо неудобства, из контекста всегда однозначно понятно о чем речь.
Если в ЯП возникают проблемы вроде описанной в статье, то это либо запутанный синтаксис, либо "одаренный" программист
let a = 5
А потом упразднили let :)неявно подразумевается, что доминирующей причиной существующего порядка организации вещей является историческое происхождение явления.
в реальности, происходящее калибруется по оптимальным траекториям с сильно превосходящим исторические характеристики давлением естественного отбора: даже реальные языки активно мутируют под давлением фактора увеличения гармоничности и удобства использования инструмента под задачи и обстоятельства
касательно самого графического глифа "=" — следует рассматривать визуальную первичность восприятия выражений
в математике, из которой «наследуется смысл символа равно» — знак равенства по сути является не «сравнением», а «декларированием»
аналогом декларирования в функциональном программирования является именно декларирование, а в императивном — не сравнение, а присвоение.
так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»
есть и более короткое и логичное объяснение. то, что пишется чаще, должно писаться короче.
в импративном коде основа алгоритма — переменные, и гораздо чаще происходит присвоение, чем сравнение. соответственно короткий знак всегда будет побеждать и вытеснять громоздкое и неудобное в процессе эволюции кода
так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»
В бейсике и присваивание и сравнение делается через "=", и при этом их не спутать.
Мне кажется что я привык к =
так же как и к десятичной системе счисления.
Кажется люди просто путают арифметический синтаксис и синтаксис языков программирования.
Это типа, как сравнивать английские буквы и латинские. Выглядят одинаково, но при этом правила использования абсолютно другие
Почему бы не использовать их правила и ограничения?
Для программирования же не придумали свой знак + и -.
= и == действительно так себе конструкция, от того, что к ней все привыкли, не делает ее логичнее.
hlfx.ru/forum/showthread.php?threadid=4646&postid=149556#post149556
Сейчас оператору := нашли идеальное применение в Go — для объявления переменной, совмещенной с инициализацией (и выводом типа). С точки зрения краткости это самое то. Но следующий шаг не сделали: можно было разрешить этот оператор внутри выражений:
``` x := (123 + (y:= a*b))
ИМХО это было бы весьма в сишном хакерском стиле :)
Мы в институте учили Лисп как строго функциональный язык,
Но это не делает его строго функциональным. Он может реализовывать разные парадигмы. Тем наверное и ценен.
никаких переменных и присваиваний, никаких циклов, только (хвостовая по возможности) рекурсия
При том, что в Common Lisp, например, нет tail call optimization (из-за динамических областей видимости, насколько я понимаю). Без циклов на нём никак.
Программист на Фортране может написать программу на Фортране на любом языке программирования
Мне кажется, с x = x + 1
проблема в том, что математика не рассматривает понятие "состояние". Любая динамика раскладывается по оси времени в статичный график, и все утверждения делаются относительно статичных моментов или их последовательности. Понятно, что в один и тот же момент x не может быть равно x + 1. Ближе всего понятие суммы по i от 1 до n, но в математике не рассматривают детали реализации, как i принимает свои значения.
есть куча методов основанных на итерациях и т.д.
но чаще словами пишут.
в методах ньютона для диффуров все вполне себе понятно написано
там даже полноценные циклы :)
Вот все эти "
а
с крышечкой", "a
со штрихом", "a
с кришечкой и n
штрихами" — они как раз оттуда.Максимум, что бывает в математике — это пролог-переменные, которые могут в одной «ветке рассуждений» получать значение, но при обнаружении противоречий — терять его.
Причём в отличие от пролога, где при необнаружении противоерчия переменные могут-таки получить серию «решений» с разными значениями в момент получения неопределённости в математике возникают все эти штрихи и
x1
, x2
, etc.Пусть X = 2, Y = 2. Если X + Y = 5, то 2 + 2 = 5, что неверно. Из этого следует, что X+Y ≠ 5.
Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?
2 + 2 = 5А что тогда "=" означает здесь?
Два равно, и оба ложные, и вселенная всё ещё не взорвалась (повторно).
X + Y = 5Это не валидно в математике, ошибка в определении задачи
Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?Вы уж скажите что вы тут понимаете под «странным» смыслом.
Как я уже писал «математическое» присваивание у нас есть только в Prolog'е и близких к нему языках. И вот как раз на нём мало кто пишет — даже из тех, кто, вроде как, предпочитает «функциональщину».
Пусть 2 = X, и 2 = Y. Если 5 = X + Y, то 5 = 2 + 2, что неверно. Из этого следует, что 5 ≠ X+Y.
Хоть и запись непривычна, смысл выражения и вывод не поменялись. С присваиванием такое бы не прошло.
Ой, сразу вспоминается задачка (из реального пособия для начальных классов), отменяющая привычное "от перестановки мест… не меняется"
… а вы про синтаксис в языках программирования...
Справедливости ради, будучи записанным словами, выражения "дважды пять" и "пятью два" отличаются не только синтаксически, но и концептуально, так что некая логика в этой рекомендации есть. Правда в описанной задаче правильно было бы записать 5*2 ("пятью два", "пять раз по два", "five times two", пять пар чашек), т.е. ровно противоположное тому, что написал автор методички. Безразмерный масштабный коэффициент принято ставить перед размерным, например 0.1C или 2πr. Численно результат, конечно, не меняется, но вот такая конвенция действительно есть, и даже отражена в языке.
Автор методички всё же не до конца прав — как по мне, более корректной трактовкой было бы "2 куска/чашку" (дозировка на порцию) и "5 чашек" (количество порций) — в таком случае мы имеем вполне разумное решение с сокращением размерности порций, при любом порядке перемножения. И решаем в удобном для себя порядке — в зависимости, от чего отталкиваемся (дозировки или количества порций).
Не дай бог моих детей так учить будут…
В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть» («обозначим» и т.п.), и ещё надо уточнять по контексту, какого из знаков — x или y — ещё не было). В том и дело, что в математике роль конкретного '=' определяется окружающими словами.
И даже в конструкциях вида «x=x+1» надо отличать, а не идёт ли тут поиск того x, при котором это справедливо (в обычной арифметике — бесконечность любого вида, в булевской — 1, и ещё есть вариантов).
В предложениях типа «пусть y = 2x» это именно присвоениеЕсли это присвоение то «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 вводится, то
- Это может быть такое y, которое отличается от y в другой части.
- Мы должны иметь право умножить на α для обратного перехода (y->x) и разделить — для прямого (x->y). Это или доказывается, или считается очевидным, но в любом случае может быть подвергнуто сомнению. На незаконности таких переходов горят >90% самодеятельных доказательств великих теорем, потому что их очень тяжело отслеживать.
И вот при таком доказанном введении (y в моём примере) появляется новая сущность. Она не была раньше, как вы утверждаете — она возникла в ходе изложения. Она может быть вре́менной (на одну часть изложения), пространственно локальной или условийно локальной (например, при x > 1).
И вот для того, чтобы сделать изложение корректным и читаемым, и надо вводить метки типа «этот знак появляется (вот тут) и теряет смысл (вот тут)». Когда я пишу «x = 2y», этой метки нет. Когда я пишу «x =: 2y», я ввожу новое y (и отменяю старое, если было). И с этим реально удобнее. Конечно, не все случаи этим можно покрыть (например, я могу сделать x = 2y + 3z, где отношение между y и z оформляется отдельным уравнением), но всё равно уже значительно проще.
Пока до отсечений не дойдёте и не обнаружите что спрятать тот факт, что 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
Для записи же на бумаге часто использовалась смешанная нотация — смотря какое обозначение короче и проще записывать (для ИП / П->X выигрывает светловодская, а для arcsin / sin-1 — киевская).
После этих калькуляторов ассемблер — плёвое дело.
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, который сам бегает по любым коллекциям.
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 — обращение к элементам что со структуройДа — но это только потому что «массив» и «указатель на массив» в C — это один тип. Если в языке это разные типы, то в случае с указателем ничто не мешает хранить «смещённый» указатель, а в случае со структурой… тут да — если массив это первый элемент структуры, то будет выигрыш… но так ли он часто встречается на практике?struct { Type *elements; } data;
, что со структуройstruct { Type elements[]; } data;
будет выглядеть какdata.elements[i]
.
Со служебной информацией не согласен: этот пересчёт производится в аллокаторе, независимо от вашего желания.Опять-таки: зависит от того, как устроен язык.
И, кстати, все ли аллокаторы хранят служебные данные перед выделенной памятью?А где ещё? Можно как в классическом
Pascal
'е — передавать в деаллокатор размер обьекта (по большому счёту это всё, что нужно деаллокатору). Но в большинстве языков испольщются данные перед выделенным участком памяти…С тем, что с современными десктопными системами пересчёт индекса будет незаметен я согласен. Но в нишу C вы не пролезете.Не из-за проблем с индексами, уверяю вас. MacOS до MacOS X — это Pascal и индексы с 1. И ничего, в определённые моменты до 40% рынка лаптопов на рынке США были PowerBookами… пока их Dell и китайцы не вытеснили…
Но и у вас будут ошибки — как я говорил выше, первая реализация языка не пишется на самом языке, а мало что из списка ЯП с первым индексом‐единицей подходит для создания нового языка.Вы очень сильно преувеличиваете сложность написания компилятора нового языка, уверяю ваc. Вот хороший оптимизирующий компилятор написать — это не один десяток человеко-лет. А просто «какой-нибудь»… нет, это задачка на пару месяцев скорее.
Кстати, интересный факт: в 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
код с функцией доступа к массиву по индексу без проверки.
Собственно, я думал будет больше языков без проверок для системного программирования. Оказывается, нет. Все делают проверки.
А с единицей у вас дырка в нуле, потому что возвращение последнего значения по нулевому индексу гарантированно принесёт очень много ошибок от программистов, которые переходят с других языков.
А можно по
a[0]
обращаться к длине массива, как в Паскале со строками ;)И получается очень приятная симметрия:
a[n]
— n-ый элемент с начала, a[-n]
— n-ый элемент с конца.Это плюс одно сравнение, если n
не константа, и, что намного важнее, тип выражения a[0]
для массива [T]
внезапно становится usize | T
вместо просто T
. Это абсолютно никому не нужно, даже выдавать «значение по‐умолчанию» как делает LabVIEW со всеми выходами за границы массива и то лучше.
Поправьте меня если я неправ, но зная про Pascal и его строки только разные слухи я могу предположить, что
- Это относится только к строкам.
- Если в Pascal есть тип строки, позволяющий строки длиннее 255 байт, то у этого типа длину строки таким образом получить не получится.
В классическом паскале длина строки вроде действительно до 255 байт, в делфях, насколько помню — 65к была (и кодировка — двухбайтовая). Но могу ошибиться — они для первого элемента и все 4 байта выделить могли.
- В Паскале — да, потому что для массивов обе границы задаются явно, и могут быть произвольными: хоть
0..10
, хоть-5..15
, хоть'a'..'z'
- webkumo прав: для «широких строк» можно было бы хранить двухбайтную длину в нулевом элементе, но для Borland совместимость с OLE/COM оказалась важнее совместимости с Паскалем, так что WideString в Delphi — это BSTR
- Для динамически типизированных языков расширение типа выражения не представляет проблемы.
Для динамически типизированных языков расширение типа не представляет только технической проблемы. Вам всё ещё нужно понять, как это отразится на реальных программах: будет ли при этом легче/сложнее программисту допустить ошибку, будет ли легче/сложнее понимать программу, …
Расширение типа на 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
В тексте статьи есть библейская отсылка, и, может быть, стоило переводить её именно так, как переводят Библию: «Авраам родил Исаака; Исаак родил Иакова; Иаков родил Иуду и братьев его». То есть «CPL родил BCPL», «BCPL родил B», «B родил 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
Это «инициализации», и, ни разу, не присваивания.
Собственно автор примерно по этому признаку их (присваивание/инициализацию) и разделяет: "… В ALGOL не было отдельного оператора для инициализации — вместо этого вы создавали переменную определенного типа и затем использовали оператор для присвоения ей чего-либо. Вы могли написать integer x; x := 5;..."
А для Фортран он, похоже, путается, то ли в версиях Фортран (начиная с Fortran 90 такая инициализация действительно появилась), то ли в его семантике. Да, в виду варианта типизации по первой букве идентификатора, можно просто написать:
X = 5.
Но, ввиду, возможности точно так же просто написать:
10 CONTINUE
X = 5.
...
GOTO 10
Это совершенно не соответствует столбцу «Инициализация».
Собственно автор примерно по этому признаку их (присваивание/инициализацию) и разделяет:
Очевидно, что нет, потому что в начале статьи он приводит пример на Питоне:
a = 1 # Инициализация a = a + 1 # Переприсвоение print(a)
В вашей терминологии — это два присваивания, верно?
Но в Фортран и Алгол времена жизни переменных начинаются и заканчиваются на границах соответствующих блоков или всей программы в целом. Я же приводил его ж цитату за Алгол-60, в котором ':=', и с его точки зрения, является только оператором присваивания, но не является оператором инициализации.
Почему "=" означает присваивание?