ABAP News for 7.40, SP08 – FOR Expressions
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
COL1 | COL2 | COL3 |
11 | 12 | 13 |
21 | 22 | 23 |
31 | 32 | 33 |
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 …
Hello Horst,
today i came across an error with a FOR expression that i can't explain.
Considering the following code everything is fine and the internal table chars is filled with 'T', 'e', 's' and 't'.
I if do it i a similar way like this, i get a STRING_OFFSET_TOO_LARGE runtime error saying that the string was accessed with offset 4.
Why is that the case? Where does the offset 4 come from? The only difference is that the substring access is wrapped with a string template.
Thanks for the clarification.
Christian
A bug ...
Hello Horst,
next one
.
Considering the following code which sums the multiples of a number (3) up to a given limit (1000).
This works fine and sum_of_multiples contains the right value of 166833. If i try to inline the first reduce statement and replace the multiples variable, the compiler complains saying "Error in STRUC-Stack".
What means this error? Can't i use REDUCE multiple times in one expression? Or does this error tell me that the expression is too complex like the infamous REGEX_TOO_COMPLEX runtime error? Or is there a subtle error in the code?
Thanks for the clarification.
Regards Christian
I just recognized that also a dump was created
Another one bytes the dust ...
And we really thought, that we have tested thoroughly enough
I'm curious how many you will still find of those ....
i'll give my best
Patches for both errors are initiated now.
Karsten Bohlmann invites you to a coffee at next TechEd
Hallo Horst,
I have tried the example with the reduced string from the ABAP Docs, but with a little extension: at the NEXT position I have added a COND construct, like this.
The scope of the statement is to filter any not allowed characters from a string.
The problem here is that the iteration runs only once
What is wrong with the construct?
Thanks Attila
The termination condition offs <= strlen( l_item ) is met from the beginning isn't it?
Yeap
I have seen that latter in debugger
After the right statement was implemented, it worked as planned(and after the string pattern was right defined).
Now I am comparing the runtimes with the "old" construct DO. IF. ENDIF.ENDDO. because this check must run in production over many many records.
I have one more question that is not to the subject related:
how can I define the literal "space" in string pattern. Now it is so defined:
regex = `[A-Za-z0-9\'\:\?\,\-\(\+\.\)/``]` (I have the feeling that I miss/overseen something here
)
Many thanks in advance
Why not simply ` `? Or `\s`?
See Special Characters in Regular Expressions
I do have read the docs, thanks for the link. Either with `` nor with \s works
Another thing to the REDUCE: the String #*+}Wow My Name should become after that +Wow My Name.
Instead of that the result looks like + W o w M y N a m e ,when "result" is with `` initialized.
where does the space overhead come from?
Thanks for clarification.
Attila
Operator CO does not evaluate regular expressions ....
From your string template, you have two literal blanks in there ....
Dear Team,
Can you please explain how it's work i am not able to understand anything..
Its very difficult to understand, one more question please correct me if i am wrong
Am i ABAP blog or in different blog.??
Thanks
Nishant
Hi Horst
Is there a way of using table expressions to add values of a column for an entity, eg, add total sales figure for customer line items and store them in an additional column.
Kind regards
Combining table expressions with appropriate constructor expressions should do.
Horst,some days ago I've posted a question on SCN but did not get an answer so far.Perhaps you can jump in with a hint. Please see my OP at:
http://scn.sap.com/thread/3797343
Back from vacation and
Christian Guenter solved it already, very good
Hi Horst
Very good features coming up with ABAP 7.4
One qtn regarding FOR operator..
Why do we need explicit assignment even if both source and target have same field names
DATA(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 )
( col1 = wa-col1 col2 = wa-col2 ) ).
if i do just like below I get syntax error
DATA(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 ) ).
Regards
Arshad
Hi Arshad,
I think you miss in this case the work area for the value transfer. It would be looking something like that:
data(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 ) ( wa) )
pretty simple
Cheers
Attila
Yep
Hello Horst,
How does one EXIT a FOR iteration?
E.g.,
How to build the FOR statement for these lines?
BR,
Suhas
What should that "do something" do? You can't place statements within a FOR expression (OK, calling a method would do).
There's nothing like EXIT or CONTINUE, to jump out of a FOR IN itab loop. In a FOR UNTIL loop you can manipulate the iteration variable.
But: instead of EXIT you can THROW an exception as workaround:
Best
Horst
Suhas approach isn't that unusual at all because if you need to find records in an internal table and you cannot use an equal condition like FLDATE = SY-DATUM you have to go this way.
So you cannot use: READ TABLE SFLIGHTS WITH KEY FLDATE <= SY-DATUM to get the first available record that fits this condition although there might be more of them.
You cannot even access this record using one of those table expression either. Hence you have to use Suhas' construct to fetch the first record and if found exit immediately.
I wish at least the new table expressions are way more flexible and support a more generic approach for determining records.
Michael
Yes, that's a common use case. We have to use the pattern
LOOP ... WHERE ...
EXIT.
ENDLOOP.
IF sy-subrc = 0.
...
ENDIF.
because READ does not support WHERE clauses with all kinds of logical conditions.
But I wonder if this is really a use case for table expressions too . In a FOR loop, you can use fully fledged WHERE clauses.
Horst
Hi Horst,
I am facing with the same Problem. I need only the first record that checks the WHERE condition - the pattern you just described. What would be in this case the solution?
PS: from that record, I need only one field for the further processing
.
Many thanks,
Attila
Hi Attila,
See the response from Jacques Nomssi here -
.BR,
Suhas
Sorry for being a bit late to the party, but why not making
up to par with
by adding some “EXIT WHEN log_exp”
! Beware, just suggesting a command, below is no current ABAP syntax !
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
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.
True, fixed by disclaimer
Just for fun another and slightly more complicated approach using a reduce expression...
What happened to KISS principle?
I am continuing the discussion here -
.I often have the requirement to copy one internal table into another and add/change some of the target's table members. Eg:
Starting from line 60 I tried to replace the coding from lines 51 up to 58, but I had no luck so far.
Any hints?
Thanks,
Michael
Sorry, for the screenshot. So far I could not paste any coding with syntax highlightning into SCN comments.
Hi Michael,
I would suggest if you can post your query as a question & not as a blog comment. That way if someone is googling for the same topic, (s)he can find it faster.
BR,
Suhas
PS - When posting a query, you'll get the syntax highlighting
Hello Horst,
I only work in the solution manager which at last gets all the nice ABAP 7.40 features with the release of version 7.2.
As far as I´ve been told it wasn´t possible to patch newer ABAP versions into the 7.1 branch, because of some weird technical contraints.
Do you maybe know, if thats still the case with 7.2 or will it be possible to update the ABAP module to 7.50 and newer once they are available?
Thanks and regards
Alexander
Hi Alexander,
Ask me anything about ABAP language, but please not about upgrades
Horst
Ok thanks for letting me know. I will see if I can find someone who has some details about this.
Maybe you can answer me some other question:
Is there already some improvement in Eclipse ADT to provide a better syntax highlighting? Last time I was able to use this (back in 2013) the code looked like in the SE80. But I really would love it, if the SE80 would provide some improvements for that too.
I think about some special formatting for instance/static method calls or similar things.
Alexander
Hey, that's a tools, not a language question ...
But as far as I know, they're constantly improving.
In 7.51 you will even be able to customize colors for selfdefined language elements.
It's already there, please check Customize Syntax Coloring in ABAP in Eclipse. Maybe not all is yet possible, but enough.
Hi, thanks for the info. Unfortunately I´m working in the Solution Manager Environment and up to the current version there´s no Eclipse support.
There will be support in version 7.2 which is currently in ramp up. However I only work in those WTS environments, where the performance of Eclipse is pretty bad. So there is no way around SAP GUI at least for the next couple of years.
Cheers
Hi
When using REDUCE in the following example the data in my table (MT_DATA) has amount values like 99.99 - always with 2 decimal places of precision. The data type /POSDW/AMOUNT is defined as CURRENCY (LEN 15, Decimals 2). However the result of the reduction is always an Integer value e.g. 100.
DATA(lv_amount) = REDUCE /POSDW/AMOUNT
( INIT amt = 0 FOR wa IN mt_data
NEXT amt = amt + wa-amount + wa-sales_tax_amount ).
Is this a "feature" of REDUCE or something that I am doing incorrectly?
All help greatly appreciated.
SOLUTION
The solution turned out to be very easy with a bit of a push in the right direction.
DATA(lv_amount) = REDUCE /POSDW/AMOUNT
( INIT amt = CONV /POSDW/AMOUNT( 0 )
FOR wa IN mt_data
NEXT amt = amt + wa-amount + wa-sales_tax_amount
).
Thanks Horst and Suhas.
Been there, faced that
Because you used INIT amt = 0, amt is defined(implicitly) as integer(I). If i remember correctly you can check this in the debugger! Try with INIT amt TYPE /POSDW/AMOUNT.
Just a suggestion, please post such questions as a post and not as a blog comment so that it is easier for others to find
BR,
Suhas
In your example, AMT is of type I. Use the CONV operator or TYPE behind INIT to enforce a p type inside the expression.
I faced the same issue – using an appropriate initial value for INIT variables also helps:
This coding behaves erronously (no decimal places):
DATA(lv_result) =
REDUCE decfloat16(
INIT x = 0
FOR <lv_float> IN lt_floats
NEXT x = x + <lv_float>
).
This way round it works (decimal places are calculated correctly):
DATA(lv_result) =
REDUCE decfloat16(
INIT x = '0.0'
FOR <lv_float> IN lt_floats
NEXT x = x + <lv_float>
).
Best regards,
Christoph
Hmm, by using a c field as "correct" type …
Not so good.
Let’s call it a decfloat16 literal. However, in my real-world application, I also went for the CONV solution as it caters for potential changes of the corresponding result type specified.
As I did not notice this issue and solution description when flying through this page for the first time, I added my own experimental code to increase the visibility of this issue in this rather comprehensive collection of issues related to the FOR and REDUCE statements. A more stackoverflow-alike one-issue-at-a-time format with voted solutions would definitely be desireable.
Regarding the REDUCE statement, I would say, it is not so good at the moment (e.g., there is no reference to this typing issue in the documentation).
Duh, the documentation says how the type of the helper variable is derived.
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).
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.
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.
If you type the first INIT variable, you can always use # for the general result.
Is there a reason why this is not implemented as default behaviour?
Cause it is a constructor expression and follows the general rules of those.
Hi Horst,
While using FOR with WHERE condition i noticed that if the WHERE condition fails then there is no change in the SY-SUBRC, So is there any other way to track the number of rows transferred to the left hand side internal table??? other than the NOT INITIAL check on the internal table.
Thanks & Regards,
Rakshith Gore
Hi Rakshith Gore,
Of course an expression should be side effect free and therefore should not influence system fields. Expressions are to be used at operand positions and there you cannot evaluate system fields anyway. If you need the number of rows selected (where sy-subrc wouldn’t be appropriate anyhow), you must think of other ways, e.g. using the built-in function lines before and after or working with LET expressions in combination with other expressions.
Best
Horst
Thanks Horst 🙂
Hello Horst,
I'm wondering... you have filter, reduce, ... but what about map?
See corresponding.
I meant the ‘map’ functionality in other programming languages (e.g. JavaScript).
Something like
loop at itab assigning field-symbol(<wa>).
function(<wa>).
endloop.
“for <wa> in itab function(<wa>)”
See e.g. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/map
… or a more general way like in python
https://www.tutorialspoint.com/python/python_for_loop.htm
Map would come very handy if you guys implement lambda expressions.
That would be a FOR outside a constructor expression.
So to say an extension of
meth( <wa> ).
to
for <wa> in itab meth(<wa>).
In the moment the workaround would be a REDUCE with a dummy result. But that works only, if meth has a returning parameter. You want to call arbitrary methods/functions in a loop.
The point is, I dont' see the benefit of the short form, if you don't use it at an operand position.
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.
Please suggest.
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.
https://help.sap.com/http.svc/rc/abapdocu_752_index_htm/7.52/en-US/index.htm?file=abenconstructor_expr_corresponding.htm
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
In my previous post all the code indentations were lost, so I try it with some screenshots:
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_len) = lines( poitem ).
poitem[] = VALUE #( FOR i = 10 THEN i + 10 UNTIL i 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_len) = lines( poitem ).
poitem[] = VALUE #( FOR i = 10 THEN i + 10 UNTIL i GT lines( poitem ) * 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
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:
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
(maybe you can also explain why a public read-only internal table is not a solution)
To follow OO best practice of encapsulation??
No. Thx for the tip 😉
“public read-only attributes” it a special caveat of ABAP, but yes, it respects encapsulation. So yes, it's not due to encapsulation..
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.
It reminds me this blog post about CL_OBJECT_COLLECTION.
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.
I can only say that the topic is beyond my competence.
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.