Меню

ABAP в XXI веке (часть 2)

|

Это вторая часть публикации Карстена Больмана, посвященной функциональным возможностям ABAP в дополнениях версии 7.40 к программному языку ABAP. Здесь рассматриваются внутренние таблицы, получение доступа к таблицам и их создание, а также агрегация, группирование и трансформация. Целью большинства расширений является смягчение жесткой императивной парадигмы.

Ключевое понятие

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

   

Если вас заинтересовала первая часть обзора функций языка ABAP 7.40, вам непременно стоит прочитать и вторую часть. Эта часть посвящена внутренним таблицам. Этот гибкий и мощный инструмент относится к самым основам ABAP, без внутренних таблиц язык стал бы бесполезным.

К сожалению, структуры ABAP для создания таблиц и обращения к ним до сих пор проходят эволюционный этап операторов «MOVE x TO y» и «COMPUTE X = y + 1», о которых шла речь в первой части: Они избыточны и неортогональны. Применяя их, мы думаем о микровоздействии на состояние, а не о наборах команд для создания значений на основе других значений, что было бы целесообразно для значительной доли задач программирования.

При работе с таблицами приходится сталкиваться с такими операторами:

READ TABLE tab WITH KEY c1 = ’a’ ASSIGNING <line>.

LOOP AT tab1 ASSIGNING <line> WHERE c1 = ’a’.

INSERT <line>-c2 INTO TABLE tab2.

ENDLOOP.

Вспомним, как задания (преобразование типа и создание структурированных значений) были вызволены из императивного мрака выражениями конструктора:

use_string( CONV string( char10 ) ).

use_struct( VALUE t_struc( c1 = ’a’ c2 = 10 ) ).

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

READ TABLE tab INDEX 1 ASSIGNING FIELD-SYMBOL(<line>).

INSERT VALUE #( c1 = 'a' c2 = 10 ) INTO TABLE tab.

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

