Перейти до вмісту

Конструкторські вирази

Конструкторський вираз (constructor expression) — це оператор (VALUE, NEW, CONV, CAST, COND, SWITCH, FILTER, REDUCE і т.д.), що будує значення певного типу прямо у виразі. Замість кількох рядків з тимчасовими змінними отримуємо компактний one-liner, який компілятор перевіряє на типи.

Загальний шаблон: OPERATOR type( ... ) або OPERATOR #( ... ), де # означає “виведи тип з контексту” (target type inference).

Будує структури та внутрішні таблиці.

TYPES: BEGIN OF ty_row,
id TYPE i,
name TYPE string,
END OF ty_row,
ty_tab TYPE TABLE OF ty_row WITH EMPTY KEY.
" Структура
DATA(row) = VALUE ty_row( id = 1 name = `Alice` ).
" Таблиця — кожен ( ... ) це рядок
DATA(tab) = VALUE ty_tab( ( id = 1 name = `A` )
( id = 2 name = `B` ) ).
" З # — тип береться з контексту (змінна вже оголошена)
DATA tab2 TYPE ty_tab.
tab2 = VALUE #( ( id = 3 name = `C` ) ).

Без BASE таблиця/структура повністю перезаписується:

tab = VALUE #( BASE tab ( id = 10 name = `X` ) ). " дописати до існуючого
row = VALUE #( BASE row name = `new name` ). " змінити лише поле
DATA(merged) = VALUE ty_tab( ( LINES OF tab1 )
( LINES OF tab2 FROM 2 TO 5 )
( id = 99 name = `manual` ) ).

Копіює структуру/таблицю у інший тип з автоматичним мапінгом за іменами полів:

TYPES: BEGIN OF ty_src, a TYPE i, b TYPE string, x TYPE c, END OF ty_src.
TYPES: BEGIN OF ty_dst, a TYPE i, b TYPE string, y TYPE c, END OF ty_dst.
DATA src TYPE ty_src VALUE ( a = 1 b = `t` x = 'X' ).
DATA(dst) = CORRESPONDING ty_dst( src ).
" dst-a = 1, dst-b = 't', dst-y порожнє (нема відповідника x)

Додатки:

" Зберегти існуючий вміст (за незбіжними полями)
dst = CORRESPONDING #( BASE ( dst ) src ).
" Явний мапінг імен
dst = CORRESPONDING #( src MAPPING y = x ).
" Виключити поле
dst = CORRESPONDING #( src EXCEPT a ).
" Для таблиць — пропустити дублі за ключем
dst_tab = CORRESPONDING #( src_tab DISCARDING DUPLICATES ).
" Глибокі структури — DEEP копіює вкладені компоненти
dst = CORRESPONDING #( DEEP src ).

Створює обʼєкт і повертає посилання на нього:

DATA(ref) = NEW zcl_service( dep = some_dep ).
" З # — тип з контексту
DATA ref2 TYPE REF TO zcl_service.
ref2 = NEW #( dep = other_dep ).
" Анонімний data reference
DATA(dref) = NEW i( 42 ). " data ref на INT зі значенням 42

Для обʼєктів — те саме, що CREATE OBJECT, тільки у форматі виразу.

Явне конвертування типу. Потрібне, коли компілятор сам не може вивести цільовий тип:

" Числовий літерал у decfloat
DATA(d) = CONV decfloat34( '3.14' ).
" Запакувати у string
DATA(s) = CONV string( sy-datum ).

Часто CONV використовується перед передачею у метод, який очікує конкретний тип.

Як CONV, але з жорсткою перевіркою: якщо значення не вміщається або втрачає точність — кидає виняток:

DATA(x) = EXACT i( '3' ). " OK → 3
DATA(y) = EXACT i( '3.14' ). " дамп CX_SY_CONVERSION_ROUNDING
DATA(z) = EXACT c length 2( 'abc' ). " дамп — не вміщається

Отримати data reference на обʼєкт даних:

DATA num TYPE i VALUE 42.
DATA(dref) = REF #( num ). " TYPE REF TO i
dref->* = 100. " змінює num
" REF на рядок таблиці
DATA(row_ref) = REF #( itab[ 1 ] ).

Downcast — звузити посилання до конкретного підтипу:

DATA animal TYPE REF TO lcl_animal.
animal = NEW lcl_dog( ).
DATA(dog) = CAST lcl_dog( animal ). " тип REF TO lcl_dog
dog->bark( ).

Якщо реальний тип не сумісний — CX_SY_MOVE_CAST_ERROR. Захист — через IS INSTANCE OF.

Умовний вираз — як тернарний оператор, але з кількома WHEN:

