Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Прочее

Операции присвоения и проверки на равенство. Возможно ли однаковое обозначение?

Всегда был интересен ответ на вопрос: почему в одних языках для проверки на равенство и для присвоения чему-то какого-то значения можно использовать одно и то же обозначение — «=», а вдругих языках используют разные обозначения?

Рассмотрим примеры:

C/C++:          if (a == 0)
                   a = b;

Pascal:		if a = 0 then
                   a := b;
                end

Clipper:	if a = 0
                   a = b
                endif

Euphoria:       if a = 0 then
                   a = b
                end if
равно и присвоить
        Как видим, в Clipper и Euphoria для проверки равенства и присвоения используется один и тот же символ «=». А вот в C/C++ и в Pascal для операций проверки равенства и присвоения выбраны разные обозначения: в C/C++ это «==» и «=», в Pascal это «=» и «:=». Поскольку суть этих операций разная, то и обозначение разные. Но хотелось бы, чтобы и то, и другое имело одинаковое обозначение «=». Как в языках Clipper или Euphoria. Возможно ли это? Почему то, что не возбраняется Clipper, то возбраняется C/C++/Pascal? Я нигде не встречал ответ на этот вопрос. Попробую дать свои соображения по этому поводу.

        В языке Euphoria проверка на равенство — это операция, возвращающая значение «истина» или «ложь». Присвоение же — это команда, которая не возвращает значения. Т.е. в терминах C/C++ — это функция типа void, а в терминах Pascal — это процедура. Как компилятор понимает, что в каком-то конкретном случае востребована операция проверки равенства, а в каком команда присвоения? А вот так: компилятор просто рассматривает контекст, в котором употреблён знак «=».

        А Clipper? Хотя в его последних версиях появились новые обозначения операций («==» для проверки равенства и «:=» для присваивания), но они не отменяли старого стиля и обозначение «=» по-прежнему могло применяться для обеих операций.

         Как устроены выражения в типизированных языках? В выражении «x = a + b» a и b имеют свои типы. В зависимости от этих типов выбирается наиболее подходящая операция «+» из множества операций «+». Это называют перегрузкой операций. Если a и b – это числа с плавающей точкой, то к ним будет применена операция сложения с плавающей точкой, а не операция сложения целых чисел. Этот выбор «+» не зависит от того, куда потребуется результат сложения, в нашем случае он должен появиться в правой части операции присваивания. Проще говоря, «+» не знает, что будет после сложения и кому понадобился результат этого сложения.

         Далее наступает черёд операции «=». Если левой части операции присваивания потребуется число с плавающей точкой, то среди одноимённых будет выбрана операция присваивания чисел с плавающей точкой. Если слева — операнд целого типа, то среди одноимённых будет выбрана операция присваивания целых чисел, но перед присвоением будет проведено преобразование значения к целому типу.

         А может ли операция «+» «подглядеть», какой тип понадобится дальше? Чтобы вернуть значение именно того типа, который требуется? Не всё так просто! Допустим, если одна операция может выдать результат типа type_a и типа type_b, а другая — принять результат типа type_a и типа type_b, то на каком варианте должен остановиться компилятор? Могут возникнуть двусмысленность, неоднозначность. Какими правилами надо руководствоваться в таких ситуациях?

         Другое дело, если мы имеем дело с безтиповыми языками типа Clipper. Данные в таких языках, конечно же, имеют тип, но информация об этом типе доступна во время выполнения программы. В таких языках нет множества одноимённых операций, каждая операция единственна и неповторима. Поэтому операция «=» может подсмотреть в каком контексте она употреблена. Если она употреблена в условном выражении, то от неё требуется сравнивать операнды, в противном случает она присваивает значение. В Clipper нет возможности переопределять операции, в том числе и «=». Но даже если захочется написать 2 одноимённые функции, одна из которых проверяла бы равенство, а другая выполняла бы присвоение, то и это невозможно: не может быть одноимённых функций. Но даже если была бы и эта возможность, то Clipper не предоставляет программистам инструментов анализа контекста употребления.

         Таким образом, в бестиповых языках может существовать одновременное использование знака «=» как для проверки равенства, так и для присвоения. Но, во-первых, эту операцию нельзя будет переопределить, ибо нельзя различить контекст употребления. И, во-вторых, возникнет другая проблема. Вне условного выражения можно записать:
a = (b == c)	// a – истина, если b равно c; иначе – ложь.
         А как заставить операцию «=» заставить проверять равенство вне конструкции «if»?