use_inttab( VALUE #( ( x - 1 ) ( x ) ( x + 1 ) ) ).

Нам необходимо рассмотреть два важных момента:

  • Доступ к табличным строкам без операторов для манипуляций с состоянием, например, READ.
  • Создание табличных значений с переменным количеством строк (например, на основе строк другой таблицы).

Далее мы перейдем к более сложным задачам: сокращение таблицы до нетабличного значения, обработка табличных строк в группах (вспомните GROUP BY для SQL) и выполнение с таблицами старой доброй операции MOVE-CORRESPONDING с пользовательским мэппингом.

Выбор табличных строк

Как известно, только один вариант синтаксиса для обращения к таблице не сбивает с толку 90% программистов: квадратные скобки. Для обращения к первой строке таблицы следует написать

tab[ 1 ]

Да, это новый синтаксис ABAP для «READ TABLE tab INDEX 1».

Этот новый вид выражений работает во всех позициях грамматики ABAP с применением выражений (которая сводится к операндам чтения неустаревших непериферийных операторов). Но выбранная строка таблицы также является l-значением, сопоставимым в этом отношении с выражениями NEW и CAST, описанными в первой части:

tab[ 1 ] = struc.

tab[ 2 ]-c1 = 'a'.

Первая строка является присвоением, которое изменяет выбранную строку таблицы в целом, а вторая строка показывает изменение только одного компонента строки путем сцепления выбранной по номеру строки таблицы с компонентом через символ дефиса «–» и последующим именем поля таблицы.

В «[…]» доступны два способа выбора: по индексу или по значению(ям) компонента. Для второго способа применяется следующий формат:

tab[ c1 = x c2 = f( y ) ]

Если критерию поиска соответствуют несколько строк, выбор строки определяется порядком вставки (аналогично для READ). Оба варианта могут относиться к явному ключу, определенному для типа таблицы:

tab[ KEY key1 INDEX 1 ]

tab[ KEY key1 c1 = x c2 = f( y ) ]

Первичный или вторичный ключ, используемый для доступа к индексу, не должен являться хеш-ключом. Компоненты явного ключа должны совпадать, например, как в операторе «READ TABLE … WITH TABLE KEY». В противном случае выражение получает семантику произвольного ключа «READ TABLE … WITH KEY» с имплицитным использованием первичного ключа по возможности. Компоненты, как и имя ключа, могут быть динамическими:

tab[ KEY (keyname) c1 = x (compname) = y ]

Таким образом, для большинства разновидностей оператора READ существуют функциональные эквиваленты. (Варианты «FROM workarea» и «BINARY SEARCH» не поддерживаются.)

У разработчика на ABAP возникает естественный вопрос: Это READ … INTO или READ … ASSIGNING (т.е. я получаю копию строки или указатель на нее)?” Ответ: Обычно это неважно, компилятор учитывает контекст и тип строки:

  • Семантика указателя задается определенным контекстом (например, вызов метода, см. ниже).
  • Кроме того, если строка относится к родовому типу, указатель становится единственной опцией.
  • Если тип строки широкий или глубокий, компилятор выбирает семантику указателя, поскольку в данном случае это эффективнее. Если тип строки узкий и плоский, вы получаете семантику копии. Такая оптимизация применима в большинстве позиций значения r.

В вызове метода компилятор использует семантику указателя по умолчанию. (Семантика копии как неявная оптимизация может привести к непредвиденному поведению в результате побочных эффектов.) Однако если тип строки узкий и плоский, Extended Syntax Check (SLIN) выдает предупреждение с рекомендацией выбрать VALUE. Эта рекомендация подразумевает следующий вариант синтаксиса:

VALUE #( tab[ … ] )

Для выбора таблицы строка копируется из таблицы в отдельное значение. Как правило, этой рекомендации SLIN стоит последовать. Аналогичный вариант

REF #( tab[ … ] )

получает ссылку на нужную строку (например, если требуется параметр типа строки REF TO).

Это новый внутренний синтаксис для операторов VALUE и REF из первой части публикации. Дополнительного оператора для ASSIGNING не предусмотрено; это значение по умолчанию. Но как явно присвоить строку таблицы символу поля, возможно, совмещенному с линейный описанием? Например:

ASSIGN tab[ i ] TO FIELD-SYMBOL(<line>).

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

ASSIGN matrix[ x ][ y ] TO <point>.

Либо цепочка включает в себя тире и имя компонента («-tab1», «-c3»):

tab0[ x ]-tab1[ c1 = y c2 = z ]-c3

Обратите внимание на сходство синтаксиса с цепочками для вызова метода:

meth0( x )->meth1( p1 = y p2 = z )->a

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

t1[ x ]-reftab[ y ]->meth( )

t2[ x ][ y ]-ref->meth( )->a

«meth( )[ x ] « не разрешено, это синтаксическая ошибка

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

READ TABLE t1 INDEX x ASSIGNING FIELD-SYMBOL(<x>). « *yawn*

READ TABLE <x>-reftab INDEX y INTO DATA(ref). « zzzz…

ref->meth( ).

А что будет в случае сбоя при выборе? Очевидно, что для такой ситуации не предусмотрен код ошибки в SY-SUBRC. Побочные эффекты выражений – вещь крайне неприятная. Концепция выражения никогда не изменяет поля SY. Вместо этого проблемный выбор таблицы генерирует особую ситуацию класса CX_SY_ITAB_LINE_NOT_FOUND. Объект особой ситуации содержит информацию о предмете сбоя (например, какой из индексов). В ряде случаев, но не всегда, это позволяет выявить проблемный выбор в цепочке. Если требуется усилить контроль, разделите цепочку. Например:

ASSIGN t2[ x ] TO FIELD-SYMBOL(<x>).

CHECK sy-subrc = 0.

ASSIGN <x>[ y ] TO FIELD-SYMBOL(<y>).

В отличие от выбора на уровне выражений оператор ASSIGN, по собственной традиции, устанавливает для SY-SUBRC значение 0 (успех) или 4 (сбой), на которое можно реагировать в потоке управления.

Если вы подготовились к тому, что искомой строки не существует, и определили для такого случая значение по умолчанию, на помощь снова приходит оператор VALUE:

VALUE #( tab[ x ][ y ] DEFAULT deflt )

В случае сбоя любого выбора в цепочке выдается значение deflt. Для получения начального значения в данном случае пишите так:

VALUE #( tab[ x ][ y ] OPTIONAL )

Для тестирования наличия строки используется встроенная предикативная функция:

CHECK line_exists( tab[ c1 = x ] ).

Если требуется индекс найденной строки:

DATA(i) = line_index( tab[ c1 = x ] ).

Если строка не найдена, возвращается значение 0. Обе функции используются для выбора в цепочке. В этом случае результат относится к окончательному выбору в цепочке. Ни одна из них не генерирует особые ситуации.

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

tab1[ idx1 ]-a = tab2[ idx2 ]-x.

tab1[ idx1 ]-b = tab2[ idx2 ]-y.

tab1[ idx1 ]-c = tab2[ idx2 ]-z.

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

ASSIGN tab1[ idx1 ] TO FIELD-SYMBOL(<r1>).

ASSIGN tab2[ idx2 ] TO FIELD-SYMBOL(<r2>).

<r1>-a = <r2>-x. <r1>-b = <r2>-y. <r1>-c = <r2>-z.

Генератор таблиц

Мы рассмотрели обращение к табличным строкам. Следующей задачей является создание таблиц в виде значений без обращения к варианту «много мелкий манипуляций с состоянием»:

LOOP AT tab ASSIGNING FIELD-SYMBOL(<x>) WHERE c0 = ’X’.

INSERT VALUE #( d1 = <x>-c1 d2 = <x>-c2 )

INTO TABLE tab1.

ENDLOOP.

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

{ f (x) | x Î S, P(x) }

Вычисляется ли набор значений f (x) функцией результата f для всех элементов x из исходного набора S, для которых верен предикат P(x). В случае применения к контейнеру списка (распространено во многих функциональных языках, например, в Haskell) это называется генерацией списка. В ABAP роль контейнера играет внутренняя таблица, поэтому название было изменено соответственно. Синтаксически получаемое выражение f (x) перемещается в конец. Таким образом, генерация таблиц становится плавной генерализацией создания таблиц с помощью оператора VALUE (Рис. 1).

  • Условие FOR вводит LOOP на уровне выражения. Оно привязывает локальный символ поля (с именем «<…>») для семантики LOOP ASSIGNING или локальную переменную для семантики LOOP INTO. Как было сказано в первой части, локальную переменную можно повторно использовать в других выражениях, но не уровне оператора. Следует четко разграничивать локальные и нелокальные переменные.
  • Выражение после IN указывает исходную таблицу, строки которой, в свою очередь, привязаны к символу FOR.
  • Дополнительно проверенные стоки можно сократить до подмножества:
  • Условие FROM /TO указывает диапазон индексов и исходной таблице.
  • Условие WHERE фильтрует строки по логическому выражению. (Круглые скобки вокруг логического выражения обязательны, в противном случае возможны проблемы с синтаксическим анализом.)
  • Перед этими условиями может стоять условие USING KEY (не показано) для указания ключа таблицы для этих ограничений.
    • Условие LET для привязки локальных переменных уже знакомо нам по первой части статьи. В генераторе таблиц оно, как правило, используется для предотвращения множественных вычислений значения из текущей строки (для ссылки используется символ FOR). На Рис. 1 обратите внимание на привязку выбора таблицы (l-значение!) к локальному символу поля (не переменной). Это позволяет избежать копирования семантики для табличного поиска.
  • Наконец, далее следует одна или несколько спецификаций строк, как в статическом случае, но с использованием символа FOR.

Оформите подписку sappro и получите полный доступ к материалам SAPPRO

У вас уже есть подписка?

Войти