Fortran

Guide To Learn

Writing the generic interface

At this point, we have our specific procedures implemented. Now we need to define the interface such that we can simply invoke average(temperature)average(wind_speed), and so on, rather than having to match the data types, like average_real(temperature)average_int(wind_speed), and so on.

To do so, we’ll open an interface block at the top of the module, before the contains statement, that will look like the following listing.

Listing 9.4 Defining a generic interface to specific procedures

module mod_average
  ...
  private                                 ❶
  public :: average                       ❷
  ...
  interface average                       ❸
    module procedure :: average_int       ❹
    module procedure :: average_real      ❹
    module procedure :: average_logical   ❹
  end interface average                   ❺
  ...
contains
  ...                                     ❻
end module mod_average

❶ Declares everything as private by default

❷ Makes only the generic function “average” publicly accessible

❸ Beginning of the interface

❹ Lists all the specific procedures to be accessible by the generic name

❺ End of interface

❻ Specific procedure definitions go here.

The interface specifies the name of our new generic procedure, average, and lists the specific procedures that are to be overridden. Recall the interface that we wrote in the previous chapter (section 8.2.6) to override the default type constructor with a custom function:

interface Person                                ❶
  module procedure :: person_constructor        ❷
end interface Person                            ❷

❶ Opens the interface block with the name of the type to override

❷ Specifies the procedure name that will override the default constructor

❸ Closes the interface block

We opened the interface block with the name that we’ll use to invoke the constructor (the name of the type, Person), and inside we specified the name of the function that will be called whenever we use the type name. Here, we’re using the exact same syntax rules, but for a slightly different purpose. We’re listing all the specific procedures that can be accessed with a generic name. Note that you can list all your specific procedures inside the interface block on the same line or a separate module procedure line:

interface average
  module procedure :: average_int, average_real, average_logical
end interface average

In essence, we’re using the same mechanism here as we did for a custom type constructor, this time for a new concept. You could use the same mechanism to define multiple type constructors that could take different sets of input arguments.

Which specific procedure will be invoked?

You may be wondering, If I define my generic interface to point to many different specific procedures, how does the compiler know which specific procedure to invoke? The simple answer is that it has to be obvious! All specific procedures that are overridden by a generic interface must be uniquely distinct. The Fortran standard defines clear rules on this matter. Two functions f1 and f2 can be distinguished in the following ways:

  • Number of positional argumentsf1(x) and f2(x, y) are distinct because they expect a different number of arguments.
  • Typef1(x) and f2(i), where x and i are declared real and integer, respectively, are distinct because their arguments have different types.
  • Rank (number of dimensions)f1(x(:)) and f2(x(:,:)) are distinct because they expect arrays of different ranks (1 and 2, respectively) as input arguments.
  • Number of optional arguments–Prior to Fortran 2018, the compiler wasn’t expected to distinguish between procedures that expect a different number of optional arguments, but now it is.

At this point, we can import this function in our main program and invoke it with integer, real, or logical data, as shown in the next listing.

Listing 9.5 Importing and applying the generic procedure in the main program

program weather_average
  ...
  use mod_average, only: average
  ...
  do n = 1, nm
    dataset = weather_data('data/processed/' &              ❶
                           // trim(cities(n)) // '.csv')
    temperature(n) = &                                      ❷
      average(denan(dataset % temperature))                 ❷
    humidity(n) = average(denan(dataset % humidity))        ❷
    wind_speed(n) = average(denan(dataset % wind_speed))    ❷
    clear_sky(n) = average(dataset % clear_sky)             ❸
  end do
  ...
end program weather_average

❶ Reads data from the file into a custom structure

❷ Removes NaNs from the array and averages it

❸ Averages the array

I’ll just touch on two items here for brevity. The first is the weather_data derived type that I use to read the data from the post-processed CSV files and store arrays into type components. We covered derived types in chapter 8, and this type is fairly straightforward. I encourage you to take a look at its implementation in src/mod_weather_ data.f 90. The second item is the denan function, which I use to remove any NaN (not a number) values from the arrays before passing them to the average function. (See also section 7.2.4 and src/mod_arrays.f 90.)

What’s a NaN?

NaN is a special value for a real number that can’t be represented otherwise. Try doing something naughty like dividing by zero–you’ll get a NaN. The square root of a negative real number? NaN!

NaNs were introduced to widespread use by the IEEE 754 standard for floating-point numbers in 1985, along with a few other special values, such as infinities of either sign.

Figure 9.2 illustrates what goes on under the hood when you pass, for example, an array to a generic function that’s just an interface to a few different specific functions.

Figure 9.2 From the real input array to the result of a generic function

We start with a real array of air temperature values. We pass this array to a generic average function. Thanks to static typing, the compiler knows the type of the input array (real), and it has a list of specific procedures that the interface average overrides:

interface average
  module procedure :: average_int
  module procedure :: average_logical
  module procedure :: average_real
end interface average

The compiler then goes in order and checks whether the type, rank, and number of arguments in the specific procedure definition match the input arguments passed to the generic procedure. Integer? No. Logical? No. Real? Match! The specific function average_real is thus matched at compile time with this particular invocation of the generic average(temp).

Exercise 1: Specific average function for a derived type

We just went through implementing the specific average function for three built-in types: realinteger, and logical. This will make for quite a flexible generic function. However, more complex and real-world apps will likely require encapsulating the arrays in custom derived types, such as the type Field that we’re now using in the tsunami simulator. Consider this type definition:

type :: Field
  real, allocatable :: data(:)
end type Field

Your goal for this exercise is to implement the specific average function that will accept a type(Field) instance as the input argument and return the average value of its data(:) component. Can you write this function such that it works regardless of whether the type of Field % data(:) is realinteger, or logical?

You can find the solution to this exercise in the “Answer key” section near the end of this chapter.

Writing the generic interface

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top