a = (b = c)	// выполняется присваивание a = b = c, а хотелось бы:
if (b = c)
	a = true
else
	a = false
         Какова мораль сей басни? А такова: в языках со статической типизацией и перегрузкой операций лучше забыть об одинаковом обозначении для операций проверки равенства и присвоения.

Последняя правка: 2014-12-23    16:37

ОценитеОценки посетителей
   ████████████████████████ 20 (57.1%)
   ████ 3 (8.57%)
   █████ 4 (11.4%)
   ██████████ 8 (22.8%)

Отзывы

     2013/05/07 16:01, 192.168.0.16, 217.66.18.132

По-моему, всё проще.

C/C++

Знак "=" является обозначением функции, которая возвращает вполне определённое значение (это отрыжка ассемблеров, в которых возвращаемое значение - это содержимое процессорного регистра). По этой причине смысл знака "=" в C/C++ существенно отличен от смысла, вкладываемого в этот знак в других языках. В частности, это выражается в том, что конструкция

if (a = b) { ...; }

трактуется принципиально иначе, чем в других языках: внутри скобок не сравнение, а вызов функции, чьё возвращаемое значение анализируется оператором if. Именно поэтому, а вовсе не из-за типизации, в C/C++ приходится использовать отдельные обозначения для функции простого сравнения и для функции присвоения с возвращением присвоенного значения.

В Паскале ограничение на использование разных знаков для сравнения и присваивания -- это исключительно прихоть Вирта. Просто с целью строгого выполнения принципа "разные функции/процедуры -- разные имена". И дело тут не в типовых-бестиповых языках, а в использовании разработчиками такого понятия, как сигнатура функции. В частности, никто не мешает разработчикам языков и компиляторов использовать такой подход для определения контекста использования двухаргументной функции "=":
OP_EQ(int&, int) -- сигнатура присваивания;
OP_EQ(int, int) -- сигнатура сравнения;

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

OP_EQ(a, b) -- присвоение значения переменной a;
OP_EQ((a), b) -- сравнение выражений =a и =b.

Сразу становится понятным ответ на следующий вопрос:

А как заставить операцию «=» заставить проверять равенство вне конструкции «if»?

Заставить элементарно. Надо обозначить контекст использования. Например, так:

a = ((b) = c)

     2013/05/07 22:29, Автор сайта

То, что «=» возвращает результат – это не отрыжка ассемблеров, а стремление к тому, чтобы любое действие возвращало результат. Фортран был ближе к ассемблеру, но там такого не было. Но уже тогда задумывались о функциональном программировании.

У Вирта была не прихоть, а стремление сделать язык проще и прозрачнее. Ритчи ведь тоже дал этим разным операциям разные обозначения.

Функции OP_EQ(int&, int) и OP_EQ(int, int) компилятор не сможет отличить друг от друга: в случае вызова OP_EQ(a, b) какая из функций должна исполняться? А что является результатом (a)? Сама переменная a. Поэтому (a) == b и a == b дают один и тот же результат. Неоднозначно как-то всё. Противоречиво.

С «математической» точки зрения запись b = c и (b) = c в смысловом отношении ничем не отличаются. В математике скобки лишь повышают приоритет операций. В С++ семантика скобок слишком перегружена. Это не только повышение приоритетов операций и вызов функций, но и приведение типов: (int)a и int(a). Наделять скобки ещё одним «магическим» смыслом не стоит. Краткость и простота могут являться самостоятельной ценностью. Уж лучше тогда a = (if b = c), чем a = ((b) = c). Но a = (b == c) всё же лучше.

     2014/01/16 08:57, Pensulo

В VisualBasic один и тот же символ "=" используется и для операции сравнения и для команды присвоения. Работает железобетонно ;)

     2014/01/16 09:10, Автор сайта

Цитирую себя: а как заставить операцию «=» заставить проверять равенство вне конструкции «if»? a = (b = c)?

     2014/01/16 10:03, Pensulo

Приведённый вами пример будет работать на VisualBasic следующим образом:
Самый левый символ "=" будет воспринят как команда присвоения переменной a результата выражения записанного правее от него "(b=c)".
А все знаки "=" в этом выражении расцениваются уже как операции сравнения (логического или числового в зависимости от типов операндов).
Но сдаётся мне вы и без моей расшифровки это знаете.
Тогда, простите, не понял сути вопроса.

     2014/01/16 10:22, Автор сайта

