Давайте посмотрим на следующий код:

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)


  1. datacompboy
    11.04.2018 18:48
    +7

    В переводе упущена отличнейшая сноска:
    «BCPL later adding a floating point keyword. And when I say “later”, I mean in 2018.»


    1. HotWaterMusic Автор
      11.04.2018 19:09
      +1

      Просто отлично. Спасибо, добавил!


  1. AnutaU
    11.04.2018 19:44
    +2

    Напомнило историю про современные технические достижения и ширину крупа лошади.


  1. juray
    11.04.2018 20:10
    +1

    Самая логичная запись присваивания — в ранних версиях Рапиры:
    Х+Y -> Х
    она отражает тот факт, что сначала вычисляется выражение и уже потом результат присваивается переменной.


    1. Desprit
      11.04.2018 20:32

      С одной стороны — да, но тогда будет сложнее искать инициализированные переменные в коде, ведь конец строки везде разный. Куда проще пробежаться глазами по одной линии.


      1. juray
        12.04.2018 10:57

        Тоже верно


    1. Cekory
      11.04.2018 23:37

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


      1. juray
        12.04.2018 11:11

        вот именно так, в постфиксной записи оператора присваивания?
        В R вроде ж присваивание префиксное:
        X <- X+Y


        1. ZyXI
          12.04.2018 11:14

          Да, именно в постфиксной. Можете посмотреть в документации: https://cran.r-project.org/doc/manuals/r-release/R-intro.html#Vectors-and-assignment.


          1. juray
            12.04.2018 11:17

            а, ясно. Просто в базовых «учебниках для чайников» такого варианта не находится.


        1. atikhonov
          12.04.2018 13:42

          Да, в любую сторону, и иногда, например в конвейерных пайпах, логичнее и «красивее» записать результат справа (dt%>%filter()%>%group_by()%>%summarize()->res)


      1. Stormwalker
        12.04.2018 19:41

        Но ради всего святого, никогда так не делайте!

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


    1. MacIn
      12.04.2018 18:23

      := из той же оперы, этот аналогично БНФ записи, т.е. это не присвоение как таковое, а указание, что под терминалом, который слева, понимается то, что справа.


    1. evgenyspace
      12.04.2018 19:42

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


  1. Samouvazhektra
    11.04.2018 20:13

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


    1. Johan
      11.04.2018 23:16

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


      1. khim
        12.04.2018 01:50

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

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


        1. juray
          12.04.2018 11:01
          +1

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

          а, ниже разжевали лучше: habrahabr.ru/post/353292/#comment_10750626


          1. khim
            12.04.2018 16:52
            +1

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

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

            Вот как-то так получается, что чем дальше от «математики» и ближе к «железу», тем проще и понятней…


            1. MacIn
              12.04.2018 18:25

              Пошаговое решение проблемы легче обозревается, потому что разбивается на малые, за раз обозримые части. А при функциональном/логическом подходе, напротив, надо всю запись целиком воспринимать одномоментно.


        1. 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 не научится оптимизировать суб-генераторы.


          1. khim
            12.04.2018 20:03

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

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

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

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


            1. sebres
              12.04.2018 20:32

              … но долгий опыт показал мне что я — скорее исключение, чем правило…

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


        1. maxzhurkin
          12.04.2018 23:05

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

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


          1. khim
            13.04.2018 00:10

            И? Почему вдруг «предписание для компьютера» вам стало вдруг проще написать, чем простое описание «исходных данных для преобразований» — примерно как тут?


            1. maxzhurkin
              13.04.2018 07:35

              А когда я утверждал что-то подобное?


            1. MacIn
              13.04.2018 17:25

              Потому что шаг алгоритма легче объять разумом, чем формулу/абстракцию целиком.


  1. velvetcat
    11.04.2018 20:43

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


    1. sebres
      12.04.2018 21:01

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


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


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

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


  1. Platon_msk
    11.04.2018 22:28
    -1

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


    1. Johan
      11.04.2018 22:52

      deleted


    1. Johan
      11.04.2018 23:04
      +1

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


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

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


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


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


      1. Apache02
        12.04.2018 10:46

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

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


        1. juray
          12.04.2018 11:02

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


        1. viktprog
          12.04.2018 11:08

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


          1. Apache02
            12.04.2018 12:02

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


            1. AgentRX
              12.04.2018 12:15

              Как вы не замечаете очевидной разницы между:
              1. x = y + 1 в математике и
              2. x = y + 1 в программировании?

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


            1. zagayevskiy
              12.04.2018 12:16

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


            1. viktprog
              12.04.2018 12:58
              +1

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


              1. Source
                12.04.2018 13:40

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


              1. MacIn
                12.04.2018 18:26

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

                В случае вычислений по данной формуле как раз-таки присваиваем.


            1. Antervis
              12.04.2018 14:00

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

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


  1. Amomum
    11.04.2018 22:43
    +1

    Всегда не любил паскаль именно за ":=" для присваивания. Просто потому что это дольше печатать, чем "=".
    Конечно, зато в паскалье "=" — это сравнение, но как-то так получается, что в коде я на порядок чаще делаю присвоение.


    1. zagayevskiy
      12.04.2018 12:17

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


      1. Source
        12.04.2018 13:42

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


      1. Amomum
        12.04.2018 13:46

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


        1. antonn
          12.04.2018 13:50

          Если я пишу на C#, то в основном нажимаю ctrl+space :)

          В Дельфи есть тоже самое, если рассматривать его синтаксис близким к паскалю из-за ":=".

          Куда более убийстввенным было для меня сравнение строк в яве через .Equals(), тут Дельфийские и Шарповские "="/"==" вспоминаются со слезами.


          1. webkumo
            12.04.2018 18:35

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


          1. Bronx
            13.04.2018 05:56

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

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


    1. rraderio
      12.04.2018 15:34

      А на каком ЯП вы сейчас программируете? уверен что есть ЯП где надо печатать меньше


      1. Amomum
        12.04.2018 17:17
        +1

        Malbolge.


        1. rraderio
          12.04.2018 17:21

          This Malbolge program displays «Hello World!», with both words capitalized and exclamation mark at the end.
          (=<`#9]~6ZY32Vx/4Rs+0No-&Jk)«Fh}|Bcy?`=*z]Kw%oG4UUS0/@-ejc(:'8dc

          print("Hello World!")


  1. mickvav
    11.04.2018 23:04
    +1

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


    1. lgorSL
      12.04.2018 00:08

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


      1. rraderio
        12.04.2018 15:39
        -2

        Go


      1. grossws
        12.04.2018 18:05
        +1

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


  1. Goodkat
    11.04.2018 23:08

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


    1. Johan
      11.04.2018 23:14

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


      1. Goodkat
        11.04.2018 23:28

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


        1. Johan
          11.04.2018 23:36

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


          1. zagayevskiy
            12.04.2018 01:09

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


      1. KvanTTT
        12.04.2018 00:50

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


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


        1. Johan
          12.04.2018 00:56

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


        1. 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()))])"


      1. netch80
        12.04.2018 17:21

        Или потребовав явного слова на это, например

            if (take a = fn(x))
        


        польза — такие случаи (нечастые, но полезные) будут явно выделяться в коде.


    1. kalininmr
      11.04.2018 23:26

      в питоне потому присваивание внутри if и запретили


      1. Goodkat
        11.04.2018 23:31

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


        1. Johan
          11.04.2018 23:41

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


          for r in iterable:
              pass

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


        1. kalininmr
          12.04.2018 00:37

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


    1. DrZlodberg
      12.04.2018 11:20

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


      1. Goodkat
        12.04.2018 11:26

        Так присваивание и сравнение в большинстве (известных мне) языков различается, так что никаких проблем. В том же С это = и ==. Проблема получается
        Проблема получается, когда забываешь написать второй символ "=". Очень частая и сложнонаходимая ошибка.


        1. ZyXI
          12.04.2018 11:31

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


          1. Johan
            12.04.2018 13:32
            +1

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


            1. Source
              12.04.2018 13:51
              +1

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


              1. TimsTims
                12.04.2018 23:05

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


        1. DrZlodberg
          12.04.2018 11:52

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


          1. netch80
            12.04.2018 17:19

            GCC не запрещает if(a=b), но предупреждает о возможной некорректности. И да, за такими предупреждениями надо следить.


            1. DrZlodberg
              12.04.2018 18:38

              Возможно просто привык уже считать это предупреждение ошибкой…


              1. ZyXI
                12.04.2018 23:44

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


    1. webkumo
      12.04.2018 12:16

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


      1. Johan
        12.04.2018 13:34

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


  1. Color
    11.04.2018 23:21

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


  1. Revertis
    11.04.2018 23:42

    Автор не прав. Сначала было:

    let a = 5
    А потом упразднили let :)


    1. pavel_kudinov
      12.04.2018 01:13

      Более того, автор допускает ошибку предпосылки:

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

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

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

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

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

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

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

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


      1. LAutour
        12.04.2018 06:19

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

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


        1. velvetcat
          12.04.2018 08:03
          +1

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


        1. rraderio
          12.04.2018 15:45
          +1

          Можно пример как это на бейсике?


          1. LAutour
            12.04.2018 18:31
            +1

            a = b или let a = b
            if a = b then


            1. rraderio
              13.04.2018 09:17

              let a = b = c

              можно и спутать


              1. MacIn
                13.04.2018 17:27

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


  1. KvanTTT
    12.04.2018 00:52

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


  1. Daniyar94
    12.04.2018 01:53

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


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


    1. SadOcean
      12.04.2018 18:38

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


  1. crystallize
    12.04.2018 06:58

    Здесь и далее- мои рассуждения об этом как человека весьма далёкого от программирования
    hlfx.ru/forum/showthread.php?threadid=4646&postid=149556#post149556


  1. NeoCode
    12.04.2018 08:28

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


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


    1. akryukov
      12.04.2018 09:28
      +2

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


  1. vesper-bot
    12.04.2018 10:00

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


    1. acmnu
      12.04.2018 10:49
      +1

      Мы в институте учили Лисп как строго функциональный язык,

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


    1. Akon32
      12.04.2018 13:12

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

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


      1. vesper-bot
        12.04.2018 18:00

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


    1. Johan
      12.04.2018 13:38

      Программист на Фортране может написать программу на Фортране на любом языке программирования


  1. CryptoPirate
    12.04.2018 11:18

    1. vesper-bot
      12.04.2018 18:02

      Можно объяснить, в чем в той цитате юмор?


      1. kalininmr
        12.04.2018 23:57

        := на череп похож


  1. michael_vostrikov
    12.04.2018 11:28

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


    1. kalininmr
      13.04.2018 20:38

      ещё как рассматривается.
      есть куча методов основанных на итерациях и т.д.


      1. khim
        13.04.2018 21:45

        В «методах, основанных на итерациях», как правило, заводится целый выводок переменных, но каждая из них имеет только одно какое-то значение…


      1. michael_vostrikov
        14.04.2018 05:29

        Напишите математическим языком, как на некоторой итерации вычислить следующее значение i.


  1. amarao
    12.04.2018 15:45

    Всем, кто говорит, что в математике "=" означает равенство «навсегда», вот вам простой пример:

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

    Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?


    1. rraderio
      12.04.2018 16:26

      2 + 2 = 5
      А что тогда "=" означает здесь?


      1. amarao
        12.04.2018 16:49

        Предположение. Если мы считаем, что X+Y=5, то оказывается, что 2+2=5, а второе утверждение ложное, значит и первое утверждение так же ложное.

        Два равно, и оба ложные, и вселенная всё ещё не взорвалась (повторно).


        1. rraderio
          12.04.2018 17:05

          Предположение
          Тогда X = 2 это тоже предположение, т.е. уже не верно что X + Y = 5 и 2 + 2 = 5 это одно и тоже


          1. amarao
            12.04.2018 17:07

            Окей. Но если мы становимся на эту позицию, то что остаётся от математики?

            Если X=2, и Y=2, то X+Y=4. Эти знаки равно что обозначают?


            1. rraderio
              12.04.2018 17:16

              Равенство.
              Если мы знаем что X = 2, Y = 2, то X + Y = 5 это утверждение не истинно, также как не истинно и 4=5.


    1. rraderio
      12.04.2018 16:37
      -1

      X + Y = 5
      Это не валидно в математике, ошибка в определении задачи


      1. amarao
        12.04.2018 16:48
        +2

        Почему не валидно? Есть куча доказательств от противного, и в них всегда приходят к противоречию, это означает, что часть утверждений (со знаком равно) не являются истинными.


        1. rraderio
          12.04.2018 17:10
          +1

          часть утверждений (со знаком равно) не являются истинными
          это я и имел ввиду :)


    1. khim
      12.04.2018 17:09

      Скажите, в этом нехитром упражнении, сколько раз я использовал "=" в странном смысле?
      Вы уж скажите что вы тут понимаете под «странным» смыслом.

      Как я уже писал «математическое» присваивание у нас есть только в Prolog'е и близких к нему языках. И вот как раз на нём мало кто пишет — даже из тех, кто, вроде как, предпочитает «функциональщину».


    1. vesper-bot
      12.04.2018 18:02

      Ни одного. Сделали неверную предпосылку, только и всего.


    1. Bronx
      13.04.2018 06:19

      Ни разу. «Пусть X = 2» — это не присваивание, это логическое утверждение «считаем истинным, что X = 2». Проверка: поменяем все операнды местами:

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

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


  1. third112
    12.04.2018 16:49

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


    1. netch80
      12.04.2018 17:26

      > В математике знак "=" не присваивание, а равенство.

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

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


      1. rraderio
        12.04.2018 17:38

        В предложениях типа «пусть y = 2x» это именно присвоение
        Если это присвоение то «x» уже должен быть определён, а мы можем определить его позже


        1. netch80
          12.04.2018 17:47

          > Если это присвоение то «x» уже должен быть определён

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

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


      1. michael_vostrikov
        12.04.2018 17:57

        В математике: «имеет значение».
        В программировании: «присвоить значение».
        Имеет значение, вычисляемое как… (а как она его получила, неважно).
        Присвоить значение, вычисленное как… (в результате чего станет верным выражение «имеет значение»).


      1. third112
        12.04.2018 18:21

        В предложениях типа «пусть y = 2x» это именно присвоение


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


      1. lgorSL
        13.04.2018 00:52

        В предложениях типа «пусть y = 2x» это именно присвоение (что обозначено дополнительным «пусть»

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


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


        1. juray
          13.04.2018 16:15

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


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


  1. rraderio
    12.04.2018 17:33

    промахнулся


  1. mSnus
    12.04.2018 17:53
    +2

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


    1. grossws
      12.04.2018 18:14

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


    1. juray
      13.04.2018 00:01

      ИП0 ИП1 + П0


      1. MacIn
        13.04.2018 17:29

        БЗ-34? В моем 61 это (П->X)0, (П->X)1, +, (X->П)0


        1. juray
          13.04.2018 17:55

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

          После этих калькуляторов ассемблер — плёвое дело.


  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]);
    


    1. 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.


    1. lgorSL
      13.04.2018 01:26
      +1

      И если в С/С++ это естественно из-за адресной арифметки, то в 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, который сам бегает по любым коллекциям.


      1. capslocky
        13.04.2018 12:21
        -1

        Я считаю, что типичная работа с массивами и их индексами (>97% случаев) в языках типа Java/Javascript/C# не обладает большим удобством из-за выбора 0, а не 1. В будущем будут создаваться новые высокороуровневые языки программирования. И одним из основных аргументов в пользу 1 будет именно большее удобство изучения программирования с нуля школьниками (студентами), у которых еще нет привычки считать индекс с 0.


        1. AnutaU
          13.04.2018 12:40

          Если эти самые новые языки не будут предназначены непосредственно для изучения школьниками, то аргумент «удобство изучения с нуля непрофессионалами» как-то слабоват. Компромиссов в дизайне языков и так слишком много, чтобы добавлять лишние ограничения, которые нужно учитывать (местами за счёт ухудшения других важных параметров).


        1. Antervis
          13.04.2018 13:04
          +3

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


          1. rraderio
            13.04.2018 14:31

            вам наглядно продемонстрировали почему индексация с 0 приводит к более простому коду в любом нетривиальном случае.
            Как получить последний элемент? с индексацией с 1 это будет
            array[length]

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


            1. ZyXI
              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]. Правда, будет ли какой?либо удар по производительности зависит от целевого процессора, окружающего кода, способности компилятора к оптимизации, …


              1. khim
                14.04.2018 01:05

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


                1. ZyXI
                  14.04.2018 01:35

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


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


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


                  1. khim
                    14.04.2018 03:03

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

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

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


                    1. ZyXI
                      14.04.2018 04:03

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


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


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


                      1. khim
                        14.04.2018 05:02

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

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

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

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

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


                1. ZyXI
                  14.04.2018 01:41

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


  1. rraderio
    13.04.2018 14:25

    промахнулся