@RonShepard
“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:
PROGRAM t
STRUCTURE /coord/
LOGICAL*4 cart_f
LOGICAL*4 polar_f
STRUCTURE cart
REAL*8 x,y,z
END STRUCTURE
STRUCTURE polar
REAL*8 r,theta,phi
END STRUCTURE
END STRUCTURE
RECORD /coord/ pos
pos.cart_f = .TRUE.
pos.cart.x = 2200.0
pos.cart.y = 1723.0
pos.cart.z = 425.0
WRITE(*,*)pos.cart
END PROGRAM t
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:
PROGRAM t
!
USE fpt_module_kinds
!
STRUCTURE /cart_struc/
REAL(KIND=kr8)x,y,z
END STRUCTURE
STRUCTURE /polar_struc/
REAL(KIND=kr8)r,theta,phi
END STRUCTURE
STRUCTURE /coord/
LOGICAL(KIND=kl4)cart_f
LOGICAL(KIND=kl4)polar_f
RECORD /cart_struc/cart
RECORD /polar_struc/polar
END STRUCTURE
!
RECORD /coord/pos
!
pos.cart_f=.TRUE.
pos.cart.x=2200.0
pos.cart.y=1723.0
pos.cart.z=425.0
!
WRITE (*,*)pos.cart
!
END PROGRAM t
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
private
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
end
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.