Смысл выражений должен быть прозрачен. Что должно означать взятие какого-то выражения в скобки? Ничего, кроме повышения приоритета выполнения. Если скобки будут иметь ещё какой-то смысл, то ясности будет меньше.

     2014/01/18 17:47, Pensulo

В VisualBasic следующие два выражения полностью идентичны:
a = (b = c)
a = b = c
А скобки действительно только лишь повышают приоритет.

     2013/12/18 22:00, Автор сайта

Был вопрос: как проверить два значения на равенство вне конструкции «if», при этом используя только «=»?

     2014/01/19 18:08, Pensulo

Ответ:
a = b = c
Или:
a = (b = c)
И без всяких конструкций if
В переменную a будет положен результат сравнения двух переменных b и c

     2014/01/20 16:20, Автор сайта

Понимаете, в Си (и десятке других языков) «a = b = c» означает «b присвоить значение c, затем a присвоить значение b, которое перед этим получило значение c». Если в VisualBasic это работает по-другому, то на основании чего второй знак «=» трактуется как проверка на равенство? По каким правилам происходит отличие операций «присвоить» и «проверить на равенство»?

     2014/01/21 14:04, Pensulo

Правило различения смысла знака "=" в VisualBasic есть и весьма простое:
В выражениях все знаки "=" означают операцию сравнения.
Есть ещё самостоятельная команда присвоения полная форма которой выглядит следующим образом:
Let var = something
Где "Let" - служебное слово собственно и обозначающее команду присвоения.
"var" - имя переменной которой будет присвоен результат вычисления выражения "something".
Так вот служебное слово "Let" позволено опускать и тогда первый слева знак "=" всегда трактуется как команда присвоения.
Само собой разумеется, что выражения в VisualBasic никогда не могут содержать команду присвоения, но это вполне себе терпимая расплата взамен на освобождение от "обязанности" употреблять два разных знака для обоих описанных в статье случаев.

PS:
Кстати, в VisualBasic есть ещё конструкция вида:
Set obj = something
Служит она для выполнения операций по копированию ссылок на объектные типы данных (в противоположность команде Let, которая выполняет копирование в памяти самих объектов) и вот в этом случае служебное слово "Set" опускать уже не разрешается.

     2014/01/21 21:37, Автор сайта

Не знаток VisualBasic, но вроде бы ООП в нём половинчатое. В C++ можно перегружать operator”=”  и operator”==”, а как это сделать в VisualBasic, если у них одинаковое обозначение? В VisualBasic присвоение «=» – это оператор (не утверждаю, а подозреваю), а в Си – это операция, возвращающая результат. Казалось бы, зачем возвращать результат, если левый операнд всё равно станет равным правому? Да нет, такое бывает не всегда:
int  a, b;
a = 10;
double c = 2.3;
a += (b = c);
«а» будет присвоено значение 12.

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

А мне видится такая расплата: невозможно переопределить операции присвоения и проверки на равенство (ведь у них одинаковое обозначение), это удар по многим вещам в ООП и обобщённом программировании (в C++ это шаблоны, STL).

Если хотите – можете выразить свою точку зрения отдельной статьё на этом сайте, просто напишите на mail(аt)compiler.su, обсудим, поговорим.

     2014/01/31 11:14, Pensulo

Простите за мою навязчивость, но мне удобнее ответить "по-месту".
Да действительно, ООП в VisualBasic заметно отличается от принятого в C++ "стандарта". В VB вообще не допускается создавать, наследовать и соответственно переопределять ОПЕРАЦИИ. Более того в VB (я имею ввиду "классически" VB, а не VB.NET) нет наследования классов. Вместо него используется подход имплементации интерфейсов, ну или подход внедрения классов (включение исходного "наследуемого" класса внутрь разрабатываемого "наследующего" класса с предоставлением доступа к исходному классу через одноимённые члены разрабатываемого класса), чего в принципе достаточно для употребления COM. Но давайте на чистоту - действительно ли в ЯВУ невозможно обойтись без возможности создавать, наследовать и переопределять операции?! А сколько неоднозначностей и потенциальным мест для трудно изобличаемых ошибок может породить такая сверх-гибкость языка? Сложность компилятора с такого языка растёт как на дрожжах, требования к уровню владения языком для программиста его использующего также повышаются.
А ежели всё-таки следовать принципу максимального употребления принципов ООП до конца, тогда давайте включим в список требований ещё и множественное наследование.

     2014/01/31 15:57, Автор сайта

