Я занимаюсь изучением синтаксиса языков программирования и пытаюсь отделить фундаментальные закономерности от исторических случайностей. Одна из таких случайностей - почти повсеместное использование терминов выражение (expression) и инструкция (statement) при описании и классификации синтаксиса языков программирования.

Интуитивно кажется, что 2 + 2 и if (x) { foo(); } - это совершенно разные сущности, но при более глубоком анализе оказывается, что подобное разделение искусственное и возникло из-за архитектурных особенностей вычислительных машин почти полвека назад и с тех пор просто «переходит» из языка в язык.

Часть 1. Историческая

Чтобы понять, откуда взялось разделение синтаксиса на выражения (expression) и инструкции (statement), нужно проследить эволюцию синтаксических правил от первых языков высокого уровня.

FORTRAN -> ALGOL 60 -> C: три акта одной пьесы

  • FORTRAN (1957) - разделение «по железу». В языке, созданном для IBM 704, выражения (X + Y*Z) были строго отделены от управляющих инструкций (IF, DO, GOTO). Первые вычисляли значения, вторые управляли потоком исполнения и смешивать их между сбой было нельзя. Эта концепция синтаксиса напрямую отражала архитектуру вычислительных машин того времени: программа понималась как последовательность машинных команд, каждая из которых либо вычисляла операнд, либо изменяла счётчик команд, но не одновременно.

  • ALGOL 60 (1960). В ALGOL 60 ввели составной оператор begin ... end (прообраз блока), но важнее другое: язык разрешил условное выражение. Конструкция if B then E1 else E2 могла появляться в позиции любого выражения, а значит, допускала запись

    x := if a > b then a else b
    

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

  • C (1972) - фиксация различий Expression vs Statement. Деннис Ритчи (при участии Кена Томпсона, автора языка-предшественника B), проектируя C как «переносимый ассемблер» для PDP-11, закрепил в грамматике чёткое различие между этими понятиями. Такое решение диктовалось стремлением к максимально прямому отображению конструкций языка на машинные инструкции: условный переход jz BEQ процессора - это действие, а не способ вычислить значение.

Lisp и параллельная вселенная без statement’ов

Почти синхронно с FORTRAN, в 1958 году, Джон Маккарти создал Lisp, фундаментально основанный на лямбда-исчислении. Здесь нет понятия statement - вся программа состоит из S-выражений: будь то вызов функции, условная конструкция или блок.

В Лиспе конструкция (if (> a b) a b) - это просто выражение, значение которого можно присвоить, передать в функцию или использовать в более сложной композиции. А блок (let ((x 5)) (+ x 1)) возвращает 6, оставаясь обычным выражением.

Это не «расширение» возможностей, а фундаментальный иной принцип: вся программа - это вычисляемые значения, а не последовательность управляющих инструкций. С этой позиции разделение на expression и statement выглядит ненужным.

Часть 2. Современные языки

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

Классическая модель: C, C++, Java (и Python)

В этих языках if, for, while категорически не могут быть частью выражения. Код

int y = if (x > 0) { 1; } else { 2; }   // ошибка компиляции

недопустим. Разработчики вынуждены либо использовать тернарный оператор:

int y = (x > 0) ? 1 : 2;

либо (в C/C++) прибегать к нестандартным расширениям вроде GCC statement expressions:

int y = ({ if (x > 0) 1; else 2; });    // только с расширениями GCC

В Python ситуация аналогична: if - это инструкция, но начиная с версии 2.5 введено тернарное выражение:

y = 1 if x > 0 else 2

Эта конструкция - точный аналог тернарного оператора ?:, «заплатка» для грамматики, которая не решилась сделать if/else полноценным выражением.

JavaScript: эмуляция выражения через функцию

JavaScript формально сохраняет C-подобное разделение, однако в язык встроены обходные инструменты. Используя IIFE (немедленно вызываемые функциональные выражения), разработчик может «обернуть» statement-логику в контекст, где требуется значение:

let y = (function() {
    if (x > 0) return 1;
    else return 2;
})();

До появления современных JIT-оптимизаций подобный трюк нёс реальные накладные расходы на вызов функции, но он наглядно демонстрирует ручную эмуляцию expression-грамматики поверх statement. Показательно, что раз сообщество идёт на подобные ухищрения - значит, потребность в таком подходе велика.

