Я занимаюсь разработкой языка, который намереваюсь заменить как Javascript, так и PHP. (Я не вижу никаких проблем с этим. Не похоже, чтобы у любого из этих языков была большая база установки.)
Одной из вещей, которые я хотел изменить, было превратить оператор присваивания в команду присваивания, исключив возможность использования возвращаемого значения.
x=1; /* Assignment. */
if (x==1) {} /* Comparison. */
x==1; /* Error or warning, I've not decided yet. */
if (x=1) {} /* Error. */
Я знаю, что это будет означать, что те однострочные функции, которые так любят люди C, больше не будут работать. Я понял (с небольшим количеством доказательств, помимо моего личного опыта), что в подавляющем большинстве случаев это происходило, это действительно была операция сравнения.
Либо это? Есть ли практическое использование возвращаемого значения оператора присваивания, которое нельзя было бы переписать тривиально? (Для любого языка, который имеет такое понятие.)
источник
while((x = getValue()) != null) {}
. Замены будут ужаснее, так как вам нужно будет использоватьbreak
или повторитьx = getValue
назначение.Ответы:
Технически, некоторый синтаксический сахар может стоить сохранить, даже если его можно заменить тривиально, если он улучшает читабельность некоторых общих операций. Но присваивание как выражение не подпадает под это. Опасность его опечатывания вместо сравнения означает, что он используется редко (иногда даже запрещено руководствами по стилю) и вызывает двойную реакцию при каждом его использовании. Другими словами, преимущества читабельности невелики по количеству и величине.
Возможно, стоит взглянуть на существующие языки, которые делают это.
if (x)
вместоif (x != null)
или вif (x != 0)
зависимости от типаx
.Однако, Python позволяет использовать один специальный случай, сопоставляя несколько имен сразу:
a = b = c
. Это считается оператором, эквивалентнымb = c; a = b
и иногда используемым, поэтому, возможно, стоит добавить и к вашему языку (но я бы не стал его использовать, так как это дополнение должно быть обратно совместимым).источник
a = b = c
а другие ответы на самом деле не поднимают.:=
для назначения.=
это назначение,==
это сравнение.if (a = true)
будет выдавать предупреждение C4706 (The test value in a conditional expression was the result of an assignment.
). GCC с C будет также броситьwarning: suggest parentheses around assignment used as truth value [-Wparentheses]
. Эти предупреждения могут быть заглушены дополнительным набором скобок, но они предназначены для того, чтобы явно указывать, что назначение было преднамеренным.a = true
действительно вычисляет в логическое значение и поэтому не является ошибкой, но также вызывает соответствующее предупреждение в C #.Вообщем нет. Идея иметь в качестве значения выражения присваивания значение, которое было присвоено, означает, что у нас есть выражение, которое может использоваться как для его побочного эффекта, так и для его значения , и это, по мнению многих, сбивает с толку.
Обычные случаи обычно делают выражения компактными:
имеет семантику в C # «преобразовать z в тип y, присвоить преобразованному значению y, преобразованное значение - это значение выражения, преобразовать его в тип x, присвоить x».
Но мы уже находимся в сфере неосторожных побочных эффектов в контексте заявления, так что на самом деле это очень мало убедительных преимуществ по сравнению с
Аналогично с
быть сокращением для
Опять же, в исходном коде мы используем выражение как для его побочных эффектов, так и для его значения, и мы делаем утверждение, которое имеет два побочных эффекта вместо одного. Оба вонючие; старайтесь иметь один побочный эффект для каждого оператора и используйте выражения для их значений, а не для их побочных эффектов.
Если вы действительно хотите быть смелым и подчеркивать, что назначение - это утверждение, а не равенство, тогда мой совет: сделайте это четко выражением утверждения .
Красный. Или
или даже лучше:
Или еще лучше
Нет абсолютно никакого способа, с которым кто-либо из них будет перепутан
x == 1
.источник
x[⍋x←6?40]
APL требовалась собственная специальная клавиатура, но это был довольно успешный язык.a+b*c --> x
? Это выглядит странно для меня.Многие языки выбирают способ назначения присваивания вместо выражения, включая Python:
и Голанг:
Другие языки не имеют присваивания, а скорее привязки с ограничением, например OCaml:
Однако
let
это само выражение.Преимущество разрешения присваивания состоит в том, что мы можем напрямую проверять возвращаемое значение функции внутри условного выражения, например, в следующем фрагменте Perl:
Perl дополнительно ограничивает объявление только этим условным, что делает его очень полезным. Он также будет предупреждать, если вы назначите внутри условия без объявления новой переменной там -
if ($foo = $bar)
будет предупреждать,if (my $foo = $bar)
не будет.Выполнение присваивания в другом выражении обычно достаточно, но может вызвать проблемы с областью видимости:
Golang в значительной степени полагается на возвращаемые значения для проверки ошибок. Поэтому он позволяет условному элементу принимать оператор инициализации:
Другие языки используют систему типов для запрета не булевых выражений внутри условного выражения:
Конечно, это не помогает при использовании функции, которая возвращает логическое значение.
Теперь мы увидели различные механизмы защиты от случайного назначения:
let
привязкиЯ оценил их в порядке возрастания предпочтений - назначения внутри выражений могут быть полезны (и это легко обойти проблемы Python с помощью явного синтаксиса объявления и другого синтаксиса именованных аргументов). Но это нормально, чтобы запретить их, так как есть много других вариантов того же эффекта.
Код без ошибок важнее краткого кода.
источник
Вы сказали: «Я подумал (с небольшими доказательствами, помимо моего личного опыта), что в подавляющем большинстве случаев это происходило, это действительно была операция сравнения».
Почему бы не исправить проблему?
Вместо = для присвоения и == для проверки на равенство, почему бы не использовать: = для присвоения и = (или даже ==) для равенства?
Заметим:
Если вы хотите, чтобы программисту было труднее ошибиться в назначении на равенство, то усложните задачу.
В то же время, если вы ДЕЙСТВИТЕЛЬНО хотели решить проблему, вы бы удалили крокод C, который утверждал, что логические значения были просто целыми числами с предопределенными символическими именами сахара. Сделайте их совсем другого типа. Тогда вместо того, чтобы сказать
Вы заставляете программиста писать:
Дело в том, что присваивание как оператор - очень полезная конструкция. Мы не уничтожали бритвенные лезвия, потому что некоторые люди порезались. Вместо этого король Джилет изобрел безопасную бритву.
источник
:=
для назначения и=
для равенства может решить эту проблему, но за счет отчуждения каждого программиста, который не вырос, используя небольшой набор неосновных языков. (2) Типы, отличные от того, что bools допускается в условиях, не всегда связаны со смешением bools и целых чисел, достаточно дать истину / ложную интерпретацию другим типам. Более новый язык, который не боится отклоняться от C, сделал это для многих типов, кроме целых (например, Python считает пустые коллекции ложными).if (a = b)
lvalue a, логическое a, b). На языке без статической типизации это также дает намного лучшие сообщения об ошибках (во время анализа против времени выполнения). Кроме того, предотвращение «ошибок a = b и a == b» может быть не единственной важной задачей. Например, я также хотел бы разрешить код, какif items:
означатьif len(items) != 0
, и что я должен был бы отказаться, чтобы ограничить условия булевыми значениями.На самом деле, чтобы ответить на вопрос, да, есть множество применений этого, хотя они немного нишу.
Например в Java:
Альтернатива без использования встроенного присваивания требует
ob
определенного вне области цикла и двух отдельных положений кода, которые вызывают x.next ().Уже упоминалось, что вы можете назначить несколько переменных за один шаг.
Подобные вещи используются чаще всего, но творческие программисты всегда будут придумывать больше.
источник
ob
объект с каждым циклом?Поскольку вы можете составить все правила, зачем теперь разрешать присваиванию поворачивать значение и просто не разрешать присваивания внутри условных шагов? Это дает вам синтаксический сахар для упрощения инициализации, в то же время предотвращая распространенную ошибку кодирования.
Другими словами, сделайте это законным:
Но сделайте это незаконным:
источник
a = b = c
кажется более ортогональным и более простым для реализации. Эти два подхода не согласны с присваиванием в выражениях (a + (b = c)
), но вы их не приняли, поэтому я предполагаю, что они не имеют значения.Судя по всему, вы находитесь на пути создания довольно строгого языка.
Имея это в виду, заставляя людей писать:
вместо того:
может показаться улучшением, чтобы люди не делали:
когда они собирались сделать:
но, в конце концов, такого рода ошибки легко обнаружить и предупредить о том, являются ли они юридическим кодом.
Тем не менее, существуют ситуации, когда:
не значит что
будет правдой.
Если c на самом деле является функцией c (), то она может возвращать разные результаты при каждом вызове. (это может быть слишком дорого в вычислительном отношении ...)
Аналогично, если c является указателем на отображаемое в памяти оборудование, то
оба могут быть разными, а также могут иметь электронные эффекты на аппаратное обеспечение при каждом чтении.
Существует множество других перестановок с аппаратным обеспечением, в которых вам необходимо точно определить, с каких адресов памяти считываются, записываются и с конкретными временными ограничениями, где выполнение нескольких назначений в одной строке происходит быстро, просто и очевидно, без рисков, связанных с синхронизацией. временные переменные вводят
источник
a = b = c
не тоa = c; b = c
, что надоb = c; a = b
. Это позволяет избежать дублирования побочных эффектов, а также сохраняет измененияa
иb
в том же порядке. Кроме того, все эти аппаратные аргументы являются глупыми: большинство языков не являются системными языками и не предназначены для решения этих проблем и не используются в ситуациях, когда эти проблемы возникают. Это вдвойне относится к языку, который пытается вытеснить JavaScript и / или PHP.a=b=c
обычно / полезно, они являются примерами мест, где нужно учитывать порядок и количество побочных эффектов. Это совершенно независимо. Правильно переписать связанное назначение, и оба варианта одинаково верны.b
в одном временном файле, а значение преобразуется в типa
в другом временном. Относительное время, когда эти значения фактически сохраняются, не определено. С точки зрения языкового дизайна, я считаю разумным требовать, чтобы все lvalues в операторе множественного присваивания имели совпадающий тип, и, возможно, также потребовать, чтобы ни одно из них не было изменчивым.Самое большое преимущество, которое я имею в виду того, что присваивание должно быть выражением, состоит в том, что оно позволяет упростить вашу грамматику, если одна из ваших целей состоит в том, что «все является выражением», в частности цель LISP.
Python не имеет этого; у него есть выражения и утверждения, назначение - утверждение. Но поскольку Python определяет
lambda
форму как одно параметризованное выражение , это означает, что вы не можете назначать переменные внутри лямбда- выражения . Иногда это неудобно, но не является критической проблемой, и это мой единственный недостаток в моем опыте - иметь назначение в Python.Один из способов, позволяющих присваивать или, точнее, эффект присваивания быть выражением, не представляя возможности для
if(x=1)
несчастных случаев, которые имеет C, - это использовать LISP-подобнуюlet
конструкцию, такую,(let ((x 2) (y 3)) (+ x y))
которая в вашем языке может оцениваться как5
. Использованиеlet
этого способа технически не обязательно должно быть назначением на вашем языке, если вы определяетеlet
как создание лексической области видимости. Определенный таким образом,let
конструкция может быть скомпилирована так же, как конструирование и вызов вложенной функции замыкания с аргументами.С другой стороны, если вы просто озабочены
if(x=1)
регистром, но хотите, чтобы присваивание было выражением, как в C, возможно, достаточно будет просто выбрать другие токены. Назначение:x := 1
илиx <- 1
. Сравнение:x == 1
. Синтаксическая ошибка:x = 1
.источник
let
отличается от присваивания большим количеством способов, чем технически введение новой переменной в новую область видимости. Начнем с того, что он не влияет на код за пределамиlet
тела и, следовательно, требует дальнейшего вложения всего кода (что должно использовать переменную), что является существенным недостатком кода с интенсивным присваиванием. Если идти по этому пути,set!
будет лучшим аналогом Lisp - совершенно не похоже на сравнение, но не требует вложения или новой области видимости.Верно. В этом нет ничего нового, все безопасные подмножества языка Си уже сделали такой вывод.
MISRA-C, CERT-C и т. Д. На все запреты присваиваются внутри условий просто потому, что это опасно.
Не существует случая, когда код, основанный на присваивании внутри условий, нельзя переписать.
Кроме того, такие стандарты также предупреждают против написания кода, который опирается на порядок оценки. Многократные назначения в одной единственной строке
x=y=z;
- такой случай. Если строка с несколькими присваиваниями содержит побочные эффекты (вызов функций, доступ к переменным переменным и т. Д.), Вы не можете знать, какой побочный эффект произойдет первым.Между оценками операндов нет последовательностей. Таким образом, мы не можем знать,
y
оценивается ли подвыражение до или послеz
: это неопределенное поведение в C. Таким образом, такой код потенциально ненадежен, непереносим и не соответствует упомянутым безопасным подмножествам C.Решением было бы заменить код на
y=z; x=y;
. Это добавляет точку последовательности и гарантирует порядок оценки.Таким образом, основываясь на всех проблемах, которые это вызвало в C, любой современный язык должен был бы преуспеть как в запрете назначений внутри условий, так и в нескольких назначениях в одной строке.
источник