Technical Articles
Finally a declaration of immutable variables with FINAL in ABAP
Yep, we already spilled the beans in our last blog which introduced the language element STEP
for ABAP internal tables (SAP BTP ABAP Environment 2202). Nonetheless, if you haven’t heard of it yet… Here’s another new ABAP language element: It’s FINAL
! Available with ABAP Release 7.89, SAP BTP ABAP Environment 2208.
Basics
While writing code in ABAP, we often would like to declare immutable variables instead of mutable variables to avoid mutable state, like overwritten results, any unwanted changes, or even security threads. The concept of immutable variables can also be referred to as static single assignment and has been wished for from the SAP Community here. Now, the keyword FINAL
is introduced as the declaration operator of a declaration expression FINAL(var)
to declare an immutable variable var
. The inline declaration with FINAL
(for immutable variables) works similar to the inline declaration with DATA
(for mutable variables). Assigning a value to an immutable variable is possible at exactly one write position in the current context. The value cannot be changed at other write positions. If you try other positions for a write access of an immutable variable, either a syntax error (if detected by the compiler) or the uncatchable exception MOVE_TO_LIT_NOTALLOWED_NODATA
(if detected at runtime) will be raised. Want a quick overview? Scroll down or click here.
ABAP Programming Guidelines
To get a feeling when to use FINAL
and when not, the ABAP Programming Guidelines offer guidance for you. The rule for immutable variables states:
Whenever you want a variable to be filled at exactly one write position and to be read only at all other positions, use an immutable variable.
The ABAP Programming Guidelines also offer a good and bad example to point out the efficient use of the declaration operator FINAL
. Since we’ve already covered the basics, let’s get straight to some examples and find out when to use an immutable variable.
Use Case
Connecting this use case to the one from the blog about the keyword STEP
, we’re back to purchase orders. For the use case of the previous blog, we imagined to work with customer data and purchase order data as one part of our daily work. Preparing for the tasks concerning our work, we constructed an internal table which represented a joined table of customer and purchase data. For this use case, we’ll work with an internal table as well as with a database table. Even though the following examples might not necessarily be realistic, they emphasize the use of immutable variables in ABAP.
Quarterly purchase orders
Remember the quarterly orders of all customers? If not, no worries, this example is covered wholly. Below, you find the basic construct for the use case involving three methods representing the three examples.
CLASS purchase_orders DEFINITION.
PUBLIC SECTION.
TYPES:
BEGIN OF customer_purchase,
cust_id TYPE c LENGTH 8,
cust_name TYPE string,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF customer_purchase,
customer_purchases TYPE STANDARD TABLE OF customer_purchase
WITH EMPTY KEY.
METHODS quarterly_purchase_order.
METHODS customer_purchase_count.
METHODS last_change.
ENDCLASS.
CLASS purchase_orders IMPLEMENTATION.
"...
ENDCLASS.
START-OF-SELECTION.
DATA(inquiry) = NEW purchase_orders( ).
inquiry->quarterly_purchase_order( ).
inquiry->customer_purchase_count( ).
inquiry->last_change( ).
cl_demo_output=>display( ).
For the first example, we get an inquiry to list all purchase orders made in the fourth quarter of the year 2021 sorted from newest date to oldest date. The result will be used for further processing, like determining customer benefits. The internal table is constructed in the method quarterly_purchase_order
and the orders
table is filled according to the inquiry.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD quarterly_purchase_order.
TYPES:
BEGIN OF order,
item_id TYPE c LENGTH 9,
purch_date TYPE datn,
proc_date TYPE datn,
END OF order.
DATA orders TYPE TABLE OF order.
DATA(customer_purchases) = VALUE customer_purchases(
( cust_id = '00000001' cust_name = `Customer A` item_id = '781029348'
purch_date = `20211108` proc_date = `20211109` )
( cust_id = '00000001' cust_name = `Customer A` item_id = '781028275'
purch_date = `20211117` proc_date = `20211118` )
( cust_id = '00000001' cust_name = `Customer A` item_id = '781029350'
purch_date = `20211203` proc_date = `20211206` )
( cust_id = '00000002' cust_name = `Customer B` item_id = '781029348'
purch_date = `20211207` proc_date = `20211208` )
( cust_id = '00000003' cust_name = `Customer C` item_id = '781029353'
purch_date = `20211215` proc_date = `20211216` )
( cust_id = '00000004' cust_name = `Customer D` item_id = '781029321'
purch_date = `20211215` proc_date = `20211216` )
( cust_id = '00000005' cust_name = `Customer E` item_id = '781029342'
purch_date = `20211216` proc_date = `20211217` ) ).
LOOP AT customer_purchases REFERENCE INTO FINAL(customer_purchase) STEP -1
WHERE purch_date <= `20211231` AND purch_date >= `20211001`.
orders = VALUE #( BASE orders
( item_id = customer_purchase->item_id
purch_date = customer_purchase->purch_date
proc_date = customer_purchase->proc_date ) ).
FINAL(quarter) = `Quarter 4 2021`.
ENDLOOP.
cl_demo_output=>write( |Orders of all customers in { quarter }:| ).
cl_demo_output=>write( orders ).
ENDMETHOD.
"...
ENDCLASS.
In total, one inline declaration with DATA
and two inline declarations with FINAL
were applied. The reasons might be obvious:
DATA(customer_purchases)
: The declaration operatorFINAL
could be used too but we expect that values might be inserted later on. You could argue to declare immutable variables as long as you really need mutable variables; in this example, we can be sure that the values of the internal table will change and thus we save the time of changing the variable later on.FINAL(customer_purchase)
: The first appearance of the declaration operatorFINAL
is inside aLOOP AT
statement afterINTO
. UsingFINAL
inside a loop, a value is assigned to the variablevar
multiple times. This also shows that at runtime, multiple write accesses can be executed.FINAL(quarter)
: The second appearance of the declaration operatorFINAL
is for declaring a text string literal. Usually, strings are expressed as immutable: In ABAP, both mutable and immutable declarations are possible.
The result of the quarterly purchase orders example shows all purchases orders in the period from October to December 2021 from the newest date to the oldest date.
Orders of all customers in Quarter 4 2021:
ITEM_ID | PURCH_DATE | PROC_DATE |
781029342 | 2021-12-16 | 2021-12-17 |
781029321 | 2021-12-15 | 2021-12-16 |
781029353 | 2021-12-15 | 2021-12-16 |
781029348 | 2021-12-07 | 2021-12-08 |
781029350 | 2021-12-03 | 2021-12-06 |
781028275 | 2021-11-17 | 2021-11-18 |
781029348 | 2021-11-08 | 2021-11-09 |
Amount of customer orders
For the second example, we’ll switch to database access. The internal table, constructed in the former example, is now a database table (exemplified here as db_customer_purchases
). The inquiry is to count all purchase orders made by each customer and to store the entries in an internal table.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD customer_purchase_count.
SELECT cust_id, COUNT(*) AS count
FROM db_customer_purchases
GROUP BY cust_id
ORDER BY cust_id
INTO TABLE @FINAL(customer_count).
cl_demo_output=>write( customer_count ).
ENDMETHOD.
"...
ENDCLASS.
This example demonstrates the use of the declaration operator FINAL
when assigning the data of the result set to an internal table.
The result of the amount of customer orders example shows all customer IDs and the amount of purchase orders.
Customers and their amount of ordered items:
CUST_ID | COUNT |
00000001 | 3 |
00000002 | 1 |
00000003 | 1 |
00000004 | 1 |
00000005 | 1 |
Last update
For the third example, you want to know the time and date when you last assigned the result set of the query from the previous example to the internal table customer_count
.
CLASS purchase_orders IMPLEMENTATION.
"...
METHOD last_change.
FINAL(time) = COND string(
LET t = '130000' IN
WHEN sy-timlo < t AND sy-timlo > '010000' THEN
|{ sy-timlo TIME = ISO } AM|
WHEN sy-timlo > t AND sy-timlo < '010000' THEN
|{ CONV t( sy-timlo - 12 * 3600 ) TIME = ISO } PM|
WHEN sy-timlo = '120000' THEN
`High Noon`
ELSE
`Error` ).
cl_demo_output=>write( |Last change: { sy-datlo } { time }| ).
ENDMETHOD.
ENDCLASS.
In this example, the inline declaration operator FINAL
declares the variable time
. The value of time
is constructed with the conditional operator COND
of a conditional expression. The example is based on an example of the ABAP Keyword Documentation. Using FINAL
in this context emphasizes that expressions can be defined as values of immutable variables.
The result of the last update example shows the time according to ISO 8601 and the date when the result set of the query of example two was last assigned to the internal table customer_count
.
Last change: 20220825 12:09:12 AM
Why is it FINAL
and not CONSTANT
, CONST
, or IMMUTABLE
?
There was already lots of discussion going on about the naming of the keyword FINAL
in this blog. So, perhaps for some of you this might be the most anticipated section.
In the beginning of the discussion regarding the static single assignment and the naming of the inline declaration operator, CONST
was favored over other names. Reasons being, for example, that there was already the statement CONSTANTS
for declaring a constant data object or a constant and that there was already the addition FINAL
for classes and methods. When getting to the details of the different names, there is, however, a difference between a data object declared as a constant and a variable declared as final:
- Constant: Defined with the
CONSTANTS
declaration statement and only one start value can be assigned with theVALUE
addition at compile time. When the program is executed, the value of a constant is stored unchangeable in the PXA. - Final: Defined at one write position, but multiple write accesses can be executed at runtime at this position. At runtime, the value of an immutable variable is not stored in the PXA.
Therefore, an immutable variable isn’t a constant but a write-protected variable that is handled similar to other write-protected variables, like input parameters passed by reference or key fields of internal tables.
Fun fact
Like all other data declarations in ABAP, both constants and immutable variables are statically visible behind their declaration only, but dynamically available in the whole program. Let’s look at an example:
METHOD fun_fact.
"Fun fact (the infamous ABAP behavior)
ASSIGN ('NUMBER1') TO FIELD-SYMBOL(<fs1>).
ASSIGN ('NUMBER2') TO FIELD-SYMBOL(<fs2>).
cl_demo_output=>new(
)->write( <fs1>
)->write( <fs2> )->display( ).
FINAL(number1) = 111.
CONSTANTS number2 TYPE i VALUE 222.
ENDMETHOD.
The example shows some infamous ABAP behavior. Right at the beginning, two fields are assigned dynamically to two field symbols. The dynamic assignment is possible even though these fields are only declared at the bottom of the example: number1
with the declaration operator FINAL
and number2
with the statement CONSTANTS
. What happens now is that both fields can be read (but not written) and accessed by using the field symbols before their declaration; the value of FINAL
is set at the position of the FINAL
operator, while the value of CONSTANTS
is already stored in the PXA from the beginning. Thus, the result looks like the following:
0
222
Summary
The fundamental technical difference is that the values of constants are stored in the PXA and immutable variables are stored like other variables in the stack memory. Summarized, all differences are important for the naming of the inline declaration operator. The name CONST
is out of discussion at this point for the reasons above and because it’s more similar to CONSTANTS
than to FINAL
and some programming languages use CONST
as a type qualifier. Overall, the name of the inline declaration operator should be short and known. In Java, for example, FINAL
is used as a modifier for immutable variables. This is why IMMUTABLE
is also out of discussion. Considering the definitions and other programming languages, the naming of FINAL
is the logical consequence. However, if you still have any doubts about the naming, the comment section is always open for you.
Quick Check
Looking at the list below, the most important take-aways for the declaration operator FINAL
are listed.
- The declaration operator
FINAL
can be used at the same positions as the declaration operatorDATA
with the only exception that the statementOPEN CURSOR
cannot be used to declare an immutable cursor. - Immutable variables are write-protected variables that can be assigned a value at one write position only, but there for multiple times.
- Immutable variables can make programs more robust and therefore, use
FINAL
as the first choice from now on.
The following two links refer to the ABAP Keyword Documentation:
Further information
You should now know how to use the new declaration operator FINAL
in your projects. Use FINAL
for declaring immutable variables. The examples given in this blog are intended for demonstration purposes only. Did you notice another new language element and are you excited to use the new declaration operator FINAL
? Write your thoughts in the comment section. Don’t miss out on new language elements and follow my profile (Lena Padeken) for similar blog posts.
Even after reading through your examples I don't really see a use case for it. Your examples show how to to do it, but do not give any benefit of doing so.
(I personally did quite some programming in Clojure, which makes a big fuzz about the immutability of it variables, but at the same time introduces ways of circumventing it).
I think, we all know the benefit of constants.
Well, here we have a kind of constants that can be filled by expressions.
Well, I think variables are called variables because they are variable?
The funny thing for me is, that people use object orientation, and all class variables are actually a state vector of an object. Now they doing a big hype about states and immutability because they want to avoid statefulness in their programs.
When you want to avoid statefulness you should not use object orientation at first place, but try to use a functional programming style (which by the way can be achieved by using forms or function module while at the same time not using global variables).
Have you ever worried about accidentally changing a field when using a field-symbol assigning (or a data reference) an internal table-line? If yes, then with immutable field-symbols you need not worry anymore.
Tbh, i have been using FINAL for majority of my data declarations and rarely find the need to use DATA. Although, sometimes ADT quick fix refactoring options don't like FINAL 🙄
We replaced DATA(...) with FINAL( ) in the dcoumentation examples wherever possible ...
Hello Lena,
thanks for compiling this information.
FINAL is decribed only as an inline declaration. It seems to me there should not be any problem with allowing FINAL to be used just like the DATA keyword for normal (non inline) data declaration.
This means the creation of an immutable variable cannot be separated from its initialization. But I can imagine a use case with multiple initialization subroutines for an immutable variable where we guarantee the value declared as FINAL and passed as a changing parameter is initialized only once.
Is there a rationale for not allowing this?
best regards,
Jacques Nomssi Nzali
Isn't CONSTANTS already what you want?
And if you need an immutable variable for the context of a method, you can use
Hello Horst,
what I want is to be able to rewrite
as
or
I could extend this to a use case with multiple initialization steps. Generally speaking, we would have an identifier that is variable before the first assignment anywhere in code, than a immutable value after the first assignment.
As I write this, I realize we would probably need an operator/predicate to test if a value was already assigned to a FINAL identifier, like IS ASSIGNED for field-symbols.
So I recognize the functional call form that abstracts the initialization step will be good enough for me most of the time. So I would rewrite the LAST_CHANGE method in the blog to
best regards,
JNN
Maybe a good use case of data declaration with FINAL separated from initialization would be a public read-only attribute of a class for a value object. It's initialized in CONSTRUCTOR method once. The use of FINAL would emphasize that the class is meant as a value object. Just as an idea... 🙂
Hello Lena,
can a "dirty" assign (like in the FUN_FACT method) be used to change the value of a variable declared with FINAL? (that would be consistent with the nickname).
best regards,
JNN
Hello JNN,
In the case of the assign example, the value of
FINAL
is set after theASSIGN
statement. If you try to overwrite the value with a subsequentASSIGN
, the uncatchable exceptionMOVE_TO_LIT_NOTALLOWED_NODATA
will be raised:Best regards,
Lena
Hi Lena Padeken ,
first im glad that this Operator finally (:D) comes to ABAP. Just one Question, and that's a pretty big one:
Why is the FINAL Operator allowed inside a LOOP Statement?
For me if a FINAL Value is declared once it should never change. But when you make use of FINAL inside a LOOP it can change?! Where is the logical approach behind this?
I build a Sandbox Program to play around with it and wrote something like
In this example I thought I receive an Syntax Error because "number_value" is initialized with 1, then it is 2.. After the LOOP it's 4.
This is a very error prone implementation of a "immutable variable" in my opinion. Could you share some insides on this decision?
Best regards,
Sascha
Hi Sascha,
A variable with the declaration operator FINAL is defined at exactly one write position, for example, inside a LOOP statement. At this position, a value can be assigned multiple times. As soon as you try to change the value of a variable declared with FINAL at another write position, this will lead to your expected syntax error.
Best regards,
Lena
Hello Lena,
I like your concise explanation of the FINAL declaration operator. I understand how this is suitable in the ABAP context. Note the term immutable is not used.
The confusion IMHO stems from the use of the term immutable variable both in your blog and in the documentation. From this definition of immutable I would have expected the write protection to be independent of the write position. I understand this expectation is incorrect.
Maybe we should just avoid using the term immutable. From now on I will say a FINAL declaration creates and/or updates a write position protected variable.
Best regards,
Jacques Nomssi Nzali
Sounds very confusing, final in a loop, is not final