Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
horst_keller
Product and Topic Expert
Product and Topic Expert


With 7.40, SP05 the first version of the iteration operator FOR was introduced. You can use it in constructor expressions with VALUE and NEW for so called table comprehensions, as e.g.

DATA(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 )

                             ( col1 = wa-col2 col2 = wa-col3 ) ).

This is an expression enabled version of LOOP AT itab. It didn't take long to ask also for expression enabled versions of DO and WHILE (couldn't stand them in otherwise expression enabled examples any more ...).

Therefore, with 7.40, SP08 we also offer conditional iterations with FOR:

... FOR i = ... [THEN expr] UNTIL|WHILE log_exp ...

You can use FOR in constructor expressions with VALUE and NEW in order to create new internal tables, e.g.:

TYPES:
  BEGIN OF line,
    col1 TYPE i,
    col2 TYPE i,
    col3 TYPE i,
  END OF line,
  itab TYPE STANDARD TABLE OF line WITH EMPTY KEY.

DATA(itab) = VALUE itab(
     FOR j = 11 THEN j + 10 UNTIL j > 40
     ( col1 = j col2 = j + 1 col3 = j + 2  ) ).


gives

COL1COL2COL3
111213
212223
313233

Neat, isn't it?


But we don't want to construct internal tables only. Now that we have all kinds of iterations available with FOR, we want to construct arbitrary types. And that's where the new constructor operator REDUCE comes in.

... REDUCE type(

      INIT result = start_value

           ...

      FOR for_exp1

      FOR for_exp2

      ...

      NEXT ...

           result = iterated_value

           ... ) ...

While VALUE and NEW expressions can include FOR expressions, REDUCE must include at least one FOR expression. You can use all kinds of FOR expressions in REDUCE:

  • with IN for iterating internal tables
  • with UNTIL or WHILE for conditional iterations.


Let's reduce an internal table:

DATA itab TYPE STANDARD TABLE OF i WITH EMPTY KEY.
itab = VALUE #( FOR j = 1 WHILE j <= 10 ( j ) ).


DATA(sum) = REDUCE i( INIT x = 0 FOR wa IN itab NEXT x = x + wa ).

First, the table is filled with VALUE and FOR and then it is reduced with REDUCE to the sum of its contents. Note that there is no THEN used to construct the table. If THEN is not specified explicitly, implicitly THEN j = j + 1 is used. Be also aware, that you can place any expression behind THEN, including method calls. You only have to make sure that the end condition is reached within maximal program run time.

Now let's reduce the values of a conditional iteration into a string:

DATA(result) =

  REDUCE string( INIT text = `Count up:`

                 FOR n = 1 UNTIL n > 10

                 NEXT text = text && | { n }| ).

The result is

Count up: 1 2 3 4 5 6 7 8 9 10

These simple examples show the principle. Now imagine, what you can do by mixing REDUCE with all the other expression enabled capabilities. I only say nested REDUCE and VALUE operators ...

To conclude I show a cunning little thing that I use in some of my documentation examples:

TYPES outref TYPE REF TO if_demo_output.

DATA(output) =
  REDUCE outref( INIT out  = cl_demo_output=>new( )
                      text = `Count up:`
                 FOR n = 1 UNTIL n > 11
                 NEXT out = out->write( text )
                      text = |{ n }| ).

output->display( ).

I reduced the values of an iteration into the output list of a display object, oh my ...

82 Comments
horst_keller
Product and Topic Expert
Product and Topic Expert
0 Kudos
Duh, the documentation says how the type of the helper variable is derived.
Former Member
0 Kudos

I totally agree on that.

Nonetheless I do not find any reference to the CONV statement in it, although I guess it can be considered a best practice to always use it whenever the first helper variable in the INIT block is initialized with a literal (or maybe even in all cases).

horst_keller
Product and Topic Expert
Product and Topic Expert
0 Kudos
Well, in your example x is of type c length 3, isn't it?

Using INIT x TYPE decfloat16 would be more appropriate and you don't need CONV in that case.
Former Member
0 Kudos
For sure, that would also be a solution for both Niall and my cases.