Ruby: всё есть выражение

В Ruby любая конструкция - выражение, без исключений. Можно написать:

y = if x > 0
  1
else
  2
end

z = case value
    when 1 then "one"
    when 2 then "two"
    else "other"
    end

w = while false
  # тело не выполняется
end  # w равно nil, но сам while - валидное выражение

Даже определение класса или метода возвращает значение последнего выражения. Грамматика Ruby не вводит отдельного нетерминала «statement» - вся программа состоит только из выражений.

Rust: гибрид - ни нашим ни вашим

Rust предлагает своеобразный компромисс: формально в грамматике различие между statement и expression есть (let-декларации, объявления элементов - это не expression), но управляющие конструкции (if, match, loop) целиком отнесены к категории Expression - в отличие от C/C++, где это принципиально невозможно.

При этом в Rust есть немало запутанных правил вокруг конструкций, которые вроде бы expression, но ведут себя странно в зависимости от контекста. Например:

let x = while true { break 5; };  // ОШИБКА! while не умеет так

тогда как очень похожий loop - умеет:

let x = loop { break 5; };  // ОК! x == 5

Различие между loop и while/for в том, что компилятор не может гарантировать, что тело while/for вообще выполнится хоть раз (условие может быть ложным с самого начала), тогда как loop либо бесконечен, либо выходит через break с конкретным значением. Эта асимметрия поведения чисто техническая, но принципиально влияет на синтаксис этих конструкций.

Или вот if со скобками и без них ведёт себя неожиданно:

fn f() -> i32 {
    if x > 0 { 1 } else { 2 } - 1
}

Кажется, что это должно вычислить (if...) - 1. Но нет! Компилятор трактует if {...} else {...} как отдельный statement (потому что он стоит не в «хвостовой» позиции блока), а - 1 - как отдельное выражение - унарный минус. Функция вернёт ошибку типов, потому что последней строкой оказывается -1, а значение if-выражения будет просто отброшено.

Чтобы получить ожидаемое поведение, нужно явно обернуть if в скобки:

let y = (if x > 0 { 1 } else { 2 }) - 1;  // так работает как надо

Но самым странным является поведение точки с запятой, которая может менять тип функции:

fn f() -> i32 {
    5 + 3;   // с точкой с запятой!
    10
}

А если поставить ; после последней строки:

fn f() -> i32 {
    5 + 3;
    10;      // теперь тут ;
}
// ОШИБКА: функция должна вернуть i32, а вернула ()

; (точка с запятой) в Rust - это не просто «конец строки», а оператор, буквально меняющий тип выражения на () (пустой тип). Забытая или лишняя точка с запятой - самая частая причина неочевидных ошибок среди новичков.

Rust действительно продвинулся дальше C в сторону «всё есть expression», но это не единый принцип, а набор точечных, местами противоречащих друг другу правил, каждое из которых решает конкретную инженерную задачу (типовую безопасность, гарантии терминации, разрешение неоднозначности парсинга), а не следствие одной красивой идеи, последовательно применённой везде.

Часть 3. Что на самом деле отличает Statement от Expression?

Если вернуться к изначальному вопросу, так чем же отличается statement от expression?

Отвергнутые критерии

  1. «Statement - выполнение, Expression - вычисление» - оба термина связаны с вычислительными действиями, разница не в природе операции.

  2. «Statement не возвращает значение» - опровергается Ruby, где всё возвращает значение.

  3. «Statement - отдельная категория грамматики» - это просто описание симптома (того, что в конкретном языке зафиксировано на уровне BNF), а не объяснение принципиальных различий между терминами.

Более продуктивным оказывается взгляд на пару statement/expression как на структурную композицию, в которой:

  • Expression - неделимая лексическая единица: литерал, идентификатор, вызов функции, математическая операция.

  • Точка с запятой ; (или её аналоги - перевод строки в Ruby) играет роль оператора-разделителя, который сообщает компилятору: нужно вычислить выражение слева, отбросить его значение и перейти к правой части. Цепочка из нескольких выражений, последовательно соединённых ; и образует statement.

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

Таким образом, statement - это не альтернативная категория синтаксиса, а композиция одного или нескольких выражений, соединённых оператором ;, где значение всей цепочки равно значению последнего элемента. Запись:

