Skip to content

Allow ASSOCIATE to reference previous names in the same ASSOCIATE constuct, e.g. ASSOCIATE(b=>a, c=>b) #321

Open
@kbauer

Description

@kbauer

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:

  1. It doesn't introduce a new keyword.
  2. It makes previously invalid code valid, but doesn't change the behavior of previously valid code.
  3. It is already allowed by Intel Fortran.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions