Почему литералы формы [] имеют значение, которое, по-видимому, зависит от контекста?

Рассмотрим следующую программу:

{$APPTYPE CONSOLE}

type
  TMyEnum = (enum1, enum2, enum3);

var
  Arr: TArray<TMyEnum>;
  Enum: TMyEnum;

begin
  Arr := [enum3, enum1]; // <-- this is an array
  for Enum in Arr do
    Writeln(ord(Enum));
  Writeln('---');

  for Enum in [enum3, enum1] do // <-- this looks very much like the array above
    Writeln(ord(Enum));
  Writeln('---');

  Readln;
end.

Результат:

2
0
---
0
2
---

Почему два цикла дают разный результат?


person David Heffernan    schedule 29.06.2015    source источник


Ответы (3)


Потому что массив содержит информацию о порядке, а набор — нет.


Объяснение с использованием документации:

внутренний формат данных статического или динамического массива< /сильный>:

хранится как непрерывная последовательность элементов типа компонента массива. Компоненты с наименьшими индексами хранятся по наименьшим адресам памяти.

Обход этих индексов с помощью цикла for in выполняется в возрастающем порядке:

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

С другой стороны, внутренний формат данных установить:

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

Таким образом, все эти «индексированные биты» хранятся в одном и том же «значении». Вот почему набор может быть приведен к типу Integer, и почему порядок, в котором добавляются биты, теряется: [enum3, enum1] = [enum1, enum3].

person NGLN    schedule 29.06.2015
comment
Я полагаю, меня больше интересовало, почему [...] иногда был массивом, а иногда набором. Я должен был сделать это более ясным. - person David Heffernan; 29.06.2015
comment
Тогда ответ будет таким: поскольку контекст определяется объявлением типов, это и есть язык. ;) - person NGLN; 29.06.2015
comment
Это то, к чему я действительно стремился. И, конечно же, я знал ответ, когда задавал вопрос. Если я пропустил какую-либо документацию, объясняющую это, было бы неплохо увидеть. +1 - person David Heffernan; 29.06.2015
comment
Да, похоже, что документация просто должна соответствовать этому новому форма объявления константного массива. - person NGLN; 29.06.2015

for Enum in Arr do
  Writeln(ord(Enum));

Здесь Arr — это массив, поэтому элементы массива выводятся по порядку. В документации говорится:

Массив просматривается в возрастающем порядке.

Следовательно, 2 выводится перед 0.

for Enum in [enum3, enum1] do
  Writeln(ord(Enum));

Здесь [enum3, enum1] — это набор, и перечислитель для набора перечисляет в порядке возрастания порядкового номера. Таким образом, на выходе сначала будет 0.

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


Таким образом, возникает вопрос о том, как [...] может быть набором или массивом в разных точках кода. Все это проистекает из нового синтаксиса динамического массива XE7, который вносит (еще одну) синтаксическую двусмысленность. Когда мы пишем

Arr := [enum3, enum1];

тогда [enum3, enum1] - это массив. Компилятор знает, что Arr — это массив, и эта информация определяет тип литерала.

Но когда мы пишем

for Enum in [enum3, enum1] do

тогда [enum3, enum1] является набором. Здесь литерал, в принципе, может быть либо массивом, либо набором. Я считаю, что в таких ситуациях компилятор всегда будет отдавать предпочтение наборам.

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

Значение литерала формы [...] зависит от его контекста.