DATA(label) = COND string( WHEN n < 0 THEN `від'ємне`
WHEN n = 0 THEN `нуль`
WHEN n > 10 THEN `велике`
ELSE `мале` ).

Без ELSE і без жодного співпадіння — результат має початкове значення типу (порожнє для string). Часта пастка, коли забули гілку.

Те саме, що COND, але з єдиним операндом і літеральними варіантами:

DATA(day) = SWITCH string( sy-fdayw
WHEN 1 THEN `Monday`
WHEN 2 THEN `Tuesday`
" ...
ELSE `unknown` ).

Коли гілок багато і всі порівнюють одну змінну — SWITCH читається краще за COND.

Повертає підтаблицю за умовою — без копіювання усієї таблиці в памʼять:

DATA(big) = FILTER #( itab WHERE amount >= 1000 ).
DATA(not_big) = FILTER #( itab EXCEPT WHERE amount >= 1000 ).
" З використанням secondary key
DATA(f) = FILTER #( itab USING KEY sec WHERE status = 'A' ).

FILTER вимагає, щоб поля у WHERE відповідали або ключу таблиці, або щоб був вказаний USING KEY. Інакше синтаксична помилка.

Оголошує локальні змінні всередині виразу — зручно для уникнення повторних обчислень:

DATA(result) = VALUE ty_tab(
LET base = get_base( )
tax = base * '0.20'
IN
( total = base + tax type = `normal` )
( total = base * 2 type = `double` ) ).

LET ... IN ... доступне у всіх constructor-виразах.

Трансформація однієї таблиці у іншу без явного LOOP:

" Проста трансформація
DATA(names) = VALUE string_table(
FOR line IN users ( line-name ) ).
" З WHERE — тільки потрібні рядки
DATA(actives) = VALUE ty_tab(
FOR line IN users WHERE ( status = 'A' )
( id = line-id name = line-name ) ).
" FROM ... UNTIL — діапазон індексів
DATA(squares) = VALUE ty_nums(
FOR i = 1 UNTIL i > 10 ( n = i * i ) ).
" Кілька джерел
DATA(pairs) = VALUE ty_pairs(
FOR a IN list_a
FOR b IN list_b
( first = a second = b ) ).

Згортка — обчислення скалярного значення з таблиці (сума, мінімум, агрегат):

" Сума
DATA(total) = REDUCE i( INIT s = 0
FOR line IN itab
NEXT s = s + line-amount ).
" Максимум
DATA(mx) = REDUCE i( INIT m = 0
FOR line IN itab
NEXT m = COND #( WHEN line-v > m THEN line-v ELSE m ) ).
" Конкатенація
DATA(csv) = REDUCE string( INIT r = ``
FOR line IN itab
NEXT r = COND #( WHEN r IS INITIAL THEN line-name
ELSE |{ r },{ line-name }| ) ).

INIT задає стартове значення, NEXT — як оновлюється акумулятор на кожному кроці.

Групування через VALUE/REDUCE

Section titled “Групування через VALUE/REDUCE”

Групування (GROUP BY) всередині виразу — читаємо як SELECT з угрупуванням:

DATA(by_dept) = VALUE ty_grp(
FOR GROUPS <grp> OF line IN employees
GROUP BY line-dept
( dept = <grp>
count = REDUCE i( INIT c = 0
FOR m IN GROUP <grp>
NEXT c = c + 1 )
total = REDUCE p( INIT s = 0
FOR m IN GROUP <grp>
NEXT s = s + m-salary ) ) ).

Детальніше — див. сторінку про групування внутрішніх таблиць.

" Таблиця активних користувачів з нормалізованими іменами
DATA(users) = VALUE ty_users(
FOR u IN raw_users WHERE ( active = abap_true )
( id = u-id
name = to_upper( u-name )
created_at = CONV timestampl( u-ts ) ) ).
" Безпечний пошук зі значенням за замовчуванням
DATA(user) = VALUE #( users[ id = 42 ] DEFAULT VALUE #( id = 0 name = `unknown` ) ).
ЗадачаОператор
Побудувати структуру/таблицюVALUE
Скопіювати з мапінгом за іменамиCORRESPONDING
Створити обʼєкт або data referenceNEW
Конвертація типуCONV (м’яка), EXACT (строга)
Посилання на обʼєкт данихREF
DowncastCAST
Кілька умов → значенняCOND
Одна змінна → варіантSWITCH
Фільтрувати таблицюFILTER
Трансформувати таблицюVALUE ... FOR
Звернути таблицю у скалярREDUCE

Адаптовано з 05_Constructor_Expressions.md (Apache 2.0). Повний перелік нюансів — в оригіналі.