expression1;
expression2;
...
expressionN

можно прочитать как (expression1 ; expression2 ; ... ; expressionN), и её значением является значение expressionN. А если нужно оформить такую цепочку как единое выражение, то её помещают в фигурные скобки:

{ expression1; expression2; ...; expressionN }

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

А как же оператор запятая в C/C++?

Оператор запятая в C/C++ очень похож на реализацию описанной формальной модели - композицию выражений:

int y = (foo(), bar(), 42);   // foo() и bar() вычислены ради побочного эффекта, y == 42

Но есть принципиальное отличие: оператор запятая - это всегда Expression и не может выйти за пределы одного expression-контекста.

int y = foo(), bar();   // Это НЕ comma-expression! Это два объявления (переменной и функции)!
int z = (foo(), bar());  // а вот это - настоящий comma operator, нужны скобки

Без явных скобок оператор , (запятая) в контексте объявления переменных или списка аргументов функции интерпретируется грамматикой иначе - как разделитель в списке (declarator-list, argument-list). И эта неоднозначность грамматики C/C++ разрешается только контекстом.

Кроме того, оператор ,(запятая) ограничен уровнем expression и не может содержать statement-конструкции:

int y = (if (x > 0) 1; else 2, 42);   // ОШИБКА - if не Expression, нельзя вставить внутрь ","

И напоследок, comma operator имеет самый низкий приоритет среди операторов C, и область его применения жёстко ограничена контекстами, где парсер может быть точно уверен, что это не разделитель списка:

f(a, b, c);   // это вызов с тремя аргументами, а не comma-expression!
f((a, b), c); // а вот это уже - comma-expression как первый аргумент

Часть 4. Формальная грамматика универсального синтаксиса

Если попробовать описать expression и statement в общем виде, получится красивая взаимная рекурсия между ними:

Program     := Statement

Expression  := atomic-expr
             | Expression binary-op Expression
             | unary-op Expression
             | Expression '(' arg-list ')'          // вызов функции
             | '{' Statement '}'                     // сгруппированный Statement - тоже Expression!

Statement   := Expression
             | Expression ';' Statement              // рекурсивная композиция через ;

arg-list    := Expression (',' Expression)*

Где:

  • Statement - это либо одно выражение, либо выражение, за которым следует ; и новый statement (рекурсивно).

  • Expression - это атомарное выражение (идентификатор, литерал, операция, вызов) либо { statement }, то есть блок, внутри которого находится statement, но который сам рассматривается как единое выражение.

Именно взаимная рекурсия, при которой блоки могут содержать statement, а statement строится из выражений, придаёт такой модели логичность и стройность, тогда как разные языки её по-разному ограничивают:

  • C-подобные языки разрешают переход { statement } -> expression только для простых выражений, но запрещают для управляющих конструкций. Поэтому if (x) { ... } нельзя использовать как выражение, хотя блок в фигурных скобках сам по себе мог бы быть выражением, если бы грамматика это допускала. Более того, GCC-расширение ({ ... }) буквально реализует такое правило: { statement } интерпретируется как expression, тогда как стандартный C так не умеет.

  • Lisp-подобные языки и Ruby вообще не нуждаются в отдельном нетерминале statement - в этих языках всё является выражением.

  • Rust оказался где-то посередине. В нём есть категория объявлений (let, fn, mod), которые являются инструкциями и не могут быть частью выражения. Тем не менее все управляющие конструкции (if, match, loop и т. д.) являются выражениями.

Заключение

Разделение на expression и statement, привычное для многих поколений разработчиков C-подобных языков - не логическая необходимость, а историческая случайность, закреплённая архитектурой машин фон Неймана, которую перенесли из FORTRAN в ALGOL, а затем в C. Оно зафиксировалось в грамматиках как «удобное» решение на заре компиляторостроения, но не имеет фундаментальных причин на уровне синтаксиса.

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

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