person David Heffernan    schedule 29.06.2015
comment
Как правило, пока не появился новый синтаксис для dynarray (XE7 или 8?), [] всегда был набором. Только после того, как был введен новый synatx, dynarrays можно было инициализировать с синтаксисом []. Так что действительно, Arr — это массив (что еще?), а общий литерал [...] всегда обозначал множество, начиная со старых добрых дней Паскаля. Так что в этом нет ничего странного, IMO. Перечислители не обязательно имеют особый порядок, но IIRC где-то упоминалось, что наборы нумеруются в порядке возрастания порядкового номера. - person Rudy Velthuis; 29.06.2015
comment
@Rudy Можете ли вы предоставить ссылку на документацию для наборов, перечисляемых в порядке возрастания? - person David Heffernan; 29.06.2015
comment
@Rudy Что касается литерал [...] всегда обозначал набор, то это уже не так. Казалось бы, это набор, если только это не массив. Это зависит от контекста. Я не могу найти никакой документации для этого. Было бы здорово, если бы вы его раскопали. - person David Heffernan; 29.06.2015
comment
Я лишь смутно помню это, поэтому я больше не знаю, где это было задокументировано, извините. Когда были введены нумераторы множеств? Должно быть, это было примерно в то время. - person Rudy Velthuis; 29.06.2015
comment
@Rudy Если это не указано в официальной документации, то, на мой взгляд, это не так важно. Документация в виде смутно припоминаемых статей, которые могли когда-либо существовать, а могли и не существовать, бесполезна. - person David Heffernan; 29.06.2015
comment
Я сказал всегда обозначает (кроме, конечно, в открытых параметрах и в объявлениях массива). И это верно со времен Вирта. Синтаксис открытого параметра был выбран в дни Delphi 4 или около того, и мне бы, на самом деле, хотелось другого, тем более что [...] уже был занят наборами (и объявлениями массивов, которые в любом случае никогда не будут перепутаны с литералами наборов, IMO ). - person Rudy Velthuis; 29.06.2015
comment
Обратите внимание, что литералы нединамических константных массивов используют (...) для массивов. Я бы понял, если бы они использовали их и для литералов dynarray, опять же, чтобы избежать путаницы с существующим синтаксисом set. - person Rudy Velthuis; 29.06.2015
comment
Я смутно помню это, но это не значит, что это не официальная документация. Я просто не знаю, где именно. - person Rudy Velthuis; 29.06.2015
comment
FWIW, на самом деле небезопасно полагаться на любой порядок перечисления, IMO, даже на массивы. Если вы хотите надежный порядок, используйте индексацию или что-то в этом роде. - person Rudy Velthuis; 29.06.2015
comment
@RudyVelthuis Для набора я думаю, что это правильное утверждение. Для массивов документация говорит об обратном. Он говорит: Массив просматривается в возрастающем порядке, начиная с самой нижней границы массива и заканчивая размером массива минус один. И, конечно, это важно. Было бы совершенно бесполезно, если бы перечислитель для массива не имел определенного порядка. Точно так же порядок перечисления для упорядоченных коллекций четко определен, и на него можно положиться. - person David Heffernan; 29.06.2015
comment
Для множеств в документации указано, что множество — это набор значений одного и того же порядкового типа. Значения не имеют внутреннего порядка[...] docwiki.embarcadero.com/RADStudio/XE8/en/Structured_Types Таким образом, это не говорит о том, что набор будет проходить в обратном порядке, но также, похоже, подразумевает, что нет естественного порядка ожидания set для прохождения. Ожидание прохождения в обратном порядке может также означать использование неопределенного поведения. - person J...; 29.06.2015
comment
@J... Я не ожидаю, что набор будет проходиться в любом порядке. Я указывал на то, что наивно можно подумать, что если [enum3, enum1] был массивом в одном месте, то он был бы массивом и в другом месте. - person David Heffernan; 29.06.2015
comment
Да, я согласен - однако вы спросили, есть ли какая-либо документация, подтверждающая идею о том, что множества перечисляются в порядке возрастания. Это было сделано только для того, чтобы привести пример из документации, который предполагает, что не следует ожидать никакого порядка (и что перечисление в порядке возрастания не обязательно гарантируется). Я согласен с тем, что синтаксис динамического массива — неудачный и запутанный выбор. - person J...; 29.06.2015
comment
@David: меня бы не удивило, если бы кому-то удалось определить тип перечислителя для вспомогательного типа, который заставлял for-in проходить массив в обратном порядке. - person Rudy Velthuis; 29.06.2015
comment
Но почему вы ожидаете, что [a, b] будет массивом, если вы не знаете, что тип был объявлен как таковой? [...] — это классический синтаксис набора. Только после того, как некоторые ввели квадратные скобки для литералов массива, возникла путаница. Таким образом, безопасный способ — сначала подумать о множествах, а затем посмотреть, может ли это быть массивом. Если не типизировано, это множество. - person Rudy Velthuis; 29.06.2015
comment
@David: словарь - это упорядоченная коллекция? - person Rudy Velthuis; 29.06.2015
comment
@Rudy В коде вопроса [enum3, enum1] в какой-то момент это массив, а позже - набор. Так что это не столько вопрос ожидания, сколько факт. - person David Heffernan; 29.06.2015
comment
@Rudy Словарь - это неупорядоченная коллекция. - person David Heffernan; 29.06.2015
comment
Если я читаю код и еще не все видел. и я вижу [...]literal, я ожидаю, что это будет набор. Если я заранее знаю тип, то это, конечно, другое дело. - person Rudy Velthuis; 29.06.2015
comment
Термин [enum3, enum1] является массивом в первой строке только потому, что целью назначения является массив. Если бы цель была набором, то та же конструкция интерпретировалась бы как набор. Приоритет набора над массивом в цикле for заключается только в том, что набор имеет более старые права. Вы можете изменить это с помощью явного приведения типа. - person Uwe Raabe; 29.06.2015
comment
@Rudy Если бы я знал тип заранее. Ну вот и все. Это зависит от контекста. Что прискорбно. В идеальном мире такого бы не было. Другие языки стараются избегать таких сценариев. - person David Heffernan; 29.06.2015
comment
Также было бы вполне правдоподобно, если бы разработчики языка сказали, что, черт возьми, у нас уже есть контекстно-зависимая типизация литералов, поэтому давайте решим, что [...] в цикле for in с большей вероятностью будет использоваться как массив. Если бы они начинали с нуля и должны были выбирать между массивом и набором, тогда массив был бы лучшим выбором. Но это сделано для того, чтобы не изменить смысл исторического кода. - person David Heffernan; 29.06.2015
comment
Я полностью согласен с тем, что это зависит от контекста, но это не совсем ново. В одном случае 7 является целым числом, а в другом — единичным. Или «A» — это либо Char, либо WideString, либо AnsiString и т. д. Так обстоит дело с литералами. Но [...]немного отличаются, потому что это всего лишь массивы в каких-то очень строго определенных синтаксических конструкциях. - person Rudy Velthuis; 29.06.2015
comment
@Rudy В других языках такой двусмысленности нет, и преимуществом этого является ясность и предсказуемость для читателя кода. Жаль, что Паскаль не пошел по этому пути. - person David Heffernan; 29.06.2015
comment
Я амбивалентен. Паскаль-множества очень типичны для этого языка. Разработчикам Delphi не следовало использовать подобный синтаксис для литералов массивов. У них уже был синтаксис (..., ...) для константных литералов массива. Они должны были использовать это для открытых параметров и литералов dynarray. (..., ...) в противном случае может быть только списком параметров, и риск путаницы между ними более или менее нулевой. - person Rudy Velthuis; 29.06.2015
comment
Паскаль существовал задолго до появления большинства этих языков, а наборов не было ни в одном из этих языков. Я не согласен, что нет такой двусмысленности. Многие другие языки используют {...} для массивов, а также для структур или перечислений. Не уверен, что мне бы это понравилось. На самом деле, я уверен, что мне бы это не понравилось. - person Rudy Velthuis; 29.06.2015
comment
И я нахожу Паскаль более удобным для чтения, чем многие другие языки. Просто подумайте о луковичном очистке/прыжках, которые вам иногда приходится делать, чтобы понять объявление C. У меня было несколько примеров на моем веб-сайте, но пока у меня нет нового провайдера, он пока не работает. - person Rudy Velthuis; 29.06.2015
comment
@RudyVelthuis Я считаю Паскаль более читаемым, чем многие другие языки, и не говорил иначе. Я не думаю, что это идеально, хотя. Все языки имеют свои сильные и слабые стороны. И даже то, что сегодня является силой, завтра может стать слабостью. Вам не нужно учить меня, что объявления C трудно читать! - person David Heffernan; 29.06.2015
comment
@RudyVelthuis Для литералов в C, C++ и C# я считаю, что тип литералов полностью определяется литералом. Другими словами, тип левой части присваивания не может влиять на тип правой части присваивания. Я ошибся? - person David Heffernan; 29.06.2015
comment
@J ... и FWIW, мой запрос на документы, касающиеся установленного порядка перечисления, состоял в основном в том, чтобы вызвать Руди, потому что я не верю, что дается какая-либо гарантия порядка перечисления. - person David Heffernan; 30.06.2015
comment
@DavidHeffernan Это было ясно - подумал, что я просто поддержу эту позицию. - person J...; 30.06.2015
comment
@Rudy Несмотря на то, что люди, знакомые с классическим Delphi, инстинктивно интерпретируют [...] как набор по умолчанию, где контекст неоднозначен, новички не получат нашей выгоды. Это, безусловно, то, что должно быть задокументировано. - person Disillusioned; 30.06.2015
comment
Я на самом деле не знаю, так ли это, но я думаю, что вы правы. Однако в Паскале 'A' может быть Char или любым строковым типом. C не знает понятия истинных констант, но Delphi знает. И настоящая константа, такая как 7, не имеет собственного типа. Тип предполагается, когда он назначается (или используется в качестве параметра). - person Rudy Velthuis; 30.06.2015
comment
@Rudy Настоящая константа, такая как 7, не имеет собственного типа. В документации указано иное docwiki.embarcadero.com/RADStudio/en/Declared_Constants - person David Heffernan; 30.06.2015
comment
Многое изменилось в документах, и не всегда корректно. - person Rudy Velthuis; 30.06.2015
comment
@RudyVelthuis Несмотря на течение времени, ваша непоколебимая вера в себя остается непоколебимой, непоколебимой Башней Истины! ;-) И еще ту же документацию можно найти в справке по Delphi 5..... - person David Heffernan; 30.06.2015

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

