Metaprogramming vs. generic programming in Fortran

From what I read on Stack Overflow and Wikipedia, generally speaking metaprogramming is to generate code using another code, while generic programming usually refers to procedures that can adopt many data types. Something like a C++ template is a mix of both? (I’m not 100 percent sure because I’m not a C++ programmer.)

Since many Fortran projects make heavy use of preprocessors (for example, fypp in stdlib), and with preprocessors we could even do something like the following

interface
    #: for i in range(1, 10)
    function func_${'%2.2d' % (i,)}$
    end function func_${'%2.2d' % (i,)}$
    #: endfor
end interface

which is hard to to implement even if generic programming is included in the newer Fortran standard. Why do we still want to put generic programming into the Fortran standard? Or will the generic programming in the newer Fortran standard similar to the C++ template that also includes metaprogramming?

PS: I read about Preprocessor support · Issue #78 · fortran-lang/fpm (github.com) and looking forward to fypp support on fpm!

2 Likes

The downside of a preprocessor (like fypp) is it knows relatively little about the semantics of the underlying language getting processed, working mainly through text substitution and macro expansion. E.g. on Wikipedia you may read

The most common example of this is the C preprocessor, … . Because it knows nothing about the underlying language, its use has been criticized and many of its features built directly into other languages.

I think this also reflects some of the opinions in the j3-fortran/fortran_proposals thread on preprocessing. Many use cases for preprocessing could be accommodated within the language.

While stdlib currently uses the approach you’ve just shown (out of pragmatic reasons), it can/does lead to bloated libraries as expressed here. With C++ templates, the compiler will perform something called template specialization, creating and compiling only the versions of the function which are actually needed. There is a nice website called cppinsights.io which shows you the template specialization. This can be very useful when debugging template meta-programs.

Here’s a code example:

template<typename T>
void swap(T& a, T&b) {
  T temp;
  temp = a;
  b = a;
  a = temp;
}

int main(){
  int a{1}, b{2};
  swap(a,b);
  return 0; 
}

And this is the template-specialized version created by the compiler:

template<typename T>
void swap(T& a, T&b) {

  T temp;
  temp = a;
  b = a;
  a = temp;
}

/* First instantiated from: insights.cpp:16 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void swap<int>(int & a, int & b)
{
  int temp;
  temp = a;
  b = a;
  a = temp;
}
#endif


int main()
{
  int a = {1};
  int b = {2};
  swap(a, b);
  return 0;
}

In your Fortran/fypp example you just created and presumably compiled 9 procedures, but perhaps your client code only calls one of them. With C++, you could write a single version with something along the lines of:

template<int N>
double func(...) {
  static_assert(1 <= N && N <= 10,
    "func: integer N out of allowed range (1 <= N <= 10)");
  // ...
}

and which can be called easily in client code

double a;
a = func<5>(...)

No need install a separate preprocessor, minimal bloat.

On the other hand, there are a few negative sides of C++ templates too. Here are some links (some of these are older and might not reflect the current status in C++):

2 Likes

If you look at Julia which has very successfully embraced both generic programing and metaprogramming, you’ll find that generic programming is the right solution 95% of the time, while metaprogramming is the solution 5% of the time. You can use metaprogramming as a solution to generic programming, but that requires making your users understand a lot of language internals. Generic code is good because it gives you extra expressive power for free.

2 Likes

Not everything, what can be done with fypp, should be done with fypp. :wink: Fypp was only meant as a temporary solution to bridge the time until proper generics/templates/whatever (and conditional compilation) will be implemented in Fortran itself. As this is envisioned for the Fortran 202Y standard, we are unfortunately talking about the next 15-20 years. :disappointed:

2 Likes

Ratfor was a Fortran 66 preprocessor created by Brian Kernighan of C fame in 1976 to facilitate structured programming in Fortran. Only the 1990 standard made it redundant. A similar preprocessor was Mortran. The existence of such tools was a sign that the language needed to be enhanced, not that modernization was unnecessary.

4 Likes

My experience with fypp is very limited, but isn’t it impossible to pass arbitrary user-defined derived types to an existing library (unless something like “poorman’s template” = an include method is used)?

Sure, fypp is just a dumb preprocessor, so it definitely does not make template/generics in Fortran superfluous.

The only thing it can offer over the current include based templating strategies is, that you can define multiple template instances within one module file and even loop over the types, which should be instantiated.

#:include "listtemplate.fypp"
#:set LISTS = [("list_int_t", "integer"), ("list_real_t", "real"), ("list_logical_t", "logical")]

module mylists
  implicit none

  private
  #:for NAME, _ in LISTS
    public :: ${NAME}$
  #:endfor

  #:for NAME, TYPE in LISTS
    @:declare_list(name=${NAME}$, itemtype=${TYPE}$)
  #:endfor

contains

  #:for NAME, TYPE in LISTS
    @:implement_list(name=${NAME}$, itemtype=${TYPE}$)
  #:endfor

end module mylists

Whether you consider it as a benefit or not, is of course a matter of taste.

1 Like

Two more preprocessors of that era include SFTRAN and S-Fortran.

In one of my favorite Fortran books, FORTRAN Tools for VAX/VMS and MS-DOS, the authors write a full Fortran preprocessor (in Fortran of course) just to add some nicer loops and if-clauses, and to bypass the six-character limit on identifiers using a slick hash table implementation. Only 4 years after they had gone through the trouble, the Fortran 90 standard raised the limit to 31 characters.

Thanks very much for the explanation. I’ve read more explanations on other sites (about fypp), and it does seem to provide a number of useful features. Even if it does not provide “true” generics or templates (which are often aware of the underlying language), I believe a powerful preprocessor is practically very useful for reducing the amount of codes :+1:

Indeed, more than 15 years ago (??!), I also wrote a preprocessor for Fortran (using very unreadable/cryptic Perl :sweat_smile:), so that I can use in-place updates (+= etc), converting . to % and any literals like 1.23 to 1.23d0, etc, etc (for my preference). I used it for one year or so, but because it was troublesome to pass such codes to other people, I went back to the “standard” Fortran style after all (with using cpp at most)… If I knew fypp at that time, I believe I definitely tried to further “customize” it for my preference (+ for fun).

Indeed, I guess another approach for introducing a “new syntax” is to use a source-to-source converter or translator or transpiler (something like the Typescript to Javascript relation…??)

Anyway, thanks very much for creating such a powerful and convenient utility :+1:

1 Like