a = 1
a = a + 1
print(a)
В среде ФП часто критикуют данный момент императивного программирования: «Как так может быть, что a = a + 1? Это всё равно что сказать „1 = 2“. В мутабельном присваивании нет смысла».
Здесь мы наблюдаем несовпадение обозначения: «равно» должно обозначать «равенство», когда на практике оно обозначает «присвоить». Я согласен с этой критикой и считаю, что это неудачная нотация. Но также мне известно, что в некоторых языках вместо
a = a + 1
пишут выражение a := a + 1
. Почему же эта запись не является нормой?На этот вопрос обычно отвечают «потому что так сделано в C». Но это похоже на перекладывание ответственности на кого-то другого: кто из нас знает, почему так сделано в C? Давайте разбираться вместе!
Большая четвёрка
В начале 1960-ых существовало четыре доминирующих высокоуровневых языка: COBOL, FORTRAN II, ALGOL-60, и LISP. В то время, программисты разбивали присваивание на два класса: инициализацию (initialization) — когда вы впервые определяете переменную, и переприсвоение (reassignment) — когда вы вы изменяется значение существующей переменной.
Итак, давайте добавим комментарии к нашему примеру на Python и получим следующий код:
a = 1 # Инициализация
a = a + 1 # Переприсвоение
print(a)
В то время люди не пользовались конкретно этими терминами для обозначения операций, но по сути это было как раз то, что делал каждый программист. В таблице ниже вы можете увидеть, какие из операторов использовались для каждого языка, и как выполнялась проверка на равенство.
Язык | Инициализация | Присваивание | Равенство |
---|---|---|---|
FORTRAN | = | = | .EQ. |
COBOL | INITIALIZE | MOVE [1] | EQUAL |
ALGOL | N/A | := | = |
LISP | let | set | equal |
В ALGOL не было отдельного оператора для инициализации — вместо этого вы создавали переменную определенного типа и затем использовали оператор для присвоения ей чего-либо. Вы могли написать
integer x; x := 5;
, но не x := 5;
. Единственный язык из списка, который использовал =
для присваивания, это FORTRAN — и он выглядит подходящим кандидатом для ответа на наш вопрос. Но мы-то с вами знаем, что C происходит от ALGOL; что, в свою очередь, означает, что по какой-то причине было решено отказаться от оператора присваивания
:=
и изменить значение оператора =
с проверки на равенство…ALGOL порождает CPL
ALGOL-60, скорее всего, является одним из самых влиятельных языков программирования в истории computer science. Вероятно, что при всём этом он также является одним из самых бесполезных языков. В основной спецификации языка намеренно не было предусмотрено никакой функциональности для ввода/вывода. Вы могли «захардкодить» вводы и измерять выводы, но если вам нужно было сделать с ними что-либо полезное, вам требовалось найти компилятор, который расширял бы базовый язык. ALGOL был спроектирован с целью исследования алгоритмов и поэтому он «ломался», когда вы пытались сделать на нём что-либо ещё.
Однако, он оказался настолько «крепким» языком, что другие захотели обобщить его для использования в бизнесе и в промышленности. Первую подобную попытку предприняли Кристофер Страчи и Кембриджский университет. Получившийся в итоге язык CPL добавил к функциональности ALGOL достаточное количество инновационных возможностей, о большей части которых мы в дальнейшем глубоко пожалели. Одной из них было определение с инициализацией, в котором переменная могла быть инициализирована и присвоена в одном выражении. Теперь вместо того, чтобы писать
x; x := 5;
вы могли просто написать integer x = 5
. Просто супер!Но здесь мы переключились с
:=
на =
. Это происходит потому, что в CPL было три типа инициализации переменной:- = означало инициализацию по значению.
- ? означала инициализацию по ссылке, поэтому если x ? y, то переприсваивание x также изменяет y. Но если вы написали x ? y + 1 и попробовали переприсвоить x, то программа бы «упала».
- ? означает инициализацию через подстановку, т.е. превращение x в функцию, не принимающую аргументов (niladic function), которая вычисляет правостороннее значение каждый раз, когда её используют. При этом нигде не объясняется, что должно случиться, если вы попробуете переприсвоить x — и я, поверьте, тоже не слишком хочу знать это.
Проблема: теперь
=
использовался и для инициализации, и для равенства. К счастью, на практике в CPL эти варианты использования символа были четко разграничены: если вы где-либо писали =
, то было однозначно понятно, что имелось в виду.Всего год спустя Кен Айверсон создаст APL, который станет использовать символ
<
для всех видов присваиваний. Поскольку на большинстве клавиатур такой клавиши нет и никогда не было, от него быстро откажется и сам автор — его следующий язык, J, тоже будет использовать для присваиваний символ =:
[2]. Однако, APL глубоко повлиял на S, который в свою очередь глубоко повлиял на R — вот почему <-
является предпочтительным оператором присваивания в R.CPL порождает BCPL
CPL был замечательным языком, обладавшим всего одним небольшим недостатком: ни у кого не получалось написать его реализацию. Несколько человек смогли частично реализовать различные подмножества из его «фич», но этот язык оказался слишком большим и сложным для компиляторов той эпохи. Поэтому неудивительно, что Мартин Ричардс решил избавиться от ненужной сложности ящыка и создал BCPL. Первый компилятор BCPL появился в 1967 году… а первый компилятор CPL — лишь в 1970-м.
Среди многих других упрощений оказались и правила «трёх типов инициализации», которые приказали долго жить. Ричардс считал, что выражения-подстановки были вещью узкоспециальной, и их можно было заменить функциями (то же самое, по его мнению, касалось и присваиваний). Поэтому он совместил их всех в простое
=
, за исключением наименований адресов глобальной памяти, которые использовали :
. Как и в случае CPL, =
представляло собой проверку на равенство. Для присвоения (reassignment), он использовал :=
— аналогично тому, как это сделали CPL и ALGOL. Многие из последовавших после языков также следовали этому соглашению: =
для инициализации, :=
для присваивания, =
для равенства. Но в широкие массы это пошло тогда, когда Никлаус Вирт создал Pascal — вот почему сегодня мы называем подобные обозначения «в стиле Pascal».Насколько мне известно, BCPL был также первым «слабо типизированным» языком, поскольку единственным типом данных было машинное слово (data word)[3]. Это позволило сделать компилятор куда более портабельным за счет потенциального увеличения количества логических ошибок, но Ричардс надеялся на то, что улучшения в процессе и наименования с описанием позволят противостоять этому. Помимо все этого, именно в BCPL впервые появились фигурные скобки с целью определения блоков.
BCPL порождает B
Кен Томпсон хотел, чтобы BCPL мог выполняться на PDP-7. Несмотря на то, что у BCPL был «компактный компилятор», он всё ещё был в четыре раза больше, чем минимальный объем рабочей памяти на PDP-7 (16 кБ вместо 4 кБ). Поэтому Томпсону требовалось создать новый, более минималистичный язык. Также по личным эстетическим причинам он хотел минимизировать количество символов в исходном коде. Это и повлияло на дизайн языка B сильнее всего; вот почему в нём появились такие операторы, как ++ и --.
Если вы оставите в стороне использование поименованных адресов глобальной памяти, в BCPL всегда использовались следующие обозначения:
=
для инициализации и :=
для переприсваивания (reassignment). Томпсон решил, что эти вещи можно совместить в единый токен, который можно использовать для всех видов присваивания, и выбрал =, поскольку оно было короче. Однако, это привнесло некоторую неоднозначность: если x
уже был объявлен, то чем было x = y
— присваиванием или проверкой на равенство? И это ещё не всё — в некоторых случаях предполагалось, что это обе операции сразу! Поэтому он был вынужден добавить новый токен ==
как единую форму выражения смысла «равняется этому». Как выражался сам Томпсон:Поскольку присваивание в типовой программе встречается примерно в два раза чаще, чем сравнение на равенство, уместно было сделать оператор присваивания вполовину короче.За время, прошедшее между появлением BCPL и B, была создана Simula 67, первый объектно-ориентированный язык. Simula последовала соглашениям ALGOL о строгом разделении шагов инициализации и переприсвоения. Алан Кей примерно в это же время начал работу над Smalltalk, который добавил блоки, но последовал такому же синтаксису.
Томпсон (к которому присоединился Денис Ритчи) выпустил первую версию B примерно в 1969 году. Так что вплоть до 1971 года (примерно) большинство новых языков использовали для присваивания обозначение
:=
.B порождает C
… остальное – уже история.
Хорошо, есть ещё кое-что, о чём стоит рассказать. ML вышел год спустя, и, насколько мне известно, был первым языком, который привлек серьезное внимание к чистым функциям и отсутствию мутаций. Но в нем по-прежнему был спасательный круг в виде ссылочных ячеек (reference cells), которые можно было переприсваивать новым значениям при помощи оператора
:=
.Начиная с 1980, мы наблюдаем рост популярности новых императивных языков, ориентированных на корректность — в частности, Eiffel и Ada, оба из которых используют для операции присваивания символ
:=
.Если посмотреть на всю картину в целом,
=
никогда не был «естественным выбором» для оператора присваивания. Почти все языки в семейном дереве ALGOL использовали вместо этого для присваивания :=
, возможно в силу того, что =
было столь тесно ассоциировано с равенством. В наши дни большинство языков использует = поскольку его использует C, и мы можем проследить эту историю до CPL, который представлял собой тот ещё бардак.Примечания
1. В этом месте COBOL становится очень странным. У них есть несколько операторов, которые могут неявно мутировать, вроде ADD TO и COMPUTE. COBOL — плохой язык.
2. Мне нравится думать, что это было своеобразным приколом над
:=
, хотя на самом деле этот оператор согласован с остальными частями языка, который использует .
и :
как суффиксы глаголов.3. Позже в BCPL добавят ключевое слово для типа с плавающей запятой. И когда я говорю «позже», я имею в виду 2018 год.
Комментарии (149)
AnutaU
11.04.2018 19:44+2Напомнило историю про современные технические достижения и ширину крупа лошади.
juray
11.04.2018 20:10+1Самая логичная запись присваивания — в ранних версиях Рапиры:
Х+Y -> Х
она отражает тот факт, что сначала вычисляется выражение и уже потом результат присваивается переменной.Cekory
11.04.2018 23:37В R и сейчас так писать можно.
juray
12.04.2018 11:11вот именно так, в постфиксной записи оператора присваивания?
В R вроде ж присваивание префиксное:
X <- X+Y
ZyXI
12.04.2018 11:14Да, именно в постфиксной. Можете посмотреть в документации: https://cran.r-project.org/doc/manuals/r-release/R-intro.html#Vectors-and-assignment.
juray
12.04.2018 11:17а, ясно. Просто в базовых «учебниках для чайников» такого варианта не находится.
atikhonov
12.04.2018 13:42Да, в любую сторону, и иногда, например в конвейерных пайпах, логичнее и «красивее» записать результат справа (dt%>%filter()%>%group_by()%>%summarize()->res)
Stormwalker
12.04.2018 19:41Но ради всего святого, никогда так не делайте!
Те, кто пользуются dplyr, знают, что еще есть pipe операторы %<>%, которые избавляют от необходимости писать каждый раз что-то вроде df < — df %>%… По моему опыту, на практике их стоит избегать в кусках кода, которые часто могут меняться.
MacIn
12.04.2018 18:23:= из той же оперы, этот аналогично БНФ записи, т.е. это не присвоение как таковое, а указание, что под терминалом, который слева, понимается то, что справа.
evgenyspace
12.04.2018 19:42Куда проще, как по мне, использовать нотацию обычного отображения x -> w = x + 1
Samouvazhektra
11.04.2018 20:13если воспринимать "a" как букву/название то действительно может показаться что a=a+1 — странно, но если сместиться на парадигму что "a" это у нас грубо говоря именнованый акцесс к памяти то
a=1 //записать в область памяти с меткой а значение 1,
a=a+1 //записать в область памяти с меткой а увеличнное на 1 предыдущее значение…Johan
11.04.2018 23:16Так, в том то и дело, что нужно приложить некоторое количество усилий, чтобы "сместиться на парадигму", однако, не совсем понятно, что этими усилиями оплачено.
khim
12.04.2018 01:50Тут есть большой парадокс: вроде как все работали с переменными на уроках математики и вроде как должно быть проще именно такие переменные использовать (наиболее близки они к «школьно-математическим» в Prolog'е)… ан нет: на практике «сместиться на парадигму» и начать воспринимать переменную как «имя коробки с содержимым» людям легко, а вот как раз «функциональщина» — воспринимается с преогромным трудом.
Интересно — почему так…juray
12.04.2018 11:01+1На уроках математики все же в одной формуле одна переменная по разные стороны знака равенства (или неравенства) подразумевает одинаковое значение.
а, ниже разжевали лучше: habrahabr.ru/post/353292/#comment_10750626khim
12.04.2018 16:52+1Нифига там не разжевали. «Математические» переменные — это Prolog. Вот там как раз можно написать «x = 2y + 1» и это даст нам как возможность исходя из «x = 1» получить «y = 0» и, наоборот, из «y = 1» получить «x = 3».
Вот только язык этот считается эзотерическим и что-то там на нём посчитать — сложнее для большинства программистов, чем на большинстве других функциональных языках.
Вот как-то так получается, что чем дальше от «математики» и ближе к «железу», тем проще и понятней…MacIn
12.04.2018 18:25Пошаговое решение проблемы легче обозревается, потому что разбивается на малые, за раз обозримые части. А при функциональном/логическом подходе, напротив, надо всю запись целиком воспринимать одномоментно.
sebres
12.04.2018 19:39Интересно — почему так…
Ну тут на самом деле множество причин, и в первую очередь потому что для многих типичный "сахар", "соль" и "магия" некоторых функциональных языков трудночитаемы.
Ну вот простейший пример на 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 не научится оптимизировать суб-генераторы.khim
12.04.2018 20:03И сравните это по читабельности ну например с python:
И на то и на другое будут жаловаться. А вот заведите массивчик в BASIC и заполните его — и всё «станет ясно»…
Хотя, вроде как и Python и Haskel ближе к оригинальному математическому определению… и короче…
P.S. Тут как бы надо сказать что речь идёт о моём и, в то же время, не моём опыте. Об опыте помощи людям с программированием. Отсюда и такие странные выражения. Потому что вот мне лично вариант на Haskell кажется чуть ли не самым понятным и читаемым… но долгий опыт показал мне что я — скорее исключение, чем правило…
P.P.S. Не многие представляют себе во что весь этот «сахар» и «соль» в результате развернутся, а хочется же понимать это сразу без утомительного переделывания/оптимизации потом — это уже совсем другая история. Вопросы эффективности программы на Haskell — это отдельная песня, но для очень большого процента людей даже понять вот что вы там понаписали — проблема. Безотностительно к тому, во что оно потом транслируется…sebres
12.04.2018 20:32… но долгий опыт показал мне что я — скорее исключение, чем правило…
Аналогично,… однако мне мой долгий опыт ещё подсказывает, что я видимо не в состоянии привить кому-либо что-нибудь функциональное (я тут не про азы).
Однако самостоятельно те же люди много позже смогли осилить ну например тот же haskell.
При том, что с императивными языками любой группы (включая скриптовые, типа питон) никогда особых проблем не наблюдалось (ну если человек в принципе обучаем программированию).
maxzhurkin
12.04.2018 23:05Интересно — почему так…
Да просто потому, что в математике это утверждение, информация, исходные данные для преобразований, а в программе — предписание для компьютера (разумеется, при посредничестве интерпретатора или компилятора)khim
13.04.2018 00:10И? Почему вдруг «предписание для компьютера» вам стало вдруг проще написать, чем простое описание «исходных данных для преобразований» — примерно как тут?
velvetcat
11.04.2018 20:43Тоже думаю, что проблема преувеличена, и иммутабельность здесь не при чем.
sebres
12.04.2018 21:01Проблема есть на самом деле...
Я вот как-то поправил одну ошибку в коде калибрования некоторого устройства, измеряющего радиационный фон:
- if ((R = NominalR) || (Rq <= EstimatedMinEnvR)) { + if ((R == NominalR) || (Rq <= EstimatedMinEnvR)) { R = (R + Rq)/2; return; }
Лишнее наверно говорить, что в той версии калибровка естественно не работала, от слова вовсе…
И это к сожалению не единственный случай на моей памяти.
Platon_msk
11.04.2018 22:28-1Проблема высосана из мизинца левой ноги.
x = 2y + 1 в школе ни у кого не вызывало вопросов.
Не можешь запомнить нотацию языка, не программируй на нём.Johan
11.04.2018 23:04+1Проблема, может, и невелика, но ваш комментарий, как раз, свидетельствует о её существовании.
x = 2y + 1 в школе ни у кого не вызывало вопросов.
Именно потому, что это равенство, и оно не обладает описанным в статье противоречием. Оно, например, обладает свойством коммутативности, в отличии от оператора присваивания.
Очевидно, что вы эту разницу не видите, а, значит, проблема есть.
А ваш пассаж в духе "А чего добился ты?", вообще, не самым удачным образом вас характеризует.
Apache02
12.04.2018 10:46
Проблем восприятия не вызывает, это формула расчета, но x = 2y+1 это оказывается равенство, а ни зависимость x от y.
Проблема действительно высосана из пальца.viktprog
12.04.2018 11:08Формула расчета и есть равенство, никакого противоречия тут нет. Но равенство это не то же самое, что и присвоение.
Apache02
12.04.2018 12:02Верно. Равенство (операция сравнения) не тоже самое, что присвоение. Но когда в физике/математике нужно «посчитать», то мы используем = как присвоение. Алгоритмы какраз «считают», поэтому = должно быть присвоением. = как равенство используется только в решении уравнений и преобразованиях формул, чем алгоритмы не занимаются.
AgentRX
12.04.2018 12:15Как вы не замечаете очевидной разницы между:
1. x = y + 1 в математике и
2. x = y + 1 в программировании?
Попробуйте изменить значение переменной y и все станет ясно. В математике вам еще придется «извращнуться» и ввести множества значений и элементы множества, чтобы «зафиксировать» состояние на тот или иной момент. В программировании итерационная модель состояний (последовательно сменная) уже в голове разработчика.
viktprog
12.04.2018 12:58+1Ничего мы в физике никуда не присваиваем. Там это такое же равенство как условие, не более того. Например,
F = mg
— закон тяготения. Где мы тут что-то чему-то присваиваем? А вычисление — это всего лишь редуцирование к простейшим уровнениям типаx = число
илиx in {числа}
. И тут тоже нет никаких присвоений.Source
12.04.2018 13:40Верно. Вычисления в физике — это просто определение частного случая равенства на основе известных значений переменных. Никаких присваиваний.
MacIn
12.04.2018 18:26Где мы тут что-то чему-то присваиваем?
В случае вычислений по данной формуле как раз-таки присваиваем.
Antervis
12.04.2018 14:00Верно. Равенство (операция сравнения) не тоже самое, что присвоение. Но когда в физике/математике нужно «посчитать», то мы используем = как присвоение
откровенно говоря, в мат. контексте x = y + 1 "=" — это не «логическое равно», а объявление функциональной зависимости между x и y.
Amomum
11.04.2018 22:43+1Всегда не любил паскаль именно за ":=" для присваивания. Просто потому что это дольше печатать, чем "=".
Конечно, зато в паскалье "=" — это сравнение, но как-то так получается, что в коде я на порядок чаще делаю присвоение.zagayevskiy
12.04.2018 12:17Дольше печатать? ORLY? Теперь, когда вы пишете на каком-нибудь С#, печатать нужно меньше? Куда вы деваете освободившееся время?
Amomum
12.04.2018 13:46Таки да, два символа печатать дольше, чем один.
Я не говорю, что суммарно в коде нужно печатать меньше… хотя благодаря современным IDE, пожалуй что и вправду меньше. Если я пишу на C#, то в основном нажимаю ctrl+space :)antonn
12.04.2018 13:50Если я пишу на C#, то в основном нажимаю ctrl+space :)
В Дельфи есть тоже самое, если рассматривать его синтаксис близким к паскалю из-за ":=".
Куда более убийстввенным было для меня сравнение строк в яве через .Equals(), тут Дельфийские и Шарповские "="/"==" вспоминаются со слезами.webkumo
12.04.2018 18:35Минус у
.equals(T o)
ровно один — возможность NPE.
А всё остальное — специфика языка, где из-за одних компромиссов возникают некоторые минусы и некоторые плюсы. В частности запрет переопределения операторов — имхо скорее благо.
Bronx
13.04.2018 05:56Равенство — это довольно размытое понятие (и для строк в особенности), чтобы уложить его в один оператор. Равны ли друг другу строки «Елка», «елка», «ёлка», u«ёлка» и U«ёлка»? Всё зависит от контекста — считаем ли бы эквивалентными строки, записанные одинаковыми символами, но в разной кодировке, или в разном регистре, или если символы одинаковые, но записаны разными codepoints, или символы разные, но для читателя оба написания эквивалентны (е/ё)? В одной и той же программе, но в разных местах, запросто может понадобиться разное поведение, и никакого поведения «по-умолчанию» может и не быть. А если вспомнить ещё и равенство ссылок vs равенство значений?
Так что я всегда с большой опаской гляжу на места в коде, где строки (или другие сложные структуры) сравниваются операторами "==", "<", ">", а не функциями вроде Еquals() или Compare(), или другими специализированными предикатами. Нереально впихнуть в простые операторы все мыслимые группы эквивалентности и способы упорядочивания — это годится только для простейших типов вроде enum-ов и чисел, и даже там оператор сравнения моментально ломается с переходом к плавающей точке.
rraderio
12.04.2018 15:34А на каком ЯП вы сейчас программируете? уверен что есть ЯП где надо печатать меньше
mickvav
11.04.2018 23:04+1Из современных языков — присваивание через ":" есть в maxmia, а через "<-" — в R
lgorSL
12.04.2018 00:08я в скале иногда определяю
:=
для присвоения по значению. Поведение = там изменить нельзя — оно всегда по ссылке для ссылочных типов.
Ещё забавно: в go есть:=
, причём там используется наоборот — для объявления переменной с присвоением значения, a=
для присвоения!grossws
12.04.2018 18:05+1Особенно хорошо, когда пытаются сделать
res, err := ...
, а одна из переменных уже была объявлена..
Goodkat
11.04.2018 23:08Всегда любил Паскаль именно за ":=" для присваивания. Да, чуть больше писать, зато никакой неоднозначности. И куча времени и нервов сэкономлена на глупых ошибках, и не надо потом мучительно гадать, что имел в виду писатель:
if (a = fn(x))...
— то ли это ошибка, то ли автор ленив или эстет.Johan
11.04.2018 23:14Справидливости ради, проблему с
if (a = fn(x))...
можно решить просто убрав возвращаемый результат у оператора присваивания.Goodkat
11.04.2018 23:28Этак у нас какой-нибудь ABAP останется, если все прикольные и удобные, хотя и немного опасные штуки убрать.
Johan
11.04.2018 23:36Конкретно это штуку и так, не редко, убирают. В Python нет такой возможности, потому что присваивание это statement, а не expression (как раз, в комментирии ниже упомянули). В Swift, та же самая петрушка.
KvanTTT
12.04.2018 00:50Тогда нельзя будет писать
a = b = c
, что иногда используется.
А проблему с
if
можно решить явным требованием типа bool у выражения.ZyXI
12.04.2018 01:06Почему нельзя? Просто сделайте такую грамматику, что
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()))])"
netch80
12.04.2018 17:21Или потребовав явного слова на это, например
if (take a = fn(x))
польза — такие случаи (нечастые, но полезные) будут явно выделяться в коде.
kalininmr
11.04.2018 23:26в питоне потому присваивание внутри if и запретили
Goodkat
11.04.2018 23:31while, for?
Конструкция видаwhile(r = next())
удобна и позволяет обойтись без дублирования кода.Johan
11.04.2018 23:41В такой ситуации предполагается использовать
for
:
for r in iterable: pass
Здесь аналог
next
является частью контракта итератора.
kalininmr
12.04.2018 00:37в питоне нет классического for, только для итератора
про while уже написали
DrZlodberg
12.04.2018 11:20Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получается когда пытаешься писать на 2х языках с разным подходом к этому вопросу, т.к. постоянно путаешь — где какой вариант. Ну и как правильно кто-то заметил — присваивание обычно более частая операция, так что на неё логичнее иметь более короткий вариант.
Goodkat
12.04.2018 11:26Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получается
Проблема получается, когда забываешь написать второй символ "=". Очень частая и сложнонаходимая ошибка.ZyXI
12.04.2018 11:31Именно в C компилятор такое часто находит, если вы не злоупотребляете скобками и придерживаетесь правила, что при компиляции не должны вылезать никакие предупреждения вообще. И есть много статических анализаторов, та же рекламируемая здесь PVS-Studio.
Johan
12.04.2018 13:32+1В том то и дело, сэкономили один символ на каждое присвоение при печати, и, вот, кто-то вынужден писать инструменты преодоления проблемы, которую можно было не создавать. А вам, мало того, что их нужно использовать, за некоторые нужно и платить.
Source
12.04.2018 13:51+1Да по-моему "в 2 раза чаще писать -> должно быть в 2 раза короче" — это бред полный, а не аргументация… Можно подумать, что пара тысяч сэкономленных символов сделает написание нетривиальной программы хотя бы на 0.01% быстрее…
На отладку 1 ошибки забытого = в == всё это "сэкономленное" время и уйдёт.TimsTims
12.04.2018 23:05Просто раньше время было другое… Написание программы занимало много времени, и это считалось нормальным, чтобы программист держал программу в голове. Гораздо проблемнее всегда была проблема с местом, памятью и мегагерцами процессоров. Считалось, что нужно экономить память (и место) везде, где это в целом возможно.
DrZlodberg
12.04.2018 11:52Да, есть такое. Однако она решается относительно просто. В каком-то из компиляторов (gcc вроде) для использования = в if требовалось добавлять вторые скобки. Т.е. нельзя написать if(a=b), надо if((a=b)). Т.е. просто так опечататься уже не получится.
А с учётом системы возвращения последнего результата действий замена = и == ничего не даст. У меня были случаи, когда на автомате писал == вместо = и долго пытался понять, почему значение не меняется.netch80
12.04.2018 17:19GCC не запрещает if(a=b), но предупреждает о возможной некорректности. И да, за такими предупреждениями надо следить.
DrZlodberg
12.04.2018 18:38Возможно просто привык уже считать это предупреждение ошибкой…
ZyXI
12.04.2018 23:44GCC и clang позволяют превратить предупреждения в ошибки компиляции. Хорошая идея так делать, как минимум, на CI сервере.
webkumo
12.04.2018 12:16if (a = fn(x))
Это исправили… в Java и некоторых других языках. Там такое выражение сработает исключительно если функция возвращает булево значение. А тот же C о булевом значении как самостоятельном явлении и не подозревает вроде до сих пор (раньше-то вообще не подозревал, а сейчас, если я правильно помню — всё равно считает числом)Johan
12.04.2018 13:34Безусловно, остроту проблемы сняли, но до конца, как вы сами заметили, не решили. Речь же шла о том, что проблему можно было вообще не создавать.
Color
11.04.2018 23:21В ABAP "=" это и присваивание, и сравнение, а в новых версиях и инициализация. И ничего, вполне нормально воспринимается, без какого-либо неудобства, из контекста всегда однозначно понятно о чем речь.
Если в ЯП возникают проблемы вроде описанной в статье, то это либо запутанный синтаксис, либо "одаренный" программист
Revertis
11.04.2018 23:42Автор не прав. Сначала было:
А потом упразднили let :)let a = 5
pavel_kudinov
12.04.2018 01:13Более того, автор допускает ошибку предпосылки:
неявно подразумевается, что доминирующей причиной существующего порядка организации вещей является историческое происхождение явления.
в реальности, происходящее калибруется по оптимальным траекториям с сильно превосходящим исторические характеристики давлением естественного отбора: даже реальные языки активно мутируют под давлением фактора увеличения гармоничности и удобства использования инструмента под задачи и обстоятельства
касательно самого графического глифа "=" — следует рассматривать визуальную первичность восприятия выражений
в математике, из которой «наследуется смысл символа равно» — знак равенства по сути является не «сравнением», а «декларированием»
аналогом декларирования в функциональном программирования является именно декларирование, а в императивном — не сравнение, а присвоение.
так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»
есть и более короткое и логичное объяснение. то, что пишется чаще, должно писаться короче.
в импративном коде основа алгоритма — переменные, и гораздо чаще происходит присвоение, чем сравнение. соответственно короткий знак всегда будет побеждать и вытеснять громоздкое и неудобное в процессе эволюции кодаLAutour
12.04.2018 06:19так что абсолютно логично использовать "=" для присвоения, а вот для сравнения правильнее использовать либо «модифицированный под смысл операции сравнения» символ равенства (==), либо «EQ»
В бейсике и присваивание и сравнение делается через "=", и при этом их не спутать.velvetcat
12.04.2018 08:03+1Правильнее сказать, что там контекст не спутать (особенно в диалектах с LET). А так это три разные операции: императивное присваивание, декларативное утверждение равенства, и сравнение.
rraderio
12.04.2018 15:45+1Можно пример как это на бейсике?
LAutour
12.04.2018 18:31+1a = b или let a = b
if a = b thenrraderio
13.04.2018 09:17let a = b = c
можно и спутатьMacIn
13.04.2018 17:27Нет, нельзя. Эта конструкция допустима только и только если a — булево и первый знак равенства — присвоение, а второй — сравнение.
Другое дело, что человек мог описаться, но это ничем не лучше и не хуже путаницы в С типа if (a = b)
Daniyar94
12.04.2018 01:53Кажется люди просто путают арифметический синтаксис и синтаксис языков программирования.
Это типа, как сравнивать английские буквы и латинские. Выглядят одинаково, но при этом правила использования абсолютно другие
SadOcean
12.04.2018 18:38И тем не менее языки программирования в той или иной форме используют обычные арифметические конструкции и конструкции из естественных языков.
Почему бы не использовать их правила и ограничения?
Для программирования же не придумали свой знак + и -.
= и == действительно так себе конструкция, от того, что к ней все привыкли, не делает ее логичнее.
crystallize
12.04.2018 06:58Здесь и далее- мои рассуждения об этом как человека весьма далёкого от программирования
hlfx.ru/forum/showthread.php?threadid=4646&postid=149556#post149556
NeoCode
12.04.2018 08:28Сейчас оператору := нашли идеальное применение в Go — для объявления переменной, совмещенной с инициализацией (и выводом типа). С точки зрения краткости это самое то. Но следующий шаг не сделали: можно было разрешить этот оператор внутри выражений:
``` x := (123 + (y:= a*b))
ИМХО это было бы весьма в сишном хакерском стиле :)akryukov
12.04.2018 09:28+2Одна из проблем оператора присваивания "=" как раз и исходит из того, что его разрешили внутри выражений.
vesper-bot
12.04.2018 10:00Задело про Лисп и переменные. Мы в институте учили Лисп как строго функциональный язык, есть (lambda ...) и восемь стартовых функций, и всё, и вертись в параболе, никаких переменных и присваиваний, никаких циклов, только (хвостовая по возможности) рекурсия — круть была неимоверная. А потом я увидел, как на Лиспе пишут как на сях.
acmnu
12.04.2018 10:49+1Мы в институте учили Лисп как строго функциональный язык,
Но это не делает его строго функциональным. Он может реализовывать разные парадигмы. Тем наверное и ценен.
Akon32
12.04.2018 13:12никаких переменных и присваиваний, никаких циклов, только (хвостовая по возможности) рекурсия
При том, что в Common Lisp, например, нет tail call optimization (из-за динамических областей видимости, насколько я понимаю). Без циклов на нём никак.
vesper-bot
12.04.2018 18:00Ну, студенческие проекты обычно не выбирали даже той кучи, которая была доступна в mulisp (один 16-битный сегмент), разве что автор затеял в какой-то функции в одном выражении дважды вызывать её саму. Да и честно, о хвостовой рекурсии я узнал только спустя несколько лет после окончания института. Так что только рекурсия, только хардкор. И ведь решали!
Johan
12.04.2018 13:38Программист на Фортране может написать программу на Фортране на любом языке программирования
CryptoPirate
12.04.2018 11:18Вспомнился баш про оператор смерти :)
michael_vostrikov
12.04.2018 11:28Мне кажется, с
x = x + 1
проблема в том, что математика не рассматривает понятие "состояние". Любая динамика раскладывается по оси времени в статичный график, и все утверждения делаются относительно статичных моментов или их последовательности. Понятно, что в один и тот же момент x не может быть равно x + 1. Ближе всего понятие суммы по i от 1 до n, но в математике не рассматривают детали реализации, как i принимает свои значения.kalininmr
13.04.2018 20:38ещё как рассматривается.
есть куча методов основанных на итерациях и т.д.khim
13.04.2018 21:45В «методах, основанных на итерациях», как правило, заводится целый выводок переменных, но каждая из них имеет только одно какое-то значение…
michael_vostrikov
14.04.2018 05:29Напишите математическим языком, как на некоторой итерации вычислить следующее значение i.
amarao
12.04.2018 15:45Всем, кто говорит, что в математике "=" означает равенство «навсегда», вот вам простой пример:
Пусть X = 2, Y = 2. Если X + Y = 5, то 2 + 2 = 5, что неверно. Из этого следует, что X+Y ? 5.
Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?rraderio
12.04.2018 16:262 + 2 = 5
А что тогда "=" означает здесь?amarao
12.04.2018 16:49Предположение. Если мы считаем, что X+Y=5, то оказывается, что 2+2=5, а второе утверждение ложное, значит и первое утверждение так же ложное.
Два равно, и оба ложные, и вселенная всё ещё не взорвалась (повторно).rraderio
12.04.2018 17:05Предположение
Тогда X = 2 это тоже предположение, т.е. уже не верно что X + Y = 5 и 2 + 2 = 5 это одно и тоже
rraderio
12.04.2018 16:37-1X + Y = 5
Это не валидно в математике, ошибка в определении задачиamarao
12.04.2018 16:48+2Почему не валидно? Есть куча доказательств от противного, и в них всегда приходят к противоречию, это означает, что часть утверждений (со знаком равно) не являются истинными.
rraderio
12.04.2018 17:10+1часть утверждений (со знаком равно) не являются истинными
это я и имел ввиду :)
khim
12.04.2018 17:09Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?
Вы уж скажите что вы тут понимаете под «странным» смыслом.
Как я уже писал «математическое» присваивание у нас есть только в Prolog'е и близких к нему языках. И вот как раз на нём мало кто пишет — даже из тех, кто, вроде как, предпочитает «функциональщину».
Bronx
13.04.2018 06:19Ни разу. «Пусть X = 2» — это не присваивание, это логическое утверждение «считаем истинным, что X = 2». Проверка: поменяем все операнды местами:
Пусть 2 = X, и 2 = Y. Если 5 = X + Y, то 5 = 2 + 2, что неверно. Из этого следует, что 5 ? X+Y.
Хоть и запись непривычна, смысл выражения и вывод не поменялись. С присваиванием такое бы не прошло.
third112
12.04.2018 16:49ИМХО если ограничиться исходным кодом для компилятора, то обозначение присваивания дело привычки. А вот как быть, если нужно написать статью (нпр., на Хабр) с описанием алгоритма, мат.доказательством его корректности и теоретической оценкой сложности для наихудшего случая? В математике знак "=" не присваивание, а равенство. И тут возможна путаница между псевдокодом описания алгоритма, листингом его реализации на конкретном ЯП и мат. формулами. Кроме того, даже в исходном коде для компилятора бывает нужно добавить комментарий с мат.формулой. Конечно, опытный программист всегда по контексту поймет, что в данном месте подразумевается под "=". А вот ученик не всегда. Поэтому ИМХО для присваивания удобнее использовать символ/комбинацию символов, отличный от "=".
netch80
12.04.2018 17:26> В математике знак "=" не присваивание, а равенство.
В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть» («обозначим» и т.п.), и ещё надо уточнять по контексту, какого из знаков — x или y — ещё не было). В том и дело, что в математике роль конкретного '=' определяется окружающими словами.
И даже в конструкциях вида «x=x+1» надо отличать, а не идёт ли тут поиск того x, при котором это справедливо (в обычной арифметике — бесконечность любого вида, в булевской — 1, и ещё есть вариантов).rraderio
12.04.2018 17:38В предложениях типа «пусть y = 2x» это именно присвоение
Если это присвоение то «x» уже должен быть определён, а мы можем определить его позжеnetch80
12.04.2018 17:47> Если это присвоение то «x» уже должен быть определён
Нет, без контекста нельзя уточнить, что определено — x или y. Один определён, второй определяется через него и получает значение. Оба направления присвоения активно используются.
(Кстати, именно под влиянием языков программирования я начал в своих конспектах в подобных случаях явно писать направление назначения — в данном случае x := 2y или x =: 2y. Очень помогало потом разбирать.)
michael_vostrikov
12.04.2018 17:57В математике: «имеет значение».
В программировании: «присвоить значение».
Имеет значение, вычисляемое как… (а как она его получила, неважно).
Присвоить значение, вычисленное как… (в результате чего станет верным выражение «имеет значение»).
third112
12.04.2018 18:21В предложениях типа «пусть y = 2x» это именно присвоение
ИМХО нет!: «пусть y = 2x» можно перевести в слова (что часто и делают, нпр., на лекциях и читая доклады): пусть y равен 2x. Попробуйте перевести «пусть y присвоить 2x» — как-то не по-русски получается ;)
lgorSL
13.04.2018 00:52В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть»
Не согласен. Присвоение некоммутативно, в отличие от равенства. Утверждение типа "пусть" или "предположим" накладывает ограничение на x и y. Если что-то из них задано, то ограничение позволяет вычислить вторую переменную.
Если же неизвестно ни одного ни другого, то утверждение вида "… = ..." всё равно имеет смысл, в отличие от присваивания.
Вот реальный пример: надо найти массу по весу тела.
Я напишу: "по закону Ньютона P = mg, поэтому m = P/g" Присвоением можно разве что вторую формулу назвать (но я её всё равно считаю частным случаем равенства)juray
13.04.2018 16:15Если же неизвестно ни одного ни другого, то утверждение вида "… = ..." всё равно имеет смысл,
Да, равенство с несколькими неизвестными описывает некое множество точек в пространстве с размерностью, равной количеству переменных.
И кстати, зафиксировав значения нескольких переменных кроме одной — значение оставшейся не всегда определяется однозначно (вспоминаем квадратные уравнения, например). Вот только не помню, может ли полученное множество решений уравнения быть бесконечным, если уравнение не вырожденное (содержит все переменные). Мне такого примера в голову не приходит.
mSnus
12.04.2018 17:53+2Использовали бы запятую, как в нормальных языках:
mov ax, dxjuray
13.04.2018 00:01ИП0 ИП1 + П0
MacIn
13.04.2018 17:29БЗ-34? В моем 61 это (П->X)0, (П->X)1, +, (X->П)0
juray
13.04.2018 17:55Ага, Б3-34 — у МК (54, 56, 52, 61) нотация отличалась, поскольку производитель другой (киевский «Кристалл» vs светловодский «Калькулятор»).
Для записи же на бумаге часто использовалась смешанная нотация — смотря какое обозначение короче и проще записывать (для ИП / П->X выигрывает светловодская, а для arcsin / sin-1 — киевская).
После этих калькуляторов ассемблер — плёвое дело.
capslocky
12.04.2018 20:16Есть еще одна аналогичная холиварная тема — с чего начинать индексацию в массиве с 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]);
ZyXI
13.04.2018 00:16Вообще?то «под капотом» у Java/JavaScript/C# тоже адресная арифметика. И, как минимум, первые реализации языков никогда не пишутся на самом языке — скорее на чём?то вроде C, с индексацией с 0. Не вижу ничего странного в том, что во многих языках решили сэкономить на декременте, тем более что в списке «кто заплатит за декремент индексов» процессор далеко не на первом месте. Ещё, к примеру, в lua индексы как бы начинаются с 1. Но в luajit есть модуль ffi для работы с динамическими библиотеками и там они с нуля. А при написании модулей для lua с использованием C API у вас неизбежно часть кода будет иметь 0 первым индексом, а часть — 1. Учитывая, что существует только один широко поддерживаемый ffi (точнее, один на двойку процессор—ОС), и он происходит от C, а без ffi язык не может быть чем?то большим, чем DSL, я не вижу ничего удивительного в том, что индексацию начинают с 0.
lgorSL
13.04.2018 01:26+1И если в С/С++ это естественно из-за адресной арифметки, то в Java/Javascript/C# это было сделано из соображений «похожести» синтаксиса на С++. То есть это просто историческое наследие, а ведь могло бы быть и так:
Начинал в школе с паскаля (и изначально привык к индексации с 1), но мне кажется, что индексация с нуля намного удобнее. Иначе вылезают проблемы с тем, что при умножении индекса на число первый индекс становится уже не первым.
Например, если хранить "двухмерные" данные в одномерном массиве:
arr[y * width + x]
vsarr[(y-1) * width + x]
Или пропустить каждый второй элемент:
arr[2*i]
vsarr[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, который сам бегает по любым коллекциям.capslocky
13.04.2018 12:21-1Я считаю, что типичная работа с массивами и их индексами (>97% случаев) в языках типа Java/Javascript/C# не обладает большим удобством из-за выбора 0, а не 1. В будущем будут создаваться новые высокороуровневые языки программирования. И одним из основных аргументов в пользу 1 будет именно большее удобство изучения программирования с нуля школьниками (студентами), у которых еще нет привычки считать индекс с 0.
AnutaU
13.04.2018 12:40Если эти самые новые языки не будут предназначены непосредственно для изучения школьниками, то аргумент «удобство изучения с нуля непрофессионалами» как-то слабоват. Компромиссов в дизайне языков и так слишком много, чтобы добавлять лишние ограничения, которые нужно учитывать (местами за счёт ухудшения других важных параметров).
Antervis
13.04.2018 13:04+31. вы считаете так, а подавляющее большинство программистов иначе. Как минимум наглядно видно что яп с индексацией с 0 в среднем многократно популярнее.
2. у школьников и студентов в принципе нет привычки считать индекс, хоть с нуля хоть с единицы. В математике они не работают с явной индексацией совсем, лишь с диапазонамии i-ми элементами.
3. вам наглядно продемонстрировали почему индексация с 0 приводит к более простому коду в любом нетривиальном случае.
4. индексация с 1 требует постоянных пересчетов в рантайме — небольшой, но заметный удар по производительностиrraderio
13.04.2018 14:31вам наглядно продемонстрировали почему индексация с 0 приводит к более простому коду в любом нетривиальном случае.
Как получить последний элемент? с индексацией с 1 это будет
array[length]
индексация с 1 требует постоянных пересчетов в рантайме — небольшой, но заметный удар по производительности
не требует, надо хранить указатель на начало — 1ZyXI
14.04.2018 00:47Как получить последний элемент? с индексацией с 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]
. Правда, будет ли какой?либо удар по производительности зависит от целевого процессора, окружающего кода, способности компилятора к оптимизации, …khim
14.04.2018 01:05
Это не вы не поняли. Это парсер не понял. Имелось в виду «хранить указатель нане требует, надо хранить указатель на начало — 1
Этого я не понял. Чем указатель на начало поможет?начало-1
». Если «внутри» в регистре (или в соответствующем поле структуры) у вас в качестве указателя на массив хранится не указатель на его первый элемент, а указатель на его [несуществующий] нулевой элемент, то в рантайме никаких дополнительных пересчётов не будет.ZyXI
14.04.2018 01:35Это тоже не всегда поможет. Если вы работаете с неизменяемыми структурами (кортеж или в вашем ЯП просто есть такой тип как «неизменяемый список»), то под них более чем логично выделять память одним куском вида
struct { size_t size; Type elements[]; }
. И вот здесь вы либо выделяете больше памяти, чем нужно, либо делаете декремент.
В принципе, чем больше вы будете думать об оптимизации ВМ для вашего языка, тем меньше вам будет хотеться сделать индексы, начинающиеся с единицы. Добавьте к этому то, что на начальном этапе у вас будут только программисты, пришедшие с других языков, а из популярных такие индексы только у Mathematic/MATLAB/Wolfram, lua и R, получите, что если вы не хотите откусить кусок от gamedev или различного рода научных вычислений, то вашей идее будут сопротивляться и вы либо?таки сделаете индексы с нуля, либо получите отток программистов, недовольных своими частыми ошибками из?за использования более привычных индексов.
(И, кстати, чтобы хранить указатель на “начало?1” вам всё равно придётся сделать как минимум две дополнительные операции: при сохранении указателя в структуре и при освобождении. Хотя, конечно, в это время вас гораздо больше будет волновать проблема «как бы делать обе операции пореже», т.к. работа с кучей — это долго.)
khim
14.04.2018 03:03Если вы работаете с неизменяемыми структурами (кортеж или в вашем ЯП просто есть такой тип как «неизменяемый список»), то под них более чем логично выделять память одним куском вида
Ничего не понял. Если у вас подобная структура, то как раз логично иметь индексы с 1, а нулевой элемент будет содержать размер если размерыstruct { size_t size; Type elements[]; }
. И вот здесь вы либо выделяете больше памяти, чем нужно, либо делаете декремент.Type
— такие же, как уsize_t
(здравствуй MacOS и Turbo Pascal!), а если нет — то всё равно нужен пересчёт, что для индексов с нуля, что для индексов с единицы.
То же самое с выделением памяти: перед массивом должна идти служебная информация (иначе невозможно будет эту память освободить, так как в большинстве случаев в функцию типаfree
эта информации не передаётся), так что в любом случае нужно будет пересчёт делать.
«Индексы с нуля» — это вопрос удобства, но никак не скорости: на практике почти всегда скорость одинакова, хотя в редких случаях вариант с индексацией от единицы будет выигравывать (здравствуй MacOS и Turbo Pascal ещё раз!).ZyXI
14.04.2018 04:03Ничего не поняли, потому что я много пишу на C — обращение к элементам что со структурой
struct { Type *elements; } data;
, что со структуройstruct { Type elements[]; } data;
будет выглядеть какdata.elements[i]
. Вы правы, тут будет пересчёт, но C это успешно скрывает.
Со служебной информацией не согласен: этот пересчёт производится в аллокаторе, независимо от вашего желания. Но перед этим всё равно будет инкремент, потому что шансы на то, что компилятор вставит тело
free()
в вашу функцию сейчас равны нулю. (И, кстати, все ли аллокаторы хранят служебные данные перед выделенной памятью?)
С тем, что с современными десктопными системами пересчёт индекса будет незаметен я согласен. Но в нишу C вы не пролезете. И, главное, не только пользователи будут допускать ошибки с непривычки. Но и у вас будут ошибки — как я говорил выше, первая реализация языка не пишется на самом языке, а мало что из списка ЯП с первым индексом?единицей подходит для создания нового языка.
khim
14.04.2018 05:02Ничего не поняли, потому что я много пишу на C — обращение к элементам что со структурой
Да — но это только потому что «массив» и «указатель на массив» в C — это один тип. Если в языке это разные типы, то в случае с указателем ничто не мешает хранить «смещённый» указатель, а в случае со структурой… тут да — если массив это первый элемент структуры, то будет выигрыш… но так ли он часто встречается на практике?struct { Type *elements; } data;
, что со структуройstruct { Type elements[]; } data;
будет выглядеть какdata.elements[i]
.
Со служебной информацией не согласен: этот пересчёт производится в аллокаторе, независимо от вашего желания.
Опять-таки: зависит от того, как устроен язык.
И, кстати, все ли аллокаторы хранят служебные данные перед выделенной памятью?
А где ещё? Можно как в классическомPascal
'е — передавать в деаллокатор размер обьекта (по большому счёту это всё, что нужно деаллокатору). Но в большинстве языков испольщются данные перед выделенным участком памяти…
С тем, что с современными десктопными системами пересчёт индекса будет незаметен я согласен. Но в нишу C вы не пролезете.
Не из-за проблем с индексами, уверяю вас. MacOS до MacOS X — это Pascal и индексы с 1. И ничего, в определённые моменты до 40% рынка лаптопов на рынке США были PowerBookами… пока их Dell и китайцы не вытеснили…
Но и у вас будут ошибки — как я говорил выше, первая реализация языка не пишется на самом языке, а мало что из списка ЯП с первым индексом?единицей подходит для создания нового языка.
Вы очень сильно преувеличиваете сложность написания компилятора нового языка, уверяю ваc. Вот хороший оптимизирующий компилятор написать — это не один десяток человеко-лет. А просто «какой-нибудь»… нет, это задачка на пару месяцев скорее.
ZyXI
14.04.2018 01:41Кстати, интересный факт: в zsh есть настройка (KSH_ARRAYS), позволяющая использовать либо 0, либо 1 в качестве первого индекса. Конечно, как можно легко предположить из названия, предназначена для режима эмуляции ksh.
datacompboy
В переводе упущена отличнейшая сноска:
«BCPL later adding a floating point keyword. And when I say “later”, I mean in 2018.»
HotWaterMusic Автор
Просто отлично. Спасибо, добавил!