However, I still wonder why typing the result value needs to be done twice by the user of the statement, one time as "real" result type and apparently also a second time as dedicated typing of the first INIT variable.
horst_keller
Product and Topic Expert
Product and Topic Expert
0 Kudos
If you type the first INIT variable, you can always use # for the general result.
Former Member
0 Kudos
Is there a reason why this is not implemented as default behaviour?
horst_keller
Product and Topic Expert
Product and Topic Expert
0 Kudos
Cause it is a constructor expression and follows the general rules of those.
udasari791
Member
0 Kudos

Hi

I am trying to manipulate of one internal tables data depends on other internal table.

ITAB1 has attributes as zipcode, addresses and more fields

ITAB 2 has valid Zipcodes

 

I have an attribute in ITAB 1, that holds a indicator (ex: Zip Indicator)

Logic: If ITAB 1 Zipcode is available in ITAB 2 then Attribute 'ZIP Indicator' in ITAB 1 should be 'X' otherwise space.

I want all the records from ITAB 1 with indicator updated . (Not a filter)

My code is below:

Lt_itab3 = same structure as ITAB 1.

 

  1. I am getting ‘x’ number of times records (X = lines of ITAB 2) for the above syntax
  2. If i use FOR with Where on ITAB 2, it filters the records

Please suggest.

0 Kudos
Is it possible to use a combination of CORRESPONDING and attribute assignments  in the work area for the value transfer of the FOR iteration. Something like below , but i get an error when i write this.



 
michael_umlauff
Explorer
0 Kudos
 

Hello Horst,

I often have to update just a few fields in a broad internal table with many columns, the most of them remain unchanged.

Is there any chance to include the unchanged fields in an easy way in table comprehensions?

In old school it looks very easy:

TYPES: gty_sflight TYPE SORTED TABLE OF sflight WITH UNIQUE KEY carrid connid fldate.
DATA: gt_sflight TYPE gty_sflight.

"Fill table gt_sflight in any way, e.g. from database table SFLIGHT
SELECT * FROM sflight INTO TABLE gt_sflight WHERE carrid = 'LH' AND connid = '400'.

LOOP AT gt_sflight ASSIGNING FIELD-SYMBOL(<fs_sflight>).

<fs_sflight>-price = <fs_sflight>-price * 2.
<fs_sflight>-seatsocc = <fs_sflight>-seatsocc + 30.

ENDLOOP.

I tried to have this with table comprehensions instead of LOOP .... ENDLOOP, but then I tediously have to enumerate all the unchanged fields in the expression. Is there any chance to default them?

DATA(gt_sflight_for) = VALUE gty_sflight( LET lt_sflight = gt_sflight IN
FOR ls_sflight IN lt_sflight
( price = ls_sflight-price * 2
seatsocc = ls_sflight-seatsocc + 30
"Here I laboriously must enumerate the remaining 12 unchanged fields
mandt = ls_sflight-mandt
carrid = ls_sflight-carrid
connid = ls_sflight-connid
fldate = ls_sflight-fldate
currency = ls_sflight-currency
planetype = ls_sflight-planetype
seatsmax = ls_sflight-seatsmax
paymentsum = ls_sflight-paymentsum
seatsmax_b = ls_sflight-seatsmax_b
seatsocc_b = ls_sflight-seatsocc_b
seatsmax_f = ls_sflight-seatsmax_f
seatsocc_f = ls_sflight-seatsocc_f
) ).

I had two other ideas, but they result in syntax errors as soon I try to change the two fields:

"Syntax error when adding "* 2" or "+ 30"
DATA(gt_sflight_for2) = VALUE gty_sflight(
FOR <l_str_sflight> IN gt_sflight
LET l_str_sflight2 = <l_str_sflight>
l_str_sflight2-price = <l_str_sflight>-price * 2
l_str_sflight2-seatsocc = <l_str_sflight>-seatsocc + 30 IN
( l_str_sflight2 ) ).

"Syntax error when adding "* 2" or "+ 30"
DATA(gt_sflight_new) = VALUE gty_sflight( LET lt_sflight = gt_sflight IN
FOR ls_sflight IN lt_sflight
( CORRESPONDING sflight( ls_sflight MAPPING price = price * 2 paymentsum = paymentsum +             30 ) ) ).

