Elemental statement provides a simple and generic interface for a procedure regarding its rank -dimensions-.
Due to Fortran strong typing programming style, It was a bit challenging to provide generic functions. It required to implement all possible forms (type and rank-dimensions-). Elemental focus in the rank issue with a simple interface.
Note an elemental procedure must be declared as scalar. So no arrays are allowed as input or output. The compiler takes care to deal with the output.
Furthermore, a function/subroutine becomes pure once elemental is defined. However it can also be set to impure (Useful when debugging as it allows to print :D).
Before
Check below how it used to be done to create a common interface for multiple dimensions -rank-. For instance, calculate procedure has implementations for scalar and arrays(…) to cover overall cases:
interface calculate
module procedure calculate_scalar, calculate_i1, calculate_i2, ..., calculate_iX
end interface
function calculate_scalar(val1, val2)
!< integer scalar implementation
integer, intent(in) :: val1
integer, intent(in) :: val2
...
end function calculate_scalar
function calculate_i1(val1, val2)
!< integer array rank 1 implementation
integer, intent(in) :: val1(:)
integer, intent(in) :: val2(:)
...
end function calculate_i1
function calculate_i2(val1, val2)
!< integer array rank 2 implementation
integer, intent(in) :: val1(:,:)
integer, intent(in) :: val2(:,:)
...
end function calculate_i2
...
Overall each form is manually written. In consequence, it is not practical at all.
Example
Let’s move on the next example. It shows how to implement elemental as pure (default) as well as impure.
program elemtest
implicit none
!< declare interface
interface
elemental integer function calculate(a, b)
integer, intent(in) :: a, b
end function calculate
elemental impure integer function calculateImpure(a, b)
integer, intent(in) :: a, b
end function calculateImpure
end interface
integer :: val1(3), val2(3)
!< compiler takes care to properly allocate 'results'
integer, allocatable :: results(:)
!< initialize values
val1 = [1,2,3]
val2 = [4,5,6]
results = calculate(val1, val2)
write (*,*) "results =", results
results = calculateImpure(val1, val2)
write (*,*) "(impure) results =", results
end program elemtest
elemental integer function calculate(a, b)
!< apply naive operation
!< interface implementation
integer, intent(in) :: a, b
calculate = a + b - 1
end function calculate
elemental impure integer function calculateImpure(a, b)
!< apply naive operation
!< interface implementation for impure
integer, intent(in) :: a, b
write(*,*) "(Impure) a, b = ", a, b
calculateImpure = a + b - 1
end function calculateImpure
As expected, the output values between both functions are the same. On the other hand, the impure statement it allows to print some values when executed.
results = 4 6 8 (Impure) a, b = 1 4 (Impure) a, b = 2 5 (Impure) a, b = 3 6 (impure) results = 4 6 8
Careful
Finally, always provide the same length for the arguments. See below what happens in case the dimensions are different:
...
integer :: val1(3), val2(2)
!< compiler takes care to properly allocate 'results'
integer, allocatable :: results(:)
!< initialize values
val1 = [1,2,3]
val2 = [4,5] !< different size
...
Compiler error:
elem.f90:20:28: 20 | results = calculate(val1, val2) | 1 Error: Different shape for elemental procedure at (1) on dimension 1 (2 and 3)