Комментарии (33)


  1. mbait
    02.08.2026 09:35

    Вместо тысячи слов хотелось бы увидеть пример дизайна языка, где нет разделения на statements и expressions, но и нет уродливого побочного синтаксиса. Скажем, решаем мы, что последнее выражение это значение блока while, но тут мы пишем последней строкой break. И приходится либо задавать break возвращаемый тип, либо... у нас снова разделение.


    1. rsashka Автор
      02.08.2026 09:35

      Ruby ?


      1. mbait
        02.08.2026 09:35

        То есть, вы просто посмотрели синтаксис языка и решили, что в компиляторе нет разделения на expr/stmt?


        1. rsashka Автор
          02.08.2026 09:35

          Что значит “посмотрел синтаксис”? Это фундаментальная особенность языка.


          1. playermet
            02.08.2026 09:35

            В Ruby есть явное разделения на statement и expression, и в официальной документации подписано что есть что. Например: break, next и redo это statements.

            А вот грамматические правила для statements в Ruby, это очень старая версия, от 2004 года, но тем не менее. Видно что есть stmt, а есть expr и expr_value.

            stmt        : kALIAS fitem  fitem
                        | kALIAS tGVAR tGVAR
                        | kALIAS tGVAR tBACK_REF
                        | kALIAS tGVAR tNTH_REF
                        | kUNDEF undef_list
                        | stmt kIF_MOD expr_value
                        | stmt kUNLESS_MOD expr_value
                        | stmt kWHILE_MOD expr_value
                        | stmt kUNTIL_MOD expr_value
                        | stmt kRESCUE_MOD stmt
                        | klBEGIN ‘{’ compstmt ‘}’
                        | klEND ‘{’ compstmt ‘}’
                        | lhs ‘=’ command_call
                        | mlhs ‘=’ command_call
                        | var_lhs tOP_ASGN command_call
                        | primary_value ‘[’ aref_args ‘]’ tOP_ASGN command_call
                        | primary_value ‘.’ tIDENTIFIER tOP_ASGN command_call
                        | primary_value ‘.’ tCONSTANT tOP_ASGN command_call
                        | primary_value tCOLON2 tIDENTIFIER tOP_ASGN command_call
                        | backref tOP_ASGN command_call
                        | lhs '=' mrhs_basic
                        | mlhs '=' mrhs
                        | expr


            1. rsashka Автор
              02.08.2026 09:35

              Что вы имеете ввиду под “statements” ? Нет возвращает значение, отдельная категория грамматики, которую нельзя использовать как отдельное выражение или что-то еще?

              Ведь “break, next и redo” - это часть грамматики языка, но она не является автономными лексическими единицами, чтобы их можно было использовать как отдельное выражение. Поэтому, да это действительно операторы, но их нельзя использовать по отдельности вне определенного контекста, т.е. они сами по себе не являются “неделимой лексической единицей” и сами по себе не являются выражениями.

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


              1. playermet
                02.08.2026 09:35

                Что вы имеете ввиду под “statements” ?

                Имею ввиду общеиспользуемое их определение, которое в том числе применяется в официальной документации Ruby.

                но их нельзя использовать по отдельности вне определенного контекста

                Как и все другие в языке кроме корневого, с которого начинается парсинг.

                А вот еще такой пример из документации:

                However, when used as a modifier, if, else, while, until and rescue are statements but not expressions.

                Statements that are not expressions cannot be used in contexts where an expression is expected, such as method arguments.

                puts( 1 if true ) #=> SyntaxError


                1. rsashka Автор
                  02.08.2026 09:35

                  Имею ввиду общеиспользуемое их определение …

                  Общепринятое определение, это “statement” не возвращает значения, но в Ruby это не так. Так какое определение вы имеете ввиду?


                  1. playermet
                    02.08.2026 09:35

                    Общепринятое определение, это “statement” не возвращает значения

                    Источник можно? В англоязычной википедии например используется "statement is a syntactic unit of an imperative programming language that expresses some action to be carried out", в русскоязычной "наименьшая автономная часть языка программирования ... Программа обычно представляет собой последовательность инструкций". И примерно то же самое написано во всех учебниках.

                    При этом каждое expression в Ruby буквально включено в statement. Это отражено как в его правилах грамматики: stmt : expr, так и в документации, что можно заметить по цитате выше: "statements that are not expressions ...". И Ruby в этом не одинок, в тех же C и C++ это тоже верно.


                    1. rsashka Автор
                      02.08.2026 09:35

                      Тогда по вашему выходит, что “expression” не может содержать инструкции? А если может (а оно может), то в чем тогда между ними различие?


                      1. playermet
                        02.08.2026 09:35

                        Тогда по вашему выходит, что “expression” не может содержать инструкции?

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

                        в чем тогда между ними различие?

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


                      1. rsashka Автор
                        02.08.2026 09:35

                        … в котором заданы эти сущности.

                        Это детали реализации грамматики, которые разработчики реализовали именно таким образом. В одном случае это будут одни правила, а в другом - другие. Просто разработчики написали их именно так и нетерминал в парсере назвали stmt, а в другом языке нетерминал грамматики взяли и назвали stmt_or_expr, но что в итоге доказывает или объясняет?

                        Все это только детали реализации, а не фундаментальная причина их использования, ведь в противном случае у нас просто не было бы причины для спора :-)

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

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


                      1. playermet
                        02.08.2026 09:35

                        Это детали реализации грамматики, которые разработчики реализовали именно таким образом

                        Так про что угодно можно сказать. Все ЯП это формальные системы, в которых если захотеть можно букве имени класса номер строки в исходниках присвоить. Но это не значит что можно перечеркнуть все устоявшиеся определения.

                        В одном случае это будут одни правила, а в другом - другие. Просто разработчики написали их именно так и нетерминал в парсере назвали stmt, а в другом языке нетерминал грамматики взяли и назвали stmt_or_expr, но что в итоге доказывает или объясняет?

                        Так уж случилось, что есть сотни императивных языков программирования, в которых отдельно stmt, отдельно expr, и назвать их можно как уходно, хоть a и b, но это две отдельные сущности, которые соотносятся определенным образом, и это не только деталь реализации, но и прописанная в документациях особенность. И так уж случилось, что нет языков, в которых есть один терминал названный stmt_or_expr, либо это две отдельные сущности, либо одной из них просто нет. Определения нужны, чтобы люди могли одинаково понимать о чем идет речь, когда слышат конкретное слово, и обычно они отражают конкретную сложившуюся по факту действительность. И она сложилась именно вот так.

                        Тем более, что речь изначально шла про Ruby, в котором это разделение такое же явное, как и в С/С++/Java. Т.е. прямо указано, что некоторые statement не являются expression, и использование их там, где ожидается expression является синтаксической ошибкой.

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

                        LISP это не императивный язык, в нем программа это список атомов, а не последовательность строк/команд. В нем просто нет statement как сущности, и это не то же самое что тождественность stmt и expr. При этом он вполне академический.


                      1. rsashka Автор
                        02.08.2026 09:35

                        Но это не значит что можно перечеркнуть все устоявшиеся определения. Определения нужны, чтобы люди могли понимать о чем идет речь, когда слышат конкретное слово

                        Напишите пожалуйся “устоявшееся” определения, чем же, по вашему, «Expression» принципиально отличается от «Statement». Я нашел несколько определений, но все они имеют опровергающие контр примеры. Может быть у вас это получится лучше.

                        … Т.е. прямо указано, что некоторые statement не являются expression, и использование их там, где ожидается expression является синтаксической ошибкой.

                        Можете привести примеры этих statement?

                        LISP это не императивный язык, в нем программа это список атомов, а не последовательность строк. В нем просто нет statement как сущности, и это не то же самое что тождественность stmt и expr. При этом он вполне академический.

                        Давайте, пожалуйста, не будем притягивать за уши лишние сущности, и сперва определимся, чем же «Expression» принципиально отличается от «Statement», а потом уже посмотрим, что есть в LISP, а чего нет.

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


                      1. playermet
                        02.08.2026 09:35

                        Напишите пожалуйся “устоявшееся” определения, чем же, по вашему, «Expression» принципиально отличается от «Statement»

                        Ну например.
                        Для statement: "syntactic unit of an imperative programming language that expresses some action to be carried out".
                        Для expression: "a sequence of operators and operands, that specifies a computation".

                        Можете привести примеры этих statement?

                        stmt if expr
                        stmt unless expr
                        stmt while expr
                        stmt until expr
                        stmt rescue expr
                        var = method expr
                        x,y = expr

                        Давайте, пожалуйста, не будем притягивать за уши лишние сущности, и сперва определимся, чем же «Expression» принципиально отличается от «Statement», а потом уже посмотрим, что есть в LISP, а чего нет.

                        Какие конкретно сущности являются лишними? Статья была посвящена терминам statement и expression, разделение которых продиктовано синтаксическими нюансами императивных ЯП, например банальным нежеланием разрешать и как-то обрабатывать конструкции вроде "print(return)", "if(break)", "while(import)" и т.п. Теперь вы приводите в пример LISP, в грамматике которого вообще не прописано ни statement, ни expression в значениях аналогичных императивным языкам.

                        Ведь я и пишу о том, что жесткое разделение на stmt и expr, это только дань привычке

                        Настолько же дань привычке, как использование латиницы в ключевых словах, организация кода по строкам с выполнением сверху вниз, позиционной десятиричной системы счисления. Можно ли сделать язык с египетскими иероглифами, выполнением по спирали на сетке против часовой стрелки и римскими числами? Можно. Является ли "дань привычке" основной причиной почему так не делают? Сомнительно.

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


                      1. Siemargl
                        02.08.2026 09:35

                        В каждом языке свое определение разделения на stmt и expr итп

                        И, соответственно, записано в БНФ

                        Осталось взять и проанализировать


                      1. rsashka Автор
                        02.08.2026 09:35

                        Осталось взять и проанализировать

                        Именно это я и сделал. Результаты анализа представлены в статье :-)


                      1. Siemargl
                        02.08.2026 09:35

                        Я тогда не уловил смысл статьи.

                        Заголовок всегда true, поскольку разделение [на stmt, expr, op, ..] зависит от контекста (ЯП и даже местоположения лексической конструкции).

                        Популяризация чтения учебников и БНФ, рассказ как оно устроено и почему?

                        Для меня это выглядит как "небо синее". И далее либо ты это принимаешь как есть, либо лезешь в физику дифракции света.


                      1. rsashka Автор
                        02.08.2026 09:35

                        Все так, разделение на stmt и expr зависит от языка, который, как правило, тянет за собой исторический груз, в том числе и из базовых учебников. Сперва мне самому захотелось разобраться в этом вопросе, а потом решил результатами поделиться с остальными.


    1. ImagineTables
      02.08.2026 09:35

      Nemerle


      1. rsashka Автор
        02.08.2026 09:35

        Точно!


    1. NeoCode2
      02.08.2026 09:35

      Я уже много лет изучаю языки программирования и разрабатываю свой, и я совершенно не вижу в чем проблема создать такой дизайн. Главное - правильная система типов.

      Для if(x>0) y; else z; возвращаемый тип это тип-сумма typeof(y) + typeof(z)

      То есть если y это string а z это float то на выходе получаем "вариант" (дискриминированное объединение) с двумя полями string и float.

      Если у нас цикл с предусловием while(x>0) y; или условие без альтернативной ветки if(x>0) y; то очевидно что результат - тип-сумма typeof(y) + never, что эквивалентно опционалу optional(typeof(y))

      Разумеется эти типы должны быть структурными (не номинативными), чтобы была совместимость с любым явно определенным пользовательским номинативным типом.

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


      1. rsashka Автор
        02.08.2026 09:35

        Разумеется эти типы должны быть структурными (не номинативными), чтобы была совместимость с любым явно определенным пользовательским номинативным типом.

        Подобное ограничение не обязательно (точнее, оно определяется системой типов языка).


        1. NeoCode2
          02.08.2026 09:35

          Это не ограничение, а скорее удобство. Компилятор генерирует "нетерминальный" составной тип, и логично чтобы он вел себя так же, как литерал вида {1,2,3} в обычном Си, которым можно инициализировать объект любой структуры из трех числовых полей (увы, в Си эта тема неразвита, и просто присвоить или передать в функцию литерал не получится - только инициализировать, но как пример подойдет и инициализация).


          1. rsashka Автор
            02.08.2026 09:35

            Так я и говорю, чтоб подобное удобство зависит от системы типов языка. Если он статически типизируемый как С/С++ , тогда ему по любому нужен какой-то тип, чтобы было что проверять при компиляции. А, например, для динамически типизируемых языков конкретный тип выводить не обязательно. Для них без анализа будет достаточно какого нибудь Any, который все равно будет проверяться в рантайме.


    1. kotan-11
      02.08.2026 09:35

      Argentum


      1. ant3mc
        02.08.2026 09:35

        Будут ли новые материалы по Аргентуму ?


  1. SIISII
    02.08.2026 09:35

    В языке, созданном для IBM 704, выражения (X + Y*Z) были строго отделены от управляющих инструкций (IF, DO, GOTO). Первые вычисляли значения, вторые управляли потоком исполнения и смешивать их между сбой было нельзя. Эта концепция синтаксиса напрямую отражала архитектуру вычислительных машин того времени: программа понималась как последовательность машинных команд, каждая из которых либо вычисляла операнд, либо изменяла счётчик команд, но не одновременно.

    Вообще-то, современные вычислительные машины ничем в этом плане не отличаются: есть машинные команды, выполняющие вычисления, а есть выполняющие переходы (с анализом некоего условия или без такового). Иногда встречаются команды, выполняющие простейшее вычисление и условный переход в зависимости от результата -- главным образом, для организации циклов по счётчику, но такие есть не везде, да и не позволяют они делать "серьёзные" вычисления.

    Это был прямой предшественник тернарного оператора

    Во-первых, это, по свой сути, оно и есть, а не "предшественник". А во-вторых, в русском языке для действий в выражениях принято слово "операция" (арифметические операции и т.п. -- не операторы; в английском -- да, operator).

    Такое решение диктовалось стремлением к максимально прямому отображению конструкций языка на машинные инструкции: условный переход jz процессора - это действие, а не способ вычислить значение.

    Во-первых, повторю уже сказанное: древние и современные машины не имеют сколько-нибудь принципиальных различий в действиях, выполняемых машинными командами; важнейшее отличие -- современные архитектуры все вычисления выполняют в регистрах, в то время как "в древности" нередко имелись команды, выполняющие операции прямо над содержимым ячеек памяти.

    Ну а во-вторых, у PDP-11 нет команды JZ, хотя идея, высказанная автором, понятна.

    архитектурой машин фон Неймана

    Архитектура фон Неймана, как и гарвардская архитектура -- это не про то, какие команды есть и что они делают, а про то, используются ли для памяти единые адреса независимо от того, команды это или данные (фон Нейман), или же адресные пространства кода и данных строго отделены друг от друга и не пересекаются, а численно один и тот же адрес указывает разные ячейки памяти в зависимости от того, является ли он адресом команды или данных (Гарвард).

    В общем, как по мне, слов в публикации много, а толку -- не очень...


    1. rsashka Автор
      02.08.2026 09:35

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

      То есть, если раньше синтаксис ЯВУ старался повторять идеологию машинных инструкций и архитектуру вычислителей, то сейчас (точнее уже много лет) синтаксис языков вообще никак не привязан к вычислительной архитектуре.


  1. Siemargl
    02.08.2026 09:35

    int y = foo(), bar(); // Это НЕ comma-expression! Это два отдельных объявления переменных!

    Здесь объявление функции bar(), а не второй переменной.


    1. rsashka Автор
      02.08.2026 09:35

      Спасибо, исправил :-)


  1. KvanTTT
    02.08.2026 09:35

    Странно, что в статье не упоминается Kotlin, в котором почти все является expression, результат которого можно использовать. И есть типы Unit и Nothing :

    fun executeAction(action: () -> Unit) {
        action() // Executes the lambda expression
    }
    
    fun main() {
        executeAction { println("Button clicked!") }
    }
    fun throwError(message: String): Nothing {
        throw IllegalArgumentException(message)
    }
    
    fun parseAge(input: String): Int {
        val age = input.toIntOrNull()
        
        // Valid because Nothing is a subtype of Int
        return age ?: throwError("Invalid age provided!") 
    }
    fun calculateComplexTax(income: Double): Double {
        // Compiles cleanly even though it doesn't return a Double yet!
        TODO("Implement regional tax calculation logic") 
    }


    1. rsashka Автор
      02.08.2026 09:35

      На самом деле у меня изначально была еще и Java, но потом я подумал, что и так очень много примеров получается, поэтому Java (и Kotlin, как её творческую переработку) я в качестве примеров приводить уже не стал, хотя может быть именно Kotlin и стоило бы упомянуть.