Does anyone have any good idea?

Thank you

Michael
michael_umlauff
Explorer
0 Kudos
In my previous post all the code indentations were lost, so I try it with some screenshots:



ShivaKona
Employee
Employee
0 Kudos
Hello Horst,


SPAN {
font-family: "Courier New";
font-size: 10pt;
color: #000000;
background: #FFFFFF;
}
.L0S32 {
color: #3399FF;
}
.L0S33 {
color: #4DA619;
}
.L0S52 {
color: #0000FF;
}
.L0S55 {
color: #800080;
}
DATA(lv_lenlinespoitem ).
poitem[] VALUE #(  FOR 10  THEN 10 UNTIL GT lv_len * 10
po_item i  acctasscat 'W' )  .

In the above syntax i have tried to embed corresponding so that only two fields of table poitem gets filled but was encountered with errors

DATA(lv_lenlinespoitem ).
poitem[] VALUE #(  FOR 10  THEN 10 UNTIL GT linespoitem ) * 10
po_item i  acctasscat 'W' )  .

And also i have tried giving length regex so that it runs for number of table rows but not successful

 

So please can you help me in this two points

 

Regards,

Shiva
nuno_godinho
Explorer
Hi Horst,

Before anything else, thank you for the great work you guys have been doing.

I'm really enjoying using the new FOR expression with VALUE, REDUCE, etc. But... it seems to still only work for internal tables. Ideally It would also accept classes that would implement some sort of IF_ITERABLE interface.

Scenario: class CL_VENDORS holds a list of instances of class CL_VENDOR in a private internal table. I would like to be able to do this:
codes = VALUE #( FOR vendor IN vendors ( vendor->code ) ).

But until now, as far as I understand, if I want to iterate through all the vendors I am forced to expose the CL_VENDORS private internal table.

Are there any plans to offer standardized iterators compatible with the FOR expression and can be implemented by any custom class?

 

Thanks,
Nuno
Sandra_Rossi
Active Contributor
0 Kudos
(maybe you can also explain why a public read-only internal table is not a solution)
sdfraga
Participant
0 Kudos
To follow OO best practice of encapsulation??
Sandra_Rossi
Active Contributor
0 Kudos
No. Thx for the tip 😉
sdfraga
Participant
0 Kudos

“public read-only attributes” it a special caveat of ABAP, but yes, it respects encapsulation. So yes, it's not due to encapsulation..

nuno_godinho
Explorer

Hi Sandra,

Today I’m using an internal table to implement the list of vendors. But tomorrow I may need to implement it in a completely different way (a collection, Object Services, lazy instancing, etc.). Or I may need to change the Internal table’s structure by replacing the simple list of references to CL_VENDOR with a more elaborate line including an indexed ID together with the reference, for performance reasons.

So, as I see it, exposing my data as a public read-only internal table is not a solution because it exposes and locks down the way the list is implemented. Simply put, it breaks encapsulation.

In order to avoid this I am declaring the internal table as private and implementing a public method called GET_LIST() which returns a list of references to CL_VENDOR. With this approach at least, unlike the public read-only, I still have the chance to adapt the GET_LIST() method If I ever change the internal implementation of the list. So I guess we can argue that using a PRIVATE internal table together with a GET_LIST() preserves encapsulation.

Still, having to use a GET_LIST() is not the most elegant approach. To cope with these scenarios most programming languages today directly support iteration of custom classes. That’s  why I was  curious to know if ABAP is planning to support these in the future.

Note: You could argue that, if the implementation is changed, I could still find a way to keep that public read-only internal table in sync with the actual list of vendors. But that would mean adding unnecessary complexity to the class.

Sandra_Rossi
Active Contributor
It reminds me this blog post about CL_OBJECT_COLLECTION.
nuno_godinho
Explorer
0 Kudos
Hi Sandra, thank you for sharing that blog post. I hadn’t read it. They are indeed related.

