Перейти к основному содержимому

Пулы — строка ссылается на целую запись

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

Две тысячи пациентов принимают тридцать врачей. В строке пациента нужны имя, фамилия и кабинет врача. Три последовательности рядом дают три независимых розыгрыша, и в строке оказывается имя одного врача рядом с кабинетом другого. Данные выглядят правдоподобно и при этом бессмысленны: Dr. John Williams, room 118, где такого врача не существует.

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

<tdc>
<env count="8" seed="clinic" local="en">
<pool name="Doctors" count="4">
<sequence name="firstName"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="lastName"><gen type="template" value="person.lastName"/></sequence>
<sequence name="room"><gen type="number" value="100..199"/></sequence>
</pool>

<sequence name="Patient"><gen type="template" value="person.female.firstName"/></sequence>
<sequence name="Seen"><gen type="pool" value="Doctors"/></sequence>
</env>
<block>
<line><data>${{Patient}} -> Dr. ${{Seen.firstName}} ${{Seen.lastName}}, room ${{Seen.room}}</data></line>
</block>
</tdc>
./run clinic.tdc
Barbara -> Dr. James Johnson, room 100
Mary -> Dr. Michael Smith, room 186
Margaret -> Dr. Robert Brown, room 148
Jennifer -> Dr. John Williams, room 154
Elizabeth -> Dr. Michael Smith, room 186
Patricia -> Dr. Michael Smith, room 186
Susan -> Dr. John Williams, room 154
Linda -> Dr. James Johnson, room 100
Вывод показан для примера

Значения получены с фиксированным seed, то есть воспроизводимы, но конкретные строки могут отличаться между версиями ядра. Смотрите на них как на пример формы, а не как на гарантию.

Четыре врача обслуживают восемь пациентов, и три поля каждого врача держатся вместе. Джеймс Смит сидит в кабинете 197 в каждой строке, где он появляется, потому что строка выбрала Смита, а не «имя, фамилию и кабинет».

Четыре члена, восемь строк. Каждая строка забирает целую строку таблицы.
  • Aпул: четыре члена, построенные один раз до прогона
  • Bсгенерированные строки
  • Cодин выбор на строку — обе ячейки строки всегда от одного и того же члена

Происходит две вещи, именно в таком порядке​

  1. Строится пул. До того как появится хоть одна строка, TDC создаёт count членов. Это происходит один раз — и для прогона на 8 строк, и на 8 миллиардов.
  2. Каждая строка берёт одного члена. <gen type="pool"> даёт своей последовательности одного целого члена на строку и публикует все его поля под именем Ссылка.поле.

Всё остальное на этой странице следует из этих двух предложений.

Кратко​

<pool> живёт прямо в <env>, рядом с <sequence>. Напрямую его не читают: из него разыгрывает последовательность.

У <pool>ОбязателенЧто задаёт
nameдаИмя, которое <gen type="pool"> называет в value
countдаСколько членов в таблице
commentнетСвободный комментарий
У <gen type="pool">ОбязателенЧто задаёт
valueдаИз какого пула разыгрывать
filterнетИз каких членов эта строка может выбирать — Отбор
parentнетОставить часть строк без записи — Отбор

Пул — это миниатюрный <env>​

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

Поэтому всё, что вы уже знаете, действует и внутри пула:

Внутри пулаОзначает
<sequence>поле каждого члена
<mix>поле, разделённое точным percent — по ЧЛЕНАМ
<switch>поле, выведенное из другого поля того же члена
<uniq> · uniq="true"члены, которые должны отличаться друг от друга
<distinct>два поля одного члена, которые должны отличаться
if=поле, которое есть только у части членов
parent=поле, построенное по тем членам, которых выбрало другое поле
<compute>поле, вычисленное из других полей члена

