Higher-Order Functions in ABAP
Contents
I have previously written that ABAP does not have higher-order functions, since there are no anonymous functions in the language. What this means is that a function (ie. callable piece of code) cannot be passed around in ABAP. We can, however, pass object references around like in any object-oriented language. These two things don’t immediately seem related, but William R. Cook gives some food for thought. After all, when we pass an object reference into a method, what have we actually done? According to Cook (2009, Section 3.1):
Object interfaces are essentially higher-order types, in the same sense that passing functions as values is higher-order. Any time an object is passed as a value, or returned as a value, the object-oriented program is passing functions as values and returning functions as values. The fact that the functions are collected into records and called methods is irrelevant. As a result, the typical object-oriented program makes far more use of higher-order values than many functional programs.
So an interface can be thought of as a structure of functions. These functions would ideally be pure (all functions in Cook’s example are) and the interface immutable by design (ie. a value type), though whether the functions are pure or not is immaterial: in any case, parameters (functions) customize what the method (higher order function) does. Returning interfaces (ie, functions) is also fairly common in ABAP. So ABAP does, indeed, have higher-order functions.
What I think mostly confuses this intuition is that object references almost always come with some associated, mutable state and have a name. This leads us to think of them more in terms of their state and identity than their methods, ie. ZCL_TIME_VALUE
instead of func inSeconds() -> Int
, making the connection with functions less than clear.
So What?
Now that we know that ABAP has higher-order functions, what can we do with them?
Reduce
We could combine higher-order functions with REDUCE
ABAP operation. First we create a type for a function that will act as if it were the callback parameter in Array.prototype.reduce in JavaScript.
|
|
Then we create a few implementations for it.
|
|
And then we create a higher-order method that will use this callback to alter what it does.
|
|
The behavior of this method is now determined by the “function” we pass as a parameter.
|
|
Unfortunately, ABAP’s clunky type system and lack of actual lambda functions means that this type of programming is not terribly well supported. Already we had to create an interface and three classes, and those types have had to have been global and have names. Those types will also only work for integers given how ABAP supports generic types. We could change the IMPORTING
parameters to generic type numeric
, but RETURNING
parameters cannot be generically typed, ie. at least the returned type will be fixed.
Compose
Reduce is just one type of higher-order function. Another is compose, which creates new functions from existing functions. This is a type of higher-order function that ABAP makes even harder to use.
Continuing on from our previous example, which more or less dealt with functions of type [Integer] -> Integer
, maybe we additionally have a function of type [Integer] -> [Integer]
that we would like to use with our previous function. Ie, we would like to do something with the table of integers before it is handled by the reduce. In Haskell, this would be simple.
|
|
Now for how this could be done in ABAP. First we will extract the reduce operation from reduce_int_table
method so that we have a function of type [Integer] -> Integer
. We also accept the reduce callback function in the constructor for the same reason. The method is defined as an instance method because otherwise we could not carry around a reference to it.
|
|
Then we define a function of type [Integer] -> [Integer]
and an implementation for it.
|
|
Now we would like to compose our double function with our reduce function. Calling one after the other is not too bad. We wrap the composed operation in a new class so that we can pass around a reference to it.
|
|
Not the prettiest, but it works. Now we would like to generically compose any two methods of these two types.
|
|
That is quite a few classes and interfaces! Given the poor support for generics and the fact that every function composition requires an interface and a class, any extensive use of these types of compositions leads to an explosion in the number of types that need to be defined and implemented.
Conclusion
The above demonstrates that while higher-order methods are possible, the support for different types of uses of higher-order functions in ABAP varies. Accepting and returning functions is something that you can easily do. Creating new functions from existing ones, in contrast, is arduous and combines poorly with ABAP’s type system. Creating composites or decorators would be simple, but this is because those patterns, by definition, deal with instances of the same exact type; but even here, the decorators and composites cannot be ad-hoc or defined in a single method, but have to instead be either global or visible in a report or class scope.
Higher-order methods are probably always useful to a degree, but they do not exist in a vacuum: what also matters is how they combine and interact with the rest of the language. In languages with dynamic types (JavaScript, Erlang), first-class functions combine with generic lists and other stuff. In languages with static types (Swift, Haskell), first-class functions combine with type inference and generics to allow for somewhat the same type of convenience. In ABAP, the lack of first-class functions combines with the partial support for generics in a way that makes higher-order functions less useful and causes an explosion in the number of types that need to be defined and implemented.
Bonus: Sum Types in ABAP
Fun fact: ABAP did not have actual enum types for the longest time. However, many values in SAP ERP/ECC – especially customizable values – more or less act like enums: for example, a purchase order type can be one of a set of allowed values and a purchase order is just a purchasing document of a specific category. While these values are enums in the database schema, to the ABAP compiler they are just character or number fields, which means that any checks for allowed values need to be implemented explicitly (excluding screens, which can do some of this automagically).
Actual enums in modern ABAP work as one would expect.
|
|
That is, if you expect enums to be just simple values that can be compared within the enum type but do little to nothing extra – somewhat like atoms in Erlang and Prolog, except comparisons can only happen among members of the ABAP enum type.
Languages like Swift and Haskell extend the basic idea of enums to so-called “sum types”. Typical example of a sum type is a Maybe type. A function that might fail for certain values can return a Maybe to represent that it may or may not return some type of result.
|
|
Traditionally in ABAP, the equivalent would be represented by throwing an exception from a method (thus returning no actual value from the method). Or in BAPIs, which have special rules for their interfaces, this type of failure would be represented by returning empty tables and structures, along with an error message.
Interestingly, Cook (2009, Section 5.2) notes that classes can be used to simulate sum types in ABAP, meaning we can create our own Maybe -type.
|
|
It is a bit clunky, given that ABAP also does not support pattern matching. Here, too, ABAP’s type system makes things worse: since ABAP does not support generic types, we must either have a Maybe -type for every type we want to return, or give up type safety and make the Maybe type generic in the way ABAP allows (TYPE string
, TYPE REF TO data
, TYPE REF TO object
). The latter would have the downside that all methods that return a Maybe would have the same return parameter signature RETURNING ... TYPE REF TO zcl_maybe
, whereas in the Haskell example we had -> Maybe ProductionOrder
.
Again, features of a language combine to make different ways of programming feel natural – or unnatural.
Sources
- Cook, W. R. (2009) On Understanding Data Abstraction, Revisited. OOPSLA 2009, Orlando, Florida, October 25–29, 2009.