You didn’t mention if I managed to make it clear why using a public read-only internal table breaks encapsulation.
Sandra_Rossi
Active Contributor
0 Kudos
I can only say that the topic is beyond my competence.
0 Kudos
Hi,
I want to ask you if there is a way to use this sentence, when a value depends on other value, that is calculated inside the same FOR.  For instance in code of attached image, the value of ATBEZ, depends on IDENT.
Thank You!
dominik_augustin
Explorer
0 Kudos
I've just stumbled over the same requirement. I took the easy exit and extracted the calculations into a method, which makes the whole loop even more readable.

But, I do have another unsolved problem: the WHERE condition can't be checked against a function result.
" have to use an additional variable
DATA(wh_datub) = result-wa_db_datub + 365.

result-it_da = VALUE tt_da( FOR i = result-wa_db_datuv - 365
THEN i + 1
WHILE i <= wh_datub
( fetch_values_for_date( ia_datub = conv #( i ) i_fabkl = fabkl i_wa_db_datub = result-wa_db_datub i_wa_db_datuv = result-wa_db_datuv ) )
).

" would expect that this is possible
result-it_da = VALUE tt_da( FOR i = result-wa_db_datuv - 365
THEN i + 1
WHILE i <= result-wa_db_datub + 365 " <-- why isn't an expression possible?
( fetch_values_for_date( ia_datub = conv #( i ) i_fabkl = fabkl i_wa_db_datub = result-wa_db_datub i_wa_db_datuv = result-wa_db_datuv ) )
).
JaySchwendemann
Active Contributor
0 Kudos

Sorry for being a bit late to the party, but why not making

FOR ... IN itab

up to par with

LOOP AT itab
INTO wa
WHERE FLDATE <= sy-datum.

EXIT
ENDLOOP.

 

by adding some “EXIT WHEN log_exp”

! Beware, just suggesting a command, below is no current ABAP syntax !

FOR wa IN itab
WHERE ( FLDATE <= sy-datum )
EXIT WHEN abap_true
( wa ) ).

 

At least you have enriched VALUE #( itab[…] OPTIONAL ) in a similar way not forcing exceptions upon us (im mentioning this because one workaround would be throwing an exception).

However, possibly this is all yet possible with some FOR … THEN … WHILE already, so it might be syntactical sugar and one more command that adds to the already big language scope ABAP did acquire

 

Chees

Jens

Sandra_Rossi
Active Contributor
By reading your comment, some people may mistakenly believe that EXIT WHEN exists in ABAP language. Here, you are proposing to the ABAP team of SAP to enhance the ABAP syntax. EXIT WHEN does not exist in ABAP language.
JaySchwendemann
Active Contributor
True, fixed by disclaimer
ChristianGünter
Contributor

Hello Horst,

Long time no see 🙂

Today I stumbled about something interesting. Considering the following code which splits a string at ';' and returns a string table. It works as expected.

  METHOD if_oo_adt_classrun~main.

out->write(
VALUE string_table(
LET input = |111;abc;789| IN
FOR i = 0 WHILE i <= count( val = input sub = ';' ) + 0
( segment( val = input index = i + 1 sep = `;` ) ) ) ).

ENDMETHOD.

If I remove the superfluous + 0 after COUNT I get this syntax error and I'm wondering why. Any clues?


Wrapping COUNT with the redundant type conversion also works. But shouldn't it work without it?

BR Christian

P.S. tried it on several ABAP releases including latest Steampunk.

ChristianGünter
Contributor
0 Kudos

Wrapping the logical expression with parentheses also works.


 
horst_keller
Product and Topic Expert
Product and Topic Expert
Hi Chistian,

Seems to be a bug. The COUNT function is not recognized as an integer, only wrapping it in another expression does it. Outside FOR, the logical expression also works without that enforcement.

I opened an internal incident for the language colleagues.

Thanks for notifying that.

Horst

ABAP Documentation
horst_keller
Product and Topic Expert
Product and Topic Expert
Hi Christian,

 

Language colleagues have fixed it in the current release 7.96/8.16 (2405/2025) but there will be no patch. Feel free to open a customer incident, if you require patching.

Kind regards

Horst

ABAP Documentation

 







ChristianGünter
Contributor
0 Kudos
Thx a lot. No need for patching as there are easy workarounds.

Thanks again and kudos for the quick response!

BR Christian