Чего в пуле быть не может — это вывода: ни <block>, ни теги обрамления. Пул это таблица, которую читают другие столбцы, а не то, что пишется в файл. Ещё в нём не может быть другого <pool> — пулы остаются плоскими таблицами, и один ссылается на другой, а не вкладывается в него.

Два имени и точка​

Имя есть у пула и у последовательности, которая из него разыгрывает. Это разные вещи:

<pool name="Doctors" count="4"> … </pool>

<sequence name="Seen"><gen type="pool" value="Doctors"/></sequence>
  • Doctors — таблица. В <block> на неё никто не ссылается.
  • Seen — столбец, в котором на каждой строке лежит один врач. Его поля читаются как ${{Seen.firstName}}.

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

Прочитать ссылку без поля нельзя — печатать нечего:

./run clinic.tdc
error[TDC229]: "Seen" draws a whole member from a pool — it has no value of its own to print
--> clinic.tdc:8:16
|
8 |   <block><line><data>${{Seen}}</data></line></block>
|                ^^^^^^^^^^^^^^^^^^^^^^
|
note: Read a field: ${{Seen.name}}.

Один выбор на строку​

В последовательности одно значение на строку. Ссылка на пул — это последовательность, значит в ней один член на строку, и все ${{Seen.…}} в этой строке читают одного и того же.

Это правило и есть смысл конструкции. Из него же следует, почему «Дмитрий Иванова» пул выдать не может: пол, имя и фамилия — поля одного члена, и строка, взявшая члена, взяла все три.

Выбор равномерный по членам — это розыгрыш, а не график дежурств, потому что долей никто не объявлял; см. объявленные доли или розыгрыш. Он ещё и адресуемый: строка 900 000 000 находит своего врача без того, чтобы 899 999 999 предыдущих вообще существовали. Поэтому пул одинаково дёшев на всех движках.

Доли внутри пула​

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

<pool name="Doctors" count="10">
<mix name="role" percent="30,70">
<case><gen type="text" value="surgeon"/></case>
<case><gen type="text" value="therapist"/></case>
</mix>
<sequence name="name"><gen type="template" value="person.lastName"/></sequence>
</pool>

Доля считается от десяти членов, а не от строк. Хирургов трое; как часто к этим троим попадают — зависит от того, как разыгрываются строки, и от filter, который превращает «трое из врачей — хирурги» в «пациент, которому нужен хирург, к хирургу и попадает».

Члены, отличающиеся друг от друга​

uniq внутри пула означает то же, что и снаружи: ни у каких двух членов нет одинакового значения.

<pool name="Rooms" count="6">
<sequence name="number" uniq="true"><gen type="number" value="100..199"/></sequence>
</pool>

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

Собственный сид пула​

Пул не разыгрывается из основного потока прогона. У него свой производный сид:

<сид прогона> + "#pool:" + <имя пула>

Пул Doctors при seed="main" строится из main#pool:Doctors. Атрибута seed у <pool> нет — весь механизм в этом выводе, а попытка его написать сообщается (TDC015).

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

Два следствия, которые стоит знать:

  • Меняете seed прогона — меняется и пул. Это и задумано: одно число по-прежнему воспроизводит весь прогон целиком.
  • Переименование пула меняет его состав, потому что имя входит в производный сид. Переименование здесь не косметика; если важен конкретный набор членов, имя лучше не трогать.

Размер​

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

ЧленовПримерноЧто происходит
до 100 000до ~29 МБничего, работает
больше 100 000TDC234, предупреждение
больше 1 000 000больше ~290 МБTDC235, отказ

Оба сообщения заканчиваются одинаково, потому что причина обычно одна: если имелось в виду число строк, то это count у <env>.

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

Куда дальше​

  • Отбор через filter — из каких членов может выбирать строка и чем это отличается от if
  • Связывание пулов — несколько пулов, пул, разыгрывающий из другого пула, и цепочки в три уровня
  • Пул целиком — <line each="Doctors"> печатает пул запись за записью: справочник или массив в JSON-документе