Closures are used in several programming languages, but are far removed from ABAP. That doesn't stop the challenge of figuring out whether it can be done in ABAP, and I believe the answer is yes! This is the crazy beaten track part of what started off as one blog about my trying to implement a closure in ABAP. The sensible ABAP-Only learning part and deep dive into ABAP Memory is
over here, and in this blog I'll explain a little about closures and delve deeper into my experiment.
A closure is typically used in JavaScript and other languages, and is when a function works with a closed dataset outside its own scope. It is also closed in the sense that it is not global either. In a sense the data scope is something between an instance attribute and global variable.
T
he owner of the data needn't even exist anymore. In JavaScript for example we can nest functions, and an outer function A can pass back an inner function B that works with the outer function's data even after the outer function has completed. A nice step by step explanation is here.
Why would we do this? It is similar to an anonymous function, but it also allows delayed execution. In other words it acts like a callback function. Hence it's an important feature of web-oriented languages where asynchronous execution is common. W
e can derive some data now, pass a function to someone for later execution. Sure this can all be done with ABAP objects in some way or another, but I wanted to implement the scoping model that makes a closure a closure.
*** DISCLAIMER *** : This is theoretical purely for educational purposes with no practical use in ABAP that I have been able to establish. I'm not advocating this as a technique to use in any real world program. I thought it would be fun to try to implement a feature found in JavaScript (and others) using ABAP, that's all. A secondary goal was to figure out if there would be any practical use in ABAP.
Show me the Code
So how can we create a closure in ABAP? Note my original work was with a global and a local class fulfilling the role of the outer and inner functions, but for this blog I put everything into one report for simplicity. The end result is the same.
First we start with the 'inner function', represented by local class
lcl_number. Its job is to do some arithmetic (method ADD) with a referenced number. In order to avoid dynamic programming with a local/global context, I also used an interface:
*--------------------------------------------------------------------*
INTERFACE lif_number.
*--------------------------------------------------------------------*
METHODS add IMPORTING i TYPE i
RETURNING VALUE(result) TYPE i.
ENDINTERFACE.
*--------------------------------------------------------------------*
CLASS lcl_number DEFINITION.
*--------------------------------------------------------------------*
PUBLIC SECTION.
INTERFACES lif_number.
CLASS-METHODS create IMPORTING i TYPE REF TO i
RETURNING VALUE(result) TYPE REF TO lif_number.
PRIVATE SECTION.
DATA iref TYPE REF TO i.
ENDCLASS.
*--------------------------------------------------------------------*
CLASS lcl_number IMPLEMENTATION.
*--------------------------------------------------------------------*
METHOD create.
DATA(num) = NEW lcl_number( ).
num->iref = i.
result = num.
ENDMETHOD.
METHOD lif_number~add.
ASSIGN iref->* TO FIELD-SYMBOL(<i>).
<i> = <i> + i.
result = <i>.
ENDMETHOD.
ENDCLASS.
Next, we want the equivalent of the outer function, a global class (here just
lcl_main) that has the data in an attribute. All it does it instantiate
lcl_number with reference to its attribute
i, and returns it to the caller.
*--------------------------------------------------------------------*
CLASS lcl_main DEFINITION.
*--------------------------------------------------------------------*
PUBLIC SECTION.
METHODS get_num RETURNING VALUE(result) TYPE REF TO lif_number.
PRIVATE SECTION.
DATA i TYPE i.
ENDCLASS.
*--------------------------------------------------------------------*
CLASS lcl_main IMPLEMENTATION.
*--------------------------------------------------------------------*
METHOD get_num.
result = lcl_number=>create( REF #( i ) ).
ENDMETHOD.
ENDCLASS.
Now let's have some fun:
*--------------------------------------------------------------------*
START-OF-SELECTION.
*--------------------------------------------------------------------*
DATA o TYPE REF TO lcl_main.
o = NEW lcl_main( ).
DATA(num1) = o->get_num( ).
o = NEW lcl_main( ).
DATA(num2) = o->get_num( ).
CLEAR o.
WRITE / |Num1 add 1 three times: { num1->add( 1 ) }, { num1->add( 1 ) }, { num1->add( 1 ) }|.
WRITE / |Num2 add 1 two times : { num2->add( 1 ) }, { num2->add( 1 ) }|.
write / 'Collect garbage'.
cl_abap_memory_utilities=>do_garbage_collection( ).
WRITE / |Num1 add 2 three times: { num1->add( 2 ) }, { num1->add( 2 ) }, { num1->add( 2 ) }|.
WRITE / |Num2 add 2 two times : { num2->add( 2 ) }, { num2->add( 2 ) }|.
Executing this, we see that
num1 and
num2 maintain independent counter variables that are external and detached from their parent.
Let's go through this step by step:
First we instantiate
lcl_main and ask it for an instance of
lcl_number. This number object
does not hold any data but works directly with the first
lcl_main instance's attribute
i via a reference.
Then we overwrite o with a new instance of
lcl_main and repeat. We end up with two instances of
lcl_number that reference attributes of two different no-longer-existing instances of
lcl_main.
We'll add and write out the result a few times for each variable. Note how num1 and num2 each increment independently, since they were spawned by separate
lcl_main instances.
We'll also collect garbage, ensuring the
lcl_main object instances are wiped out. Note how each
lcl_number instance keeps its value, even though the
lcl_main object no longer exists.
Conclusion
If anyone has any practical use in ABAP, do let me know. The closest I've come is perhaps a multi-window dynpro where the same values for multiple objects can be determined once and each window can work with it and react and pass instances around in events.
It would also be an interesting technique for better control over data, or overcoming some of the issues with static attributes, or enforcing mutability. Or we just do it because we can
🙂
The example source code is on GitHub
over here