When talking about typing there is also a topic which requires some attention, namely interface parameter typing. The interface here refers to procedure's signature. From developers point of view this is like creating an API and exposing it to world. In other languages like C or VB, we usually see only this procedure interface as an entry point to some functional code behind it. This code can be hashed so it might not be seen by developer. We must therefore ensure that parameters we are passing are correctly typed.
Glossary
Before we start talking a little bit about the parameters themselves, I want you to get familiarized with the correct naming. In ABAP world, the procedure is code of block which has a signature (or interface), bodyand is reusable. So the procedure can be a Function Module, a Subroutineor a Method. The signature is nothing but how the procedure is seen outside (how it is exposed to the world). The body can be hidden (hashed) and is the actual code executed by procedure. I this blog I will be using name procedure when referring to any of the above. If referring to any specific I will directly point that out.
Local and global typing
In general you can use only global types (DDIC ones) for Function Modules' and Methods' parameters, and either local or global ones for Subroutines'. This however can be overcome, but is off-topic here.
Genericity
This term you might be already familiar with, or at least you have heard of that. Generic is something that is specified partly or is not specified at all. In ABAP it especially applies to typing parameters and field symbols. Saying that parameter is of generic type, means that developer (who uses the procedure) knows a little or knows nothing about the type of the parameter that should be passed. This implies that he/she can pass "partly any" or any data object he/she wants.
Let's have a look at the below table of how SAP categorizes parameter typing:
Category | Generic formal data type | Possible actual data type |
Fully generic | ANY | Any data type |
Table-like | ANY TABLE | Any table data type |
Index-accessed table | INDEX TABLE | STANDARD/SORTED table data type |
Standard table | STANDARD TABLE, TABLE | Any standard table data type |
Sorted table | SORTED TABLE | Any sorted table data type |
Hashed table | HASHED TABLE | Any hashed table data type |
Structure-like | STRUCTURE | Any DDIC structure data type (obsolete) |
Elementary, flat | c, n, x, p | Data type of respective elementary type without length specification |
As you can see parameter of type ANY really accepts data object of ANY data type. The similar is with type ANY TABLE.
Shopping relevance
You may think I am crazy but I think the generic typing has much more to do with shopping then we would think. Imagine that a wife (apologies to Ladies and bachelors) sends you to do the shopping in a supermarket. You get the following list of items:
Such list would make me a little bit confused. While it is very clear to me that under beer she most probably meant six-pack 😉 I am desperately trying to figure out what she meant by Potatoes or Some snack. I am not quite sure whether we need 1kg or 2kg of the former, and which snack she feels like having. Basically we can categorize above lists into several groups:
I am sure what to buy | I am not quite sure - I will be guessing or I will call her | I have no clue what she meant |
0,5 kg Apples, 5 tomatoes, One can of spinach | Milk (1 or 2 cartons, how much fat), Beer (actually she might want some for her) | Some snack (I probably buy any) |
The things "I am sure about" in ABAP would map to fully specified. The things "I am not quite sure" in ABAP would be those semi specified (or semi generic). The ones "I have no clue about" are those fully generic.
Responsibility
While for shopping list I am taking full responsibility for not being smart enough to figure out what my wife has meant, in ABAP the original developer is the one who must assure data correctness (as different developers under "any" might understand something different).
If we create i.e. Function Module with parameter ANY TABLE we must ensure we are not trying to use it in any type specific operation i.e. the following would cause syntax error:
READ TABLE itab ... INDEX i_index.
Obviously we are trying to access the table via INDEX, but the table can be either STANDARD, SORTED or HASHED one. The last one can only be accessed by key. So although the above seems syntactically correct we would get the error as the system is not sure which kind of table we really mean. In order we could run the program we must change the parameter to typed as INDEX TABLE. This way we restrict "consumer" (developer) to pass only STANDARD or SORTED table, thereby making our function less generic (more specified).
Possibilities
The rule is simple, the more generic program is, the more useful it may become. However always you should not overuse this programming "feature". At first place try to provide fully specified type, then if really necessary go for more generic typing.
Example
Let's say we have the developed a generic READ function, which returns the record of any internal table we pass to the function either via index or key access. This function could look like
FUNCTION Z_FM_READ_LINE .
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(ITAB) TYPE INDEX TABLE
*" REFERENCE(I_KEY_FIELD) TYPE STRING OPTIONAL
*" REFERENCE(I_KEY_VALUE) TYPE ANY OPTIONAL
*" REFERENCE(I_INDEX) TYPE I OPTIONAL
*" EXPORTING
*" REFERENCE(ES_LINE) TYPE ANY
*"----------------------------------------------------------------------
IF i_index IS SUPPLIED.
IF i_index IS NOT INITIAL.
READ TABLE itab INTO es_line INDEX i_index.
ENDIF.
ELSE.
READ TABLE itab WITH KEY (i_key_field) = i_key_value into es_line.
ENDIF.
ENDFUNCTION.
For importing parameters we used a fully specified i_key_fieldand i_index, a semi specified table (index table) and fully generic i_key_value(as we can pass any data object here) and es_line(return any structure line we want).
Formal vs actual parameter
Formal parameter is the one which a procedure has in its signature (interface), while the actualis the one passed to the procedure during its call. Therefore the formal parameter data type is the one which the parameter is typed with, while the actual has the type of real passed data object. During procedure call the formal parameter receives the actual one's type.
Formal parameter data type must therefore always be at least as "wide" as the actual parameter (or wider - more generic), but never vice versa. It must be possible to system to perform casting during parameter passing.
Note!
This restriction is especially important when passing object references, where casting to parent object is always possible, but to child object not necessarily. This is also a subject for dynamic casting operator ?= . Don't worry, we won't go as deep in our considerations here. This might be a topic for some next blogs.
No static component
Another point to consider is that by making es_linegeneric, we can't access its components in the function directly, like
es_line-carrid = …
es_line-connid = …
This is simply because we don't statically know whether this work area will ever receive the line type which has CARRID or CONNID components. This is only known during runtime - dynamically. So we say, the statictype of work area es_line (type of formal parameter) is generic but the dynamic one (type of actual parameter passed to procedure) will be as line of the table passed to FM.
Therefore in order we could access any of its components, we need to do this dynamically. This is the point where field symbols come into play.
Note!
Typing and working with field symbols is a task for next classes so I won't be discussing it here now.
This small piece of code is quite powerful and we could it is such program
DATA: gt_scarr TYPE STANDARD TABLE OF scarr,
gt_sflight TYPE SORTED TABLE OF sflight WITH UNIQUE KEY carrid connid fldate.
DATA: wa_scarr TYPE scarr,
wa_sflight TYPE sflight.
SELECT * FROM scarr INTO TABLE gt_scarr UP TO 10 ROWS.
SELECT * FROM sflight INTO TABLE gt_sflight
FOR ALL ENTRIES IN gt_scarr
WHERE carrid = gt_scarr-carrid.
DESCRIBE TABLE gt_scarr.
DO sy-tfill TIMES.
CALL FUNCTION 'Z_FM_READ_LINE'
EXPORTING
itab = gt_scarr
i_index = sy-index
IMPORTING
es_line = wa_scarr.
CALL FUNCTION 'Z_FM_READ_LINE'
EXPORTING
itab = gt_sflight
i_key_field = 'CARRID'
i_key_value = wa_scarr-carrid
IMPORTING
es_line = wa_sflight.
IF wa_scarr IS NOT INITIAL AND
wa_sflight IS NOT INITIAL.
WRITE: / 'First scheduled flight number for ', wa_scarr-carrid,
' is ',wa_sflight-connid,
' which departs on ', wa_sflight-fldate.
ENDIF.
CLEAR: wa_scarr, wa_sflight.
ENDDO.
The real examples are of course more useful and usually advanced then just reading a line of a table, but it basically shows how generic parameter nature makes the code more reusable.
Further grouping
Another grouping SAP uses for generic typing is the following
Category | Generic formal data type | Possible actual data type |
Elementary, flat | SIMPLE | Any elementary data type; flat, character structure |
Numeric | NUMERIC | Numeric data types: i, f, p |
Character-like | CLIKE | Text-like data type: c,n,d,t,string |
CSEQUENCE | Character-like data type: c, string | |
Hexadecimal | XSEQUENCE | Byte-like data type: x, xstring |
These categories are stricter in terms of actual parameter typing, but still allow the developer to work with partly generic data.
Preferred order
The order in which we should consider parameter typing is the following
Of course the choice depends on different factors i.e. whether we want to use the procedure only for purpose of certain program. Usually there is no need to create highly generic routines just for usage of one program, but on the other hand there is sometimes no point in creating a function module just for one time and case use. So you should always try to balance this thing.
Anyhow the choice should be always made by asking yourself a question in this regard keeping in mind the order of typing to consider.
One more way for generic typing
Before we sum up I want you to be aware of one more thing. You can pass the parameter generically by means of its data reference(pointer). Please do not confuse it with passing by reference (the other way is passing by value).
The genericdata reference is of type
type ref to data
The same we have for objects (those typed as ref to class)
type ref to object
This means we can pass reference of any data object we want and later in the procedure body we can dereference it using field symbols (also casting here is possible). More on this will come in the next part of this blog series.
Note!
Such formal parameter should be treated with special care as actual parameter can pass any reference. So it is like using generic type ANY, but it simply comes in form of a pointer to any data object, rather as data object itself.
Conclusion
Although generic parameter typing is tempting, as you can work with partly any or entirely any data object you want, it is also very error prone. You should always try to type the parameter as much specified as it is possible and use more generic types only if the first fails. Well, the true is that this creates opportunities to developer, but also leaves all the responsibility for any inconsistencies in the procedure coming through the generic nature of the formal parameters.
Generic typing is also very useful in combination with field symbols. But this is a subject for next reading...