This is what I currently do, and have done for some 30 years, and I have found it to be widely supported and reasonably portable. However, the C preprocessor is not really part of the fortran standard, so we would also need to add that to the top five list.
I have a few files where I have #if 0 ... #endif blocks that are a few hundred lines long. They are basically a small paper describing the algorithm and the argument conventions that are built right into the fortran file itself. That way, I know the fortran code and the documentation will not be separated or out of sync.
At the moment, I’m in favor of looking at F202y (hopefully out in 2028 for the 70th anniversary) as a chance to make a limited number of non-backwards compatible changes as well as add important new features. So my list of 5 (well, 6) things probably looks like
generic programming (I’m worried about the power of our typing system, though)
better rank-agnostic programming (maybe a subset of the generics or maybe like Mathematics of Arrays where we treat array indexes as function transformations)
standardize minimal preprocessor support (cpp-like, presumably)
minor language convenience features: +=, -=, …, non-SAVE initializations, multi-ASSOCIATE instead of nesting, etc.
An area that I can’t speak to, but that seems important is enhanced support for parallel computing and hybrid computing (DO CONCURRENT and beyond). It seems it’s what Fortran is for, these years.
I only wrote it a few weeks ago, but without the comment I would already be confused about what it does. I’d like the ability to annotate actual arguments with intents in procedure calls, so that one could write
If this proposal were adopted and implemented in compilers, projects could require that all intent(out) and intent(in out) arguments in callers be so annotated, which would increase clarity. The reader could assume that non-annotated actual arguments correspond to intent(in) dummy arguments.
An FYI for prep(1) preprocessor users is that IF directives by default must be logical expressions so “#if 0” will not work by default with “–prefix ‘#’”; you must use the “-fpp” switch. I very much prefer keeping documentation and/or references to documentation with the source as well so prep(1) has a lot of options for that, including changing blocks of text to comments and/or writing them to separate files; so I do not use this method of embedding text any more myself, but I see that usage in others’ files.
Because of variations in cpp and fpp when I used something similiar I found “#ifdef DOCUMENTATION” a little more portable, as some other preprocessors also enforced the arguments to a #if to be a logical expression.
Many preprocessors also support /* … */ C-like comment blocks as well, but I also found that was not true of all default processors, at least in the past.
One of the tests for the prep(1) -fpp switch tests that “#if 0” syntax …
fpp mode tests
#if defined(_CRAYFTN) || defined(__NVCOMPILER)
BAD: ONE DEFINED
#else
GOOD: NONE DEFINED
#endif
#define _CRAYFTN
#if defined(_CRAYFTN) || defined(__NVCOMPILER)
GOOD: ONE SHOULD BE DEFINED
#else
BAD: NONE DEFINED
#endif
#define __NVCOMPILER
#if defined(_CRAYFTN) || defined(__NVCOMPILER)
GOOD: BOTH DEFINED
#else
BAD: BOTH NOT DEFINED
#endif
#if NOTDEFINED
BAD: "#if NAME" NOT treated like #ifdef NAME
#else
GOOD: "#if NAME" treated like #ifdef NAME like cpp and intel fpp
#endif
#if !NOTDEFINED
GOOD: "#if NAME" treated like #ifdef NAME like cpp and intel fpp
#else
BAD: "#if NAME" NOT treated like #ifdef NAME
#endif
#if 0
BAD: should not appear
#else
GOOD: should appear
#endif
#if 1
GOOD: should appear
#else
BAD: should not appear
#endif
EOF
fpp mode tests
GOOD: NONE DEFINED
GOOD: ONE SHOULD BE DEFINED
GOOD: BOTH DEFINED
GOOD: "#if NAME" treated like #ifdef NAME like cpp and intel fpp
GOOD: "#if NAME" treated like #ifdef NAME like cpp and intel fpp
GOOD: should appear
GOOD: should appear
In the C preprocessor, 0 is false and 1 is true. So #if 0 is indeed a logical test. That 0 is a character string, not a numeric value. The C preprocessor only works with character strings, there are no other data types.
GOOD: "#if NAME" treated like #ifdef NAME like cpp and intel fpp
This is at least a little confusing. Those two tests do not have the same meaning. Here is an example that demonstrates the difference.
A block of free-format text would be allowed for a write statement, or be used to generate an external document or define a CHARACTER array or be available as help, not just comments. This is allowed with the $BLOCK directive in the prep(1) preprocessor and greatly simplifies code development and documentation if you desire documentation and source to be contained in a single file; as an example.
At least a subset of this is available in many languages, such as the ``` strings in python.
Rank agnostic programming that would combine with whole array operations to allow for generic subprograms to handle arbitrary rank arguments, e.g.
subroutine add(x, y, z)
implicit none
real, intent(in) :: x(…), y(…)
real, intent(out) :: z(…)
z = x+y
return
end subroutine add
Do you have any more compelling examples? I find it unintuitive to write code like:
call add(x,y,z)
when z = x + y would already do. Moreover, don’t elemental procedures already satisfy this kind of usage pattern, where the operation should be applied independently over all array elements? The same function could be written as:
elemental real function add(x,y)
real, intent(in) :: x, y
add = x + y
end function
Thanks for your clarifying question! I provided an insufficiently complex example. You are correct that the example I provided could be written as elemental procedure so let me supply another example that is not elemental:
subroutine example2(v,w, x, y, z)
implicit none
real, intent(in) :: v(..), w(..), x(..), y(..)
real, intent(out) :: z(..)
real :: inner_product
inner_product = sum(v \* w)
z = x + inner_product*y
return
end subroutine example2
This example could be invoked on sets of argument arrays of any rank but is not elemental. The “why” I would like to have this is that I have some very complicated Krylov subspace algorithms that I would like to apply to a series of sets of arrays of the same, but arbitrary, rank. The algorithms can be expressed entirely in terms of whole array operations together with intrinsic functions that are rank-generic, i.e. sum(), without any reference to the rank of the dummy argument. Instead of two lines of code as in the example above the algorithm may have 50+ lines of code. This code must be invoked on multiple sets of arrays (all arrays in the set have the same rank but different sets of arrays have different ranks). At present, the only way I can implement this is to have multiple routines that share the same body via an include file (containing the algorithm code written in terms of whole array operations and intrinsics). This is clumsy and error prone at best as it requires maintaing a different subroutine for each rank.
@paprof I haven’t used it myself yet, but isn’t this already part of the F2023 standard to use the rank agnostic x(..) arrays and do operations on them, exactly as you wrote?
That is a good question but unfortunately the answer seems to be “No”. The 2023 standard has assumed rank dummy arguments that can be used in conjunction with a select rank construct but whole array operations are not permitted on assumed rank objects. From the standard: C838 An assumed-rank variable name shall not appear in a designator or expression except as an actual 16 argument that corresponds to a dummy argument that is assumed-rank, the argument of the function 17 C_LOC or C_SIZEOF from the intrinsic module ISO_C_BINDING (18.2), the first dummy argument 18 of an intrinsic inquiry function, or the selector of a SELECT RANK statement
Essentially assumed rank is really a rank-polymorphic object that has limited use just like polymorphic variables. What I would like to see is that whole array operations are permitted on assumed rank objects and that assumed rank objects can be supplied to elemental procedures. This would allow me to easily create rank-generic procedures that could handle arrays of any rank. Conceptually this seems like it would only require a compiler to translate whole array expressions into a set of nested loops (the number of nested loops being determined by the rank of the object).
To enable a rank-generic procedure using a select rank construct I would have to code another procedure to be invoked for each possible rank that I wanted to support. This would require the duplication of an algorithm in every routine that applies to a specific rank.
Thanks for your comment! It exciting the hear that these capabilities are in consideration for the 202Y standard! Either approach would make my life much easier.
Here is a request for a minor feature addition that should be easy for compiler developers to provide: Provide an instrinsic function (or a module variable, e.g. max_character_length, in the iso_fortran_env module) which would specify the maximum possible length of a character variable (which is processor dependent). This is necessary to write portable code that reads in character strings. Currently when defining a variable to receive a an input character string of unknown length one is left to guess at a string length and hope that a given processor supports a character length of that size. I frequently get questions from students learning Fortran of “How long can a character variable be?” Having to answer “It is processor dependent” is always unsatisfying and elicits frowns from students.