Description
The ASSOCIATE
construct is useful to introduce names for expressions in a scoped manner, but its utility is limited the frequent use for nesting. Utility would be significantly improved, if referencing previously associated names in the same ASSOCIATE
block would be allowed, i.e.
ASSOCIATE(b => a, c => b)
and similar.
1. Related suggestions
1.1. LET semantics
There are suggestions for a more general construct, usually referring to the concept as let
, e.g. “A shorthand for immutability #221” and “LET statement concept #279”.
If such suggestions would gain traction, they would cover this use-case.
However, they are much larger in scale. Extending the standard-compliant behavior of the ASSOCIATE
construct seems more likely to be accepted into the standard:
- It doesn't introduce a new keyword.
- It makes previously invalid code valid, but doesn't change the behavior of previously valid code.
- It is already allowed by Intel Fortran.
- It should be much easier to implement by other compilers, resulting in likely faster availability for usage in real-world projects.:₋)
1.2. Variable initialization without SAVE attribute
A similar purpose with more explicit typing would be fulfilled by “Syntax to initialize variables in declarations without SAVE #234” and “Deprecate and remove implicit save behavior #40”.
This suggestion still has the advantage of being independent of backward-compatibility considerations such as arise with disabling the implicit SAVE
attribute.
Compared to #234, this suggestion allows a more terse syntax, where types are inferred, using an already-existing mechanism.
2. Real world example
For example, the following block-construct is an anonymized version of code I wrote in my workplace project:
! Previously defined variables: iFreq0, frequencies, df, Omega, iStart, data
BLOCK
! locals
REAL :: frequency
INTEGER :: iFreq, iFreqPlusOffset, iFreqMinusOffset
INTEGER :: iOffset, iOffsetMinusFreq
COMPLEX :: A(3,3)
! body
frequency = frequencies(iFreq0)
iFreq = freq2Index(frequency)
iFreqPlusOffset = freq2Index(frequency + Omega)
iFreqMinusOffset = freq2Index(frequency - Omega)
iOffset = freq2Index(Omega)
iOffsetMinusFreq = freq2Index(Omega - frequency)
A(:,:) = diag(data(iFreq, iStart:iStart+2))
! ...
END BLOCK
Ideally, this BLOCK could be replaced with
ASSOCIATE( &
frequency => frequencies(iFreq0), &
iFreq => freq2Index(frequency), &
iFreqPlusOffset => freq2Index(frequency + Omega), &
iFreqMinusOffset => freq2Index(frequency - Omega), &
iOffset => freq2Index(Omega), &
iOffsetMinusFreq => freq2Index(Omega - frequency), &
A => diag(data(iFreq, iStart:iStart+2)))
! ...
END ASSOCIATE
This is allowed by Intel Fortran, with the expected behavior. It is rejected by, for instance, gfortran
, and does indeed not seem to be standard conforming. See e.g. discussion in this stackoverflow.com post.
In order to be allowed across compilers, currently nesting is
required, where each associate construct can only see names associated
in an outer scope.
ASSOCIATE(frequency => frequencies(iFreq0))
ASSOCIATE( &
iFreq => freq2Index(frequency), &
iFreqPlusOffset => freq2Index(frequency + Omega), &
iFreqMinusOffset => freq2Index(frequency - Omega), &
iOffset => freq2Index(Omega), &
iOffsetMinusFreq => freq2Index(Omega - frequency))
ASSOCIATE(A => diag(data(iFreq, iStart:iStart+2)))
! ...
END ASSOCIATE
END ASSOCIATE
END ASSOCIATE
! ...
END SUBROUTINE demonstration
The increase in nesting level hinders code readability and effectively discourages use of the ASSOCIATE
construct.
In practice most code in the project does not use either BLOCK
or ASSOCIATE
, and instead defines all local variables at the subroutine level, which leads to problems with refactoring, as values assigned in one section of large subroutines by default leak to the next section, regardless of whether the value, or only the variable name, is reused.
3. Limitations
Between this suggestion and those linked in “1. Related Suggestions”, none address the very much real-world scenario of needing to explicitly declare output variables for subroutines.
Since there is currently no efficient way of returning large data structures (vectors, matrices, ...) from a function, and due to the use of long-existing APIs like LAPACK
, it is common in Fortran to see results returned by output parameters.
For these, more convenient declaration of variables close to the code line, where they are first needed, would be discussed, e.g. implicit blocks, where
DO iDM = 1, n
TYPE(DataManagerType) :: dataManager
CALL getDataManager(iDM, dataManager)
! ...
REAL :: matrix(3,3)
CALL dataManager%getTransformationMatrix(matrix)
! ...
END DO
would be equivalent to
DO iDM = 1, n
BLOCK
TYPE(DataManagerType) :: dataManager
CALL getDataManager(iDM, dataManager)
! ...
BLOCK
REAL :: matrix(3,3)
CALL dataManager%getTransformationMatrix(matrix)
! ...
END BLOCK
END BLOCK
END DO