Понимаете, в чём дело. Есть такая штука, как обобщённое программирование, а в ней есть возможность строить произвольные контейнеры. Это некие множества. Для программирования на высоком уровне абстракций необходимо, например, уметь сравнивать элементы множеств. Например, необходимым условием сортируемости этого множества является наличие операции «меньше или равно» для классов, экземпляры которых хранятся в множестве. Если нет такой операции – то нет и стандартной сортировки, её придётся делать самому. Если для Вашего класса определены операции «==» и «=», то это даёт дополнительные возможности для манипулирования объектами написанного вами класса. Вы определяете некие стандартные операции, и в результате можете пользоваться большим количеством алгоритмов.

Вот в чём одна из выгод переопределения операций.

Конечно, есть и оборотная сторона медали в виде сложности компилятора и сложности самого языка. Поэтому хотелось бы, сохранив возможности, попробовать упростить некоторые вещи типа шаблонов C++. Мне не очень нравится то, как сделаны эти шаблоны.

     2014/02/04 12:21, Pensulo

Где можно популярно просветиться на тему обобщённого программирования без погружения в материал с головой? А также про методы его самостоятельной реализации в языках программирования на подобии следующей
реализации ООП в FORTH.

     2014/02/04 18:44, Автор сайта

Думаю, хорошо было бы познакомиться с идеями Алексея Степанова, с его библиотекой STL. Алексей в своё время добился такого изменения C++, чтобы в нём появились шаблоны и стала возможной STL. Но читать Степанова «без погружения в материал» невозможно.

Надо заметить, что обобщённое программирование не есть ООП, первое лишь опирается на последнее.

     2014/02/06 08:59, Настя

в (си) Дан текст произвольной длины, оканчивающийся «;». Проверить есть ли в тексте скобки.

     2014/02/06 13:44, Автор сайта

Настя, у Вас, наверное, сессия?

int  est_li_skobki(char* s)
{ int i;
  for (i=0; i < strlen(s); ++i)
    if (s[i] == '(' || s[i] == ')')
      return  1;
  return  0;
}
Вам всё-таки лучше самостоятельно всё это изучать.

     2014/04/23 02:55, Utkin

Таким образом, в бестиповых языках может существовать одновременное использование знака «=» как для проверки равенства, так и для присвоения. Но, во-первых, эту операцию нельзя будет переопределить, ибо нельзя различить контекст употребления.

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

А как заставить операцию «=» заставить проверять равенство вне конструкции «if»?

Анализируя подобную ситуацию как частный случай. Чисто с технической стороны никаких проблем нет. Да, это будет немножко медленно транслироваться и для интерпретатора будет небольшое падение в скорости (и то, если без байткода). Здесь могут возникнуть вопросы у борцов за  чистоту идеи. Дескать, не понятно, а потому не кошерно.
Совершенно не увидел у автора причин для беспокойства. И типовые и безтиповые языки могут позволить такое поведение. Просто в С++ это выстрел в ногу, на котором кстати часто попадаются новички, в Паскале Вирт сразу запретил такое безобразиe (и на мой взгляд правильно сделал).

     2014/10/28 07:04, Котятка

Да, это работает!

     2014/11/07 13:54, Сергей

На мой взгляд, разделение операций имеет большое преимущество в понимании программы человеком. В теории можно много от чего отказаться с сохранением формальной возможности скомпилировать в корректный код. Например, в языке PL/1 нет зарезервированных слов, поэтому конструкция вида "if else then then else else" могла быть скомпилирована и иметь вполне корректную сематику. Однако, это приводит к сложностям уже на стадии лексического разбора, про понимание программистом вообще молчу. Именно по этой причине следует избегать двусмысленных коснтрукций, таких как "a = b = 1" в предположении, что оператор "=" может быть как присваиванием, так и сравнением. В случае разных операторов двусмысленности не возникает.

     2015/11/02 01:31, Nick

Автор сайта, вы не правы...
Первый комментатор про С/С++ написал правду, utkin про Вирта и Сергею тоже плюс

     2016/03/25 21:20, rst256

Что тут спорить все же просто. Разве не очевиден конфликт? если присвоение и равенство обе применимы в lvalue - левое значение или выражение, они там будут неотличимы друг от друга. Можно смотреть по типу(запрет присваивать булеан?), можно первую считать присваиванием(if(x==1) тогда как будет?), т.д.
Если присвоение не lvalue то все легко и БЕЗОПАСНО, тоже касается и обратного разное обозначение позволяет применять присваивание как выражение, тоже БЕЗОПАСНО, кроме случая когда применяется мягкое отсечение для операций (нет обязат. разделителя операций, в си -";") смежные присваивания присваивания сольются в одно.
Остальное на совести разработчиков, разрешать или нет то или иное.

     2018/05/23 21:57, Александр Коновалов aka Маздайщик

