What are your favorite and least favorite things about Fortran?


“but you can certainly define derived types that have other derived types as members.”

Yes, of course, and so also with structures, but that is not the issue with nested structure declarations. Consider the program:


    STRUCTURE /coord/
       LOGICAL*4 cart_f
       LOGICAL*4 polar_f
       STRUCTURE cart
          REAL*8 x,y,z
       STRUCTURE polar
          REAL*8 r,theta,phi

    RECORD /coord/ pos

    pos.cart_f = .TRUE.
    pos.cart.x = 2200.0
    pos.cart.y = 1723.0
    pos.cart.z = 425.0



The declarations of the structures cart and polar are nested within the structure coord. Their scope is restricted to the enclosing structure coord. This is the nesting issue which has no corresponding construct in derived type. fpt will move the nested declarations out of the enclosing structure, checking and correcting for name contention. This is the first necessary step in converting to derived types.

So the “problem” with automatic conversion is that you have something like an anonymous type whose scope is limited to within the enclosing structure. This could be translated as something like:

program t

integer, parameter :: wp = selected_real_kind(14)

type jfuy748rtfvnchdu
   real(wp) :: x, y, z
end type jfuy748rtfvnchdu
type uro58jdhti692udh
   real(wp) :: r, theta, phi
end type uro58jdhti692udh
type coord
   logical :: cart_f, polar_f
   type(jfuy748rtfvnchdu) :: cart
   type(uro58jdhti692udh) :: polar
end type coord

type(coord) :: pos

pos%cart_f = .true.
pos%cart%x = 2200.0_wp
pos%cart%y = 1723.0_wp
pos%cart%z = 425.0_wp

WRITE(*,*) pos%cart

end program t

Those oddball type definitions would of course be some kind of automatically generated hash strings, maybe 8 to 24 characters long, and constructed in a way to guarantee no conflicts within the scoping unit. If a programmer were doing the translation by hand, then he would define the types with meaningful names, say cart_type and polar_type, and use those. As shown, the final references to those variables is a straightforward translation from the nonstandard “.” to the standard “%”.

The other features you mention, UNION and MAP, are related to the way that common blocks could be declared in different scoping units with different types of variables. This was typically a way to reuse some workspace. Memory used to be limited and precious, so programmers did not want to waste even a byte.

Depending on how these are used in the legacy code, this can be done by making the derived type components allocatable. They would be unallocated in their “normal” state, and then when used locally they would be allocated as appropriate, used in the usual way, and then deallocated at the end. I can see that this would be more difficult to do automatically.

As with UNION .. MAP structure from DEC/VAX FORTRAN, the problem, I feel, lies entirely with the very expectations of fully automated translations of some nonstandard constructs and semantics into modern Fortran language.

In principle, anything may be possible. And those providing toolsets such as fpt may do better over time.

In practice, as of this decade, a KISS refactoring approach toward modernization striving for good coding practices and focusing on a codebase at a time will likely yield far better return on the time investment when such nonstandard features are involved in a legacy application.

With the STRUCTURE within a STRUCTURE case, the use of a Fortran MODULE to indicate a namespace where the “anonymous” type becomes a PRIVATE derived type entity whose name is mnemonic rather than mangled with arbitrary string sequences is something that any domain scientist who has to work with said codebase will find much easier to handle in terms of future enhancements and maintenance and support.

@RonShepard is quite right that the anonymous types have to be named, and his proposal to move the nested types out of the enclosing structure is in fact what fpt does. Provided that the refactoring tool has a complete symbol table and scope analysis (as fpt has) there is no need to generate meaningless names for the anonymous types. fpt constructs names and checks that there are no name collisions. The fpt output from the example program, removing nested structures, is:

        USE fpt_module_kinds
        STRUCTURE /cart_struc/
        STRUCTURE /polar_struc/
        STRUCTURE /coord/
           RECORD /cart_struc/cart
           RECORD /polar_struc/polar
        RECORD /coord/pos
        WRITE (*,*)pos.cart

I added the commands to change data sizes to kinds and to write the code in free format. In this code we could change the structures to derived types because there are no MAP and UNION constructs, but we have not yet automated this.

Whereas a manual output of the same can be like so:

module coord_m

   use fpt_module_kinds, only : kr8, k14


   real(kr8), parameter ::  ZERO = 0.0_kr8

   type :: cart_t
      real(kind=kr8) :: x = ZERO
      real(kind=kr8) :: y = ZERO
      real(kind=kr8) :: z = ZERO
   end type 
   type :: polar_t
      real(kind=kr8) :: r = ZERO
      real(kind=kr8) :: theta = ZERO
      real(kind=kr8) :: phi = ZERO
   end type 

   type, public :: coord_t
      logical(kind=kl4) :: cart_f = .false.
      logical(kind=kl4) :: polar_f = .false.
      type(cart_t)  :: cart
      type(polar_t) :: polar
   end type
end module
   use fpt_module_kinds, only : kr8
   use coord_m, only : coord_t

   type(coord_t) :: pos

   pos%cart_f = .true.
   pos%cart%x = 2200.0_kr8
   pos%cart%y = 1723.0_kr8
   pos%cart%z = 425.0_kr8

   print *, pos%cart


And which can be food for thought if anyone is considering automated translations …

1 Like

@FortranFan - thank you. That is much closer to the original semantics.