2

In order to check whether all entries of an internal table lt_itab meet a condition COND, I would like to use REDUCE statement. The loop of course needs to terminate once a line violating COND occurs. The second code block further down seems to work but appears to me like a slight abuse of the iteration index. Are you aware of a better/more transparent solution within the REDUCE syntax? Is it possible to work with a structure (integer, boolean) for the iteration variable? The INDEX INTO option seems not to work with REDUCE. Compatibility to kernel-release 753 (or lower) would be nice.

Here is my Minimal Reproducible Example (MRE) which passes syntax check only if lvr_flag_allowed = abap_false OR is commented out (viz -> "lvr_flag_allowed = abap_false OR):

DATA: lt_itab         TYPE TABLE OF i,
      rv_flag_allowed TYPE boole_d.

lt_itab = VALUE #( ( 2 ) ( 1 ) ( -1 ) ( 5 ) ).

IF lt_itab IS NOT INITIAL.
  rv_flag_allowed = REDUCE #( INIT lvr_flag_allowed = abap_true
                              FOR lvf_idx = 1 UNTIL lvr_flag_allowed =  abap_false OR
                                                    lvf_idx > lines( lt_itab )
                              NEXT lvr_flag_allowed = xsdbool( lt_itab[ lvf_idx ] < 0 ) ).
ENDIF.

RETURN.

Currently it gives this syntax check message (its ID is MESSAGEG[M):

The variable "LVR_FLAG_ALLOWED" cannot be used here.

Do you know the technical reason why this does not work? The SAP documentation on REDUCE - Reduction Operator only states

Usually the expression expr (after THEN) and the termination condition log_exp (after UNTIL or WHILE) depend on the iteration variable var.

Hence a workaround MRE came to my mind while writing this down:

DATA: lt_itab       TYPE TABLE OF i,
*      rv_flag_allowed TYPE boole_d,
      rv_last_index TYPE i.

lt_itab = VALUE #( ( 2 ) ( 1 ) ( -22 ) ( 5 ) ( 7 ) ( 4 ) ).

IF lt_itab IS NOT INITIAL.
  rv_last_index = REDUCE #( INIT lvr_last_index = 0
                              FOR lvf_idx = 1 THEN COND #( WHEN lt_itab[ lvf_idx ] < 0
                                                                THEN 0
                                                                ELSE lvf_idx + 1 )
                                              UNTIL lvf_idx = 0 OR
                                                    lvf_idx > lines( lt_itab )
                              NEXT lvr_last_index = lvr_last_index + 1 ).
ENDIF.

RETURN.

It makes "double" use of the iteration index and returns rv_last_index = 3. I'm returning an integer now rather than a boolean in order to check the correct abort result. Does this seem correct to you?

Many thanks in advance for your feedback and improvement suggestions (beyond the classical while/until loops ;-)) !

Suncatcher
  • 10,355
  • 10
  • 52
  • 90
dasausTeR
  • 23
  • 1
  • 6
  • 3
    The only possibility to exit at any time is to throw an exception using `COND` or `SWITCH` and word `THROW`, but then it won't return any value. You can ask SAP at their support Web site why they designed it this way. – Sandra Rossi Nov 04 '21 at 12:01
  • 2
    Related: https://stackoverflow.com/questions/52539819/can-i-check-for-initial-or-not-equal-values-with-line-exists – Jonas Wilms Nov 04 '21 at 18:35
  • 1
    unlikely you will avoid LOOPing, however you can reduce the number of iterations via [grouping by](https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US/abaploop_at_itab_group_by.htm) field you are checking on – Suncatcher Nov 05 '21 at 15:46
  • Thank you @SandraRossi for your input and moreover the very instructive improvements on my original post! `THROW` probably could be combined with `TRY ... CATCH ... ENDTRY` to set the final result. But I guess that's not best practice either. – dasausTeR Nov 06 '21 at 21:19
  • You can't "set the final result" with `THROW`, as I said before "it won't return any value." (it will raise an exception instead) – Sandra Rossi Nov 07 '21 at 03:19

1 Answers1

5

I would actually like to express this using line_exists, unfortunately table expressions only support equality comparisons though (and sadly not <):

"                                              invalid syntax v
DATA(some_negative) = xsdbool( line_exists( values[ table_line < 0 ] ) ).

A slightly more verbose but working variant would be using a LOOP AT with an immediate EXIT. (yes, this is not "modern syntax", though still very readable IMO):

DATA(some_negative) = abap_false.
LOOP AT values WHERE table_line < 0 TRANSPORTING NO FIELDS.
  some_negative = abap_true.
  EXIT.
ENDLOOP.

I don't think that REDUCE is the right tool for the job, as it is supposed to comprehend the table into one value (in other languages there is also no short-circuit, e.g. the .reduce in JS, though they have other methods for this purpose like .some and .every and the alike). If the number of truthy rows is low, not having short circuiting for them might be acceptable, and this REDUCE statement would at least not visit falsy rows through the additional WHERE ( ... ) clause:

DATA(some_negative) = REDUCE abap_bool(
  INIT result = abap_false
  FOR entry IN values
   WHERE ( table_line < 0 )
   NEXT result = abap_true
).
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 1
    Many thanks to @JonasWilms for your extensive input. In my given context, I have an itab of object references. The check is actually a method call `table_line->check( )` rather than `table_line < 0`. Apparently I should have been more precise in my original post as my MRE was too simple. But even with that in mind, `loop at itab assigning ` seems more readable and (for now) the way to go. For the sake of completeness: `REDUCE` can also be used with a structured data type for the iteration expression. One could use a boolean to store the check result. – dasausTeR Nov 06 '21 at 21:12