Good OOP design to avoid copy/paste in Fortran

376 views Asked by At

Given the minimal working example below, I would like to modify it to avoid copy/pasting the calls of

call func_some_calc1(par)
call func_some_calc2(par)

in both main_func_problem1 and main_func_problem2. Ideally I want to have one function main_func that is behaving differently for input parameters of type t_parameters_problem1 and t_parameters_problem2. I could declare its par parameter of base type class(t_parameters_base), but then having a switch inside that function depending on the actual argument type (using select type) is architecturally not good.

To solve this I tried to create a procedure in type t_parameters_base, which is calling those routines, to implement something like this (C++ syntax):

class t_parameters_base {
  virtual void main_func() {
    func_some_calc1(this)
    func_some_calc2(this)
  }
}
class t_parameters_problem1: public t_parameters_base {
  virtual void main_func() {
    t_parameters_base::main_func();
    func_some_calc3_problem1(this);
  }
}

But the problem is that those routines are using an input parameter of this type, which leads to circular dependency. How is it possible to solve this problem?

Update: Note that I really want to keep the implementation of func_some_calc1 and func_some_calc2 in different files (modulus/classes) as they implement very different logic using some private functions from their classes.

module parameters_base
  type, public :: t_parameters_base
    integer :: n
  end type t_parameters_base
end module parameters_base

module parameters_problem1
  use parameters_base
  implicit none

  type, extends(t_parameters_base), public :: t_parameters_problem1
    integer :: p1
  end type t_parameters_problem1
end module parameters_problem1

module parameters_problem2
  use parameters_base
  implicit none

  type, extends(t_parameters_base), public :: t_parameters_problem2
    integer :: p2
  end type t_parameters_problem2
end module parameters_problem2

module some_calc1
  use parameters_base
  implicit none
contains
  subroutine func_some_calc1(par)
    class(t_parameters_base) :: par
  end subroutine func_some_calc1
end module some_calc1

module some_calc2
  use parameters_base
  implicit none
contains
  subroutine func_some_calc2(par)
    class(t_parameters_base) :: par
  end subroutine func_some_calc2
end module some_calc2

module some_calc3_problem1
  use parameters_problem1
  implicit none
contains
  subroutine func_some_calc3_problem1(par)
    type(t_parameters_problem1) :: par
    print*, par%p1
  end subroutine func_some_calc3_problem1
end module some_calc3_problem1

module some_calc3_problem2
  use parameters_problem2
  implicit none
contains
  subroutine func_some_calc3_problem2(par)
    type(t_parameters_problem2) :: par
    print*, par%p2
  end subroutine func_some_calc3_problem2
end module some_calc3_problem2

module main_problem1
  use parameters_problem1
  use some_calc1
  use some_calc2
  use some_calc3_problem1
  implicit none
contains
  subroutine main_func_problem1(par)
    type(t_parameters_problem1) :: par

    call func_some_calc1(par)
    call func_some_calc2(par)
    call func_some_calc3_problem1(par)
  end subroutine main_func_problem1
end module main_problem1

module main_problem2
  use parameters_problem2
  use some_calc1
  use some_calc2
  use some_calc3_problem2
  implicit none
contains
  subroutine main_func_problem2(par)
    type(t_parameters_problem2) :: par

    call func_some_calc1(par)
    call func_some_calc2(par)
    call func_some_calc3_problem2(par)
  end subroutine main_func_problem2
end module main_problem2

program module_test
  use parameters_problem1
  use parameters_problem2
  use main_problem1
  use main_problem2

  implicit none

  type(t_parameters_problem1) :: par1
  type(t_parameters_problem2) :: par2

  par1%p1 = 1
  par2%p2 = 2

  call main_func_problem1(par1)
  call main_func_problem2(par2)
end program module_test
1

There are 1 answers

3
Bálint Aradi On

I think, what you are aiming for are polymorphic types with type bound procedures. Below you find a working example. To keep it simple, I do not have added any data to the types, but of course, that can be easily done. The routine invokeCalc12 is only defined in the base type, but can be invoked from the derived types, and as in Fortran all methods are virtual, it will call the right methods.

module calc_base
  implicit none

  type, abstract :: CalcBase
  contains
    procedure(calcInterface), deferred :: calc1
    procedure(calcInterface), deferred :: calc2
    procedure :: invokeCalc12
  end type CalcBase

  interface
    subroutine calcInterface(self, ii)
      import :: CalcBase
      class(CalcBase), intent(inout) :: self
      integer, intent(in) :: ii
    end subroutine calcInterface
  end interface

contains

  subroutine invokeCalc12(self, ii)
    class(CalcBase), intent(inout) :: self
    integer, intent(in) :: ii
    call self%calc1(ii)
    call self%calc2(ii)
  end subroutine invokeCalc12

end module calc_base


module some_calc
  use calc_base
  implicit none

  type, extends(CalcBase) :: SomeCalc
  contains
    procedure :: calc1
    procedure :: calc2
    procedure :: calc3
  end type SomeCalc

contains

  subroutine calc1(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    print *, "SomeCalc1:calc1", ii
  end subroutine calc1

  subroutine calc2(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    print *, "SomeCalc1:calc2", ii
  end subroutine calc2

  subroutine calc3(self, ii)
    class(SomeCalc), intent(inout) :: self
    integer, intent(in) :: ii
    call self%%invokeCalc12(ii)
  end subroutine calc3

end module some_calc


program test
  use some_calc
  implicit none

  type(SomeCalc) :: mySimulation

  call mySimulation%calc3(42)

end program test

Note: I've seen that similar question has been posted at comp.lang.fortran, but at the moment I did not find a working example there, therefore the posting here.