Перейти к основному содержимому
Версия: 7.0

Правила: действия и присваивание

Правила действий

  1. Ассистент ОБЯЗАН избегать FOR, когда тот же результат можно выразить множественной (set-based) конструкцией.

    FOR итерирует строку за строкой, и к нему СЛЕДУЕТ прибегать в последнюю очередь, когда нет декларативной альтернативы. Единственное измеренное исключение работает в обратную сторону: когда присваиваемое значение — агрегат GROUP, границы которого коррелируют с обновляемой строкой, и оба множества велики, множественный вариант может скомпилироваться в запрос, материализующий всю корреляцию, и построчный FOR ... NOINLINE может оказаться быстрее — там, где индекс по агрегируемому классу позволяет отвечать на агрегат каждой строки индексным обращением.

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

    • агрегация или материализация множества -> GROUP SUM, GROUP CONCAT, GROUP MAX, GROUP LAST, GROUP AGGR
    • присваивание свойства по множеству -> прямое присваивание свойства с параметрами вместо цикла FOR ... DO
    • экспорт табличных или иерархических данных -> EXPORT FROM, EXPORT JSON FROM, EXPORT XML FROM, EXPORT CSV FROM
    • построение структурированных данных -> JSON FROM, XML FROM
    • массовые интеграционные записи -> NEW, DELETE или множественное изменение свойства вместо построчного FOR

    FOR приемлем, когда тело имеет настоящий построчный поток управления, такой как условный APPLY, MESSAGE, throwException или внешние вызовы, которые нельзя выразить как операцию над множеством.

  2. Параметры, вводимые в NEW alias = Class и FOR expr(p) [NEW alias = Class] DO { ... }, НЕ следуют обычным правилам лексической области видимости из мейнстримных языков программирования.

    Такие параметры видны ТОЛЬКО внутри тела блока NEW или цикла FOR, которые их вводят.

    Ассистент НЕ ДОЛЖЕН ссылаться на эти параметры вне вводящего их блока.

    Когда зависимое вычисление должно переиспользовать эти параметры, ассистенту СЛЕДУЕТ вкладывать дополнительные блоки NEW или FOR внутрь вводящего блока, где параметры ещё находятся в области видимости, вместо того чтобы выносить значения во вспомогательное хранилище.

    И наоборот, параметр, объявленный внутри агрегата GROUP, принадлежит этому агрегату и НЕ виден снаружи; в частности, он не может служить переменной цикла объемлющего FOR. Объявите переменную собственным параметром FOR, а агрегат используйте только как логическое условие по ней.

  3. Ассистенту СЛЕДУЕТ избегать введения свойств LOCAL без конкретной необходимости.

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

  4. LOCAL обычно оправдан, когда выполняются ОБА условия:

    • его значение нетривиально вычислить (агрегация, join'ы, многошаговая логика, внешние вызовы или другая работа, которую стоит материализовать), И
    • то же значение используется более одного раза, так что материализация позволяет избежать повторного вычисления.
  5. По возможности ассистенту СЛЕДУЕТ предпочитать альтернативы новому LOCAL:

    • встраивать выражение в каждое место использования, если оно дешёвое
    • вкладывать блоки NEW / FOR, чтобы промежуточные значения оставались в области видимости параметров
    • использовать обычное (не LOCAL) вычисляемое свойство, когда значение переиспользуется в нескольких действиях
  6. Это рекомендации, а не жёсткие запреты. Если ассистент не может найти рабочий синтаксис для конструкции без LOCAL или другой подход постоянно не получается и не выходит построить чистое действие, откат к LOCAL приемлем как последнее средство.

    Устоявшиеся паттерны LOCAL, предписанные другими правилами (например, промежуточное хранение при импорте, перенос между вложенными сессиями), остаются допустимыми; ассистенту СЛЕДУЕТ держать такие LOCAL минимальными по количеству и области.

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

    В генерируемых скриптах (eval, наполнение данных) ассистенту СЛЕДУЕТ давать параметрам верхнеуровневых операторов уникальные имена, чтобы не зависеть от порядка операторов.

  8. Многие системные служебные действия возвращают результат через одноимённое локальное свойство без параметров (например, в Utils: действие fileExists[ISTRING[500]] пишет в свойство fileExists[]). Такой элемент — ДЕЙСТВИЕ, а не логическое свойство: ассистент ОБЯЗАН сначала вызвать действие, а затем прочитать свойство без параметров (fileExists(path); IF fileExists() THEN ...), и НЕ ДОЛЖЕН использовать форму с параметрами внутри выражения (IF fileExists(path) — неверно).

Правила присваивания (<-)

  1. Аргументы изменяемого свойства в левой части <- могут быть выражениями от параметров оператора (sentFolder(account(f)) <- f), но новые локальные параметры вводятся только как типизированные параметры, а не внутри выражений. Запись «по вычисляемому ключу» по аналогии с императивным map[key] = value легко нарушает это.

    Поэтому, перенаправляя ссылки-самоссылки при глубоком копировании графа объектов, ассистенту СЛЕДУЕТ держать обратное отображение и итерироваться с ЦЕЛЕВЫМ объектом в роли параметра — link(Copy n) <- newOf(link(srcOf(n))) WHERE spec(n); — а не писать link(newOf(x)) <- newOf(link(x));

  2. <- выражение IF условие присваивает всё выражение ВСЕМ объектам: там, где условие не выполняется, свойство перезаписывается NULL. Фактически это сброс-плюс-запись.

    ДОБАВЛЯЯ присваивание к свойству, уже заполненному ранее в том же действии, ассистент ОБЯЗАН использовать форму WHERE (prop(x) <- TRUE WHERE cond(x)), которая меняет только строки, подходящие под условие. Второе присваивание в форме IF тому же свойству ОБЯЗАНО рассматриваться как сигнал тревоги при ревью.

  3. Написанный прямо в теле действия или события PREV(<выражение>) переносит в состояние начала сессии ВСЁ обёрнутое выражение, включая подвыражения-аргументы: аргумент, вычисленный в текущей сессии (LOCAL, свойство объекта, созданного в сессии), внутри PREV читается как NULL, молча обнуляя результат.

    Чтобы прочитать предыдущие данные при текущих аргументах, ассистент ОБЯЗАН обернуть PREV в отдельное свойство — prevF(x) = PREV(f(x)); — и вызывать его, а не писать PREV(f(<вычисленный в сессии аргумент>)) в теле.

  4. Параметр, через который читается или изменяется свойство с именем, объявленным у нескольких классов, ОБЯЗАН быть аннотирован классом при первом использовании (date(Interaction i) <- ...): перегруженное имя разрешается по классам параметров, и нетипизированный параметр даёт ошибку «ambiguous name». Особенно это касается событий: их оператор — отдельный контекст параметров, в котором класс больше ниоткуда не выводится, а условие i IS Interaction класс параметра не задаёт.

Правила циклов (FOR, WHILE)

  1. FOR фиксирует свой набор до первой итерации: условие вычисляется один раз, подходящие строки читаются, и тело выполняется по разу на строку этого набора. То, что меняет тело, — включая данные под условием — не добавляет и не убирает итераций.

    Перечитывает набор WHILE, но делает это по ШАГАМ, а не по строкам: один шаг заново вычисляет условие, читает весь подходящий набор и выполняет тело для каждой его строки, и только потом набор читается снова; итерации прекращаются, когда он возвращается пустым. Поэтому строка, уже попавшая в текущий шаг, свою очередь получит, даже если более ранняя строка того же шага сделала условие для нее ложным.

  2. Без ORDER FOR обходит свой набор в произвольном порядке. Ассистент ОБЯЗАН задавать явный ORDER всюду, где результат зависит от последовательности — нумерация, накопительные итоги, любое чтение записанного предыдущей итерацией, — и всюду, где TOP ограничивает число взятых строк, и ОБЯЗАН завершать этот ORDER ключом, различающим любые две строки.

Правила потоков (NEWTHREAD, NEWEXECUTOR)

  1. Серверное поточное действие разделяет сессию изменений вызывающего кода, а сессии изменений не потокобезопасны.

    Поэтому ассистенту СЛЕДУЕТ оборачивать тело серверного NEWTHREAD в NEWSESSION, а когда ему нужна собственная транзакция базы — в NEWSESSION NEWSQL. Это размен: обычный NEWSESSION перестает видеть несохраненные изменения вызывающего, поэтому обертка опускается только там, где разделение сессии сделано намеренно И известно, что одновременно они не выполняются.

    Что дает обертка внутри транзакции APPLY, зависит от того, КОГДА тело стартует на самом деле. Пока транзакция открыта, NEWSESSION, включая NEWSQL, сессию не создает — действие откладывается в текущую, — а проверка происходит в момент выполнения тела, а не в момент его планирования. SCHEDULE DELAY — это число миллисекунд, а не барьер, ждущий применения, так что и он ничего не гарантирует. Ассистент НЕ ДОЛЖЕН рассчитывать, что поток, запущенный из глобального обработчика, окажется изолированным.

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