Fortran

Guide To Learn

Writing a custom type constructor

Fortran provides a simple mechanism to override the default type constructor with a user-defined function or subroutine. This gives you the power to do any prep work with type components such as allocation, initialization, input validation, and others.

Let’s first define the function that returns an instance of the type whose constructor we’re overriding. For example, we could write a custom greeting message that depends on the occupation of the person. Let’s say Bob is an engineer, and Davey is a pirate. Their respective greetings could sound something like

Bob says Hi, there.
 Davey says Ahoy, matey!

We’ll write our custom constructor to accept nameage, and occupation as mandatory arguments, and to test for the value of occupation. The complete code for this custom constructor is shown in the following listing.

Listing 8.4 Custom constructor for a derived type

pure type(Person) function person_constructor( &     ❶
  name, age, occupation) result(res)                 ❶
  character(len=*), intent(in) :: name               ❷
  integer, intent(in) :: age                         ❷
  character(len=*), intent(in) :: occupation         ❷
  res % name = name                                  ❸
  res % age = age                                    ❸
  res % occupation = occupation                      ❸
  if (occupation == 'Pirate') then                   ❹
    res % greeting_message = 'Ahoy, matey!'          ❹
  else                                               ❹
    res % greeting_message = 'Hi, there.'            ❹
  end if                                             ❹
end function person_constructor

❶ The function result must have the Person type.

❷ Input arguments

❸ Setting type components to instance values

❹ Depending on input, sets custom value for a component

First, the function that will override the default type constructor must result in that same type, in this case Person. It’s not required (and sometimes not possible) for the constructor function to be pure; however, here it’s a reasonable choice, since we don’t cause any side effects from within the function. Second, any components that we want to set at the initial time, we can pass as input arguments to this function. In this case, we pass and explicitly assign the nameage, and occupation components. In general, you’re not required to initialize any or all components, in which case they’ll be left undefined, and you’ll need to be careful not to reference them in expressions before first defining them. Also note that here we declare the input character strings as character(len=*), which instructs the compiler to accept character strings of any length as input. Finally, we assign a custom greeting message to the type instance depending on the value of occupation.

Now that we have a function that will override the default type constructor, we need to tell the compiler to invoke the person_constructor function whenever we use the type instance creation syntax, in this case Person(). This is done by specifying the interface to the derived type of the same name:

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

❶ Creates an interface to the Person type

❷ Points to the procedure to be used

❸ Closes the interface block

We use the module procedure statement inside the interface block to indicate which procedure to call to create an instance of the derived type Person. This interface block must be placed in the declarative section of the module after the definition of the derived type, but before the contains statement, as shown in the following listing.

Listing 8.5 Order of type definition, its interface, and the custom constructor function

module mod_person
 
  type :: Person                                    ❶
    ...                                             ❶
  end type Person                                   ❶
 
  interface Person                                  ❷
    module procedure :: person_constructor          ❷
  end interface Person                              ❷
 
contains
 
  pure type(Person) function person_constructor()   ❸
    ...                                             ❸
  end function person_constructor                   ❸
 
end module mod_person

❶ First define the type.

❷ Then specify the interface.

❸ Finally, define the custom constructor function.

To successfully override the type constructor, you need to make sure of the following:

  • The name of the interface matches the name of the derived type.
  • The interface points to a valid function defined in the module.
  • The function result is of the same type as the derived type.

To see this in action, from the main program, we’d do something like this:

type(Person) :: some_person
some_person = Person('Bob', 32, 'Engineer')
print *, trim(some_person % name) // &
  ' says: ' // trim(some_person % greeting_message)
some_person = Person('Davey', 44, 'Pirate')
print *, trim(some_person % name) // &
  ' says: ' // trim(some_person % greeting_message)

Note that to be able to assign to the greeting_message component, it needs to have been declared in the derived type definition, which I’ve omitted here for brevity.

Resolving the constructor interface

When a custom constructor function overrides the default one, the compiler will first try to use the custom function and will check that all the actual arguments (those passed in the function call) match all the dummy arguments (those defined in the function definition) by type and kind. If the arguments are incompatible, the compiler will then attempt to revert to the default type constructor. If the arguments are then incompatible with any of the type components, the compiler will abort with an error message.

This makes Fortran a strongly typed, and a bit more verbose, language, but it also makes it more reliable and robust once compiled and running.

Let’s apply the custom constructor technique to our tsunami Field derived type. Inside the constructor function, we’ll have the chance to do all the necessary prep work, such as calculating start and end indices of the data array, allocating the array in memory, and initializing its values.

Writing a custom type constructor

Leave a Reply

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

Scroll to top