Как было правильно замечено выше, в Visual Basic (классическом) оператор присваивания и операция сравнения записываются одним знаком «=». В языках Си, Си++ и их потомках так нельзя из-за того, что любое выражение является оператором. Т.е. явно бессмысленная конструкция
f()+g();
тоже будет корректным оператором (но некоторые компиляторы могут выдать предупреждение). А операция присваивания является одним из допустимых двуместных операторов наряду с операцией сравнения. И поэтому им нужны разные обозначения.

В Visual Basic «голые» выражения операторами быть не могут. Оператором может быть либо оператор присваивания
x = 10
либо оператор вызова процедуры (или функции с игнорированием возвращаемого значения):
DrawLine 0, 0, 10, 20
Замечу, что в вызове процедуры скобки вокруг параметров можно не писать.

В Паскале то же самое, что и в Бейсике, просто Вирт из идейных соображений решил использовать два разных знака.

Поэтому, если хочется использовать один знак и для присваивания, и для сравнения, придётся ввести оператор (statement) присваивания и запретить быть присваиваниям внутри выражений. Да, если хочется переопределять операции для собственных типов в духе C++ (определяя методы/функции со специфическими именами вида operator@), то для statement’а присваивания придётся придумывать какое-нибудь другое обозначение. Вроде statement=. Или, на манер деструкторов ~Widget(), =Widget(const Widged& rhs) либо Widget=(const Widget& rhs).

     2018/05/25 00:52, Александр Коновалов aka Маздайщик

Оффтопик.
int  est_li_skobki(char* s)
{ int i;
for (i=0; i < strlen(s); ++i)
if (s[i] == '(' !! s[i] == ')')
return 1;
return 0;
}
Даёшь структурное программирование! Во имя Дейкстры!
int  est_li_skobki(char *s) {
  while (*s != '\0' && *s != '(' && *s != ')') {
    ++s;
  }
  return *s != '\0';
}

Написать отзыв

Написать автору можно на электронную почту mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Каким должен быть язык программирования?

Анализ и критика

Устарел ли текст как форма представления программы

Русский язык и программирование

Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

Некоторые «вкусности» Алгол-68

«Двухмерный» синтаксис Python

Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

Должна ли программа быть удобочитаемой?

Стиль языка программирования

Тексто-графическое представление программы

●  Разделители

●  Строки программы

●  Слева направо или справа налево?

Комментарии

●  Длинные комментарии

●  Короткие комментарии

●  Комментарии автоматической генерации документации

●  Нерабочий код

Нужны ли беззнаковые целые?

Шестнадцатиричные и двоичные константы

Условные операторы

Переключатель

Циклы

●  Продолжение цикла и выход из него

Некошерный «goto»

Операции присвоения и проверки на равенство. Возможно ли однаковое обозначение?

Так ли нужны операции «&&», «||» и «^^»?

Постфиксные инкремент и декремент

Почему в PHP для конкатенации строк используется «.»?

Указатели и ссылки в C++

Использование памяти

Почему динамическое распределение памяти – это плохо

Как обеспечить возврат функциями объектов переменной длины?

●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●  Массивы переменной длины в C/C++

●  Размещение объектов в стеке, традиционный подход

●  Размещение объектов переменной длины с использованием множества стеков

●  Размещение объектов переменной длины с использованием двух стеков

●  Реализация двухстековой модели размещения данных

●  Двухстековая модель: тесты на скорость

●  Размещение объектов переменной длины с использованием одного стека

Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

Безопасность и размещение объектов переменной длины в стеке

Массивы, структуры, типы, классы переменной длины

О хранении данных в стеке, вместо заключения

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Прочее

Последние комментарии

2018/06/14 00:37, rst256
Лень — двигатель прогресса

2018/05/31 18:52, rst256
Программирование без программистов — это медицина без врачей

2018/05/31 17:57, rst256
Циклы

2018/05/31 17:50, Comdiv
Разбор цепочек знаков операций

2018/05/31 17:42, Comdiv
Как отличить унарный минус от бинарного

2018/05/30 18:57, Александр Коновалов aka Маздайщик
Раскрутка компилятора

2018/05/29 21:52, Автор сайта
Указатели и ссылки в C++

2018/05/28 20:29, Александр Коновалов aka Маздайщик
Анонс будущих статей