Search code examples
oopfortranpolymorphismintel-fortran

Extending an object and overriding a procedure without being deferred in Fortran


I have a code with lots of different features and methods. Some methods are all for the same feature, i.e. only one among a selection can be selected.

Furthermore, depending on a feature I might need to do modify subroutines elsewhere. So in a loop in routine inject I might have a small if statement asking if I have used feature A, then do a few extra operations. This is very frustating since different features seems to be connected with others routines very arbirarirly, and can be difficult to maintain.

I have decided to do following to avoid this:

I define an object t_inject with the purpose to execture routine inject. I rewrite my routine inject such that it contains only the code that is common for all different scenarios.

 type t_inject 


 contains 

 procedure,nopass :: inject => inject_default
 end type 

Now I have another object to handle my feature A in case it is selected.

 type,extends(t_inject) :: t_inject_a


 contains 

 procedure, nopass :: inject => inject_a
 end type 

My subroutines inject_a and inject have same interface. E.g.

 subroutine inject_a( part ) 
  type(t_part) , intent(inout) :: part % an external data type 
 call inject(part) 
 ! do the extra bit of stuff you need to do

 end subroutine 

 subroutine inject( part) 
   type(t_part) , intent(inout) :: part % an external data type 

  ! carry out the default stuff 
 end subroutine 

Now in my main program

class(t_inject) :: inj 

allocate(inj :: t_inject_a) 

call inj% inject ( part) 

Is that the way you would do it and is it valid?
I initially thought of doing an abstract declared type with a deferred inject procedure where I then could extent. But for a very trivial problem I might not need that - I am also wondering whether my call call inj% inject(part) is sufficient for the compiler to know to where to go. Sometimes I see codes which need the class is condition before making the call.


Solution

  • I think three points should be modified:

    • The type-bound procedures need to refer to actual procedure names (via =>). So, I have changed the name of a module procedure inject() to inject_default(). (But please see test2.f90 also).
    • We need to attach allocatable to a class variable (e.g., inj2) to allocate it with a concrete type (e.g., t_inject_a).
    • In the allocate statement, the name of a concrete type should appear before ::, such that allocate( t_inject_a :: inj2 ).

    The modified code may look like this:

    !! test.f90
    module test_mod
        implicit none
    
        type t_inject 
        contains 
            procedure, nopass :: inject => inject_default
        endtype
    
        type, extends(t_inject) :: t_inject_a
        contains 
            procedure, nopass :: inject => inject_a
        endtype
    
        type t_part    !! some other type
            integer :: x = 100, y = 200
        endtype
    
    contains
        subroutine inject_default( part )
            type(t_part), intent(inout) :: part
    
            print *, "x = ", part % x
        endsubroutine
    
        subroutine inject_a( part ) 
            type(t_part), intent(inout) :: part
    
            call inject_default( part )
            print *, "y = ", part % y
        endsubroutine 
    end
    
    program main
        use test_mod
        implicit none
        class( t_inject ), allocatable :: inj1, inj2
        type( t_part ) :: part
    
        !! Polymorphic allocation with concrete types.
        allocate( t_inject   :: inj1 )
        allocate( t_inject_a :: inj2 )
    
        print *, "inj1:"
        call inj1 % inject( part )
    
        print *, "inj2:"
        call inj2 % inject( part )
    end
    

    "gfortran-8 test.90 && ./a.out" gives

     inj1:
     x =          100
     inj2:
     x =          100
     y =          200
    

    We can also use a module procedure inject() (rather than inject_default()) by using procedure, nopass :: inject, for example:

    !! test2.f90
    module test_mod
        implicit none
    
        type t_inject 
        contains 
            procedure, nopass :: inject
            ! procedure, nopass :: inject => inject  !! this also works
        endtype
    
        type, extends(t_inject) :: t_inject_a
        contains 
            procedure, nopass :: inject => inject_a
        endtype
    
        type t_part    !! some other type
            integer :: x = 100, y = 200
        endtype
    
    contains
        subroutine inject( part )
            type(t_part), intent(inout) :: part
    
            print *, "x = ", part % x
        endsubroutine
    
        subroutine inject_a( part ) 
            type(t_part), intent(inout) :: part
    
            call inject( part )
            print *, "y = ", part % y
        endsubroutine 
    end
    
    !! The remaining part (and the result) is the same...
    

    In addition, one can also separate actual procedures like inject() in a different file and use them to define new types like t_inject (see mylib.f90 and test3.f90 below). This might be useful to reuse routines in some library file.

    !! mylib.f90
    module mylib
        implicit none
    
        type t_part    !! some other type
            integer :: x = 100, y = 200
        endtype
    
    contains
    
        subroutine inject( part )
            type(t_part), intent(inout) :: part
    
            print *, "x = ", part % x
        end
        subroutine inject_a( part ) 
            type(t_part), intent(inout) :: part
    
            call inject( part )
            print *, "y = ", part % y
        end
    end
    
    !! test3.f90
    module test_mod
        use mylib
        implicit none
    
        type t_inject 
        contains 
            procedure, nopass :: inject
        endtype
    
        type, extends(t_inject) :: t_inject_a
        contains 
            procedure, nopass :: inject => inject_a
        endtype
    end
    
    !! The main program is the same as test.f90.
    !! compile: gfortran-8 mylib.f90 test3.f90