Если константное выражение представляет собой строку символов, объявленная константа совместима со строкой любого типа. Если строка символов имеет длину 1, она также совместима с любым типом символов.

В случае строки символов компилятор будет использовать левую часть для определения типа правой части. Разница между этим и кодом в вопросе заключается в том, что этот случай четко задокументирован, а случай в вопросе - нет.

Пример использования символов:

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

var
  A: Char;
  B: AnsiChar;

begin
  A := 'a';
  B := 'a';

  Writeln(A);
  Writeln(B);

  Readln;
end.

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

Project10.dpr.17: A := 'a';
004D6731 66C705C8034E006100 mov word ptr [$004e03c8],$0061
Project10.dpr.18: B := 'a';
004D673A C605CA034E0061   mov byte ptr [$004e03ca],$61

Компилятор использует целевой тип присваивания, чтобы определить, какого типа должна быть строка символов (в данном случае «a»). Аналогичная вещь происходит в вопросе.

Спасибо Дэвиду за дополнительную информацию в комментариях

person Graymatter    schedule 29.06.2015
comment
То, что вы говорите, в целом верно, но пример не убедителен. Вполне возможно, что 1 будет целым числом, и компилятор выдаст этот код при присвоении типу с плавающей запятой. Это вполне приемлемое продвижение типа, которое компилятор может сделать во время компиляции. Вы бы с радостью написали MyReal := MyInt и не предполагали, что MyInt должен быть реальным типом. Вы были бы очень довольны, если бы продвижение выполнялось во время выполнения. - person David Heffernan; 29.06.2015
comment
Документация подтверждает это: docwiki.embarcadero.com/RADStudio/en/ Числа с десятичными точками или показателями степени обозначают вещественные числа, а другие цифры обозначают целые числа. - person David Heffernan; 29.06.2015
comment
@DavidHeffernan Хорошо, изменено, чтобы использовать лучший пример. Я согласен с вами, Int и Single были плохим примером. - person Graymatter; 29.06.2015
comment
Опять же, компилятор может делать то же самое. Вполне вероятно, что 'a' — это AnsiChar, которого благополучно повысили. Я думаю, что это не так, но было бы неплохо это продемонстрировать. Довольно неприятно, что тип литерала можно определить по контексту. - person David Heffernan; 29.06.2015
comment
@DavidHeffernan Если бы это было так, нельзя ли было бы неявно присвоить ансичар символу? - person Graymatter; 29.06.2015
comment
И строки символов зависят от контекста. В документах говорится, что если константное выражение является строкой символов, объявленная константа совместима с любым типом строки. Если строка символов имеет длину 1, она также совместима с любым типом символов. - person David Heffernan; 29.06.2015
comment
@DavidHeffernan Но разве не в этом суть? В этом случае они говорят, что это зависит от контекста, поэтому RHS (в данном случае строка символов) зависит от контекста. Единственная разница между этим и случаем в вашем вопросе заключается в том, что этот документ задокументирован как таковой, а другой - нет. Я согласен, однако, это расстраивает, потому что компилятор может определить и получить результат, которого вы не ожидаете, потому что он меняет тип в RHS. - person Graymatter; 29.06.2015
comment
Я соглашался с вами, но говорил, что больше обоснований поможет в тексте ответа. - person David Heffernan; 29.06.2015
comment
@DavidHeffernan Спасибо, я обновил ответ. Я надеюсь, что теперь он содержит достаточно информации. - person Graymatter; 29.06.2015