b-fg
b-fg

Reputation: 4137

Derived types and extended types construction

I am quite new to Fortran OOP and I am facing some issues at initializing parent and derived types. I have one module containing the parent type object (sorry for the overuse of the word..) and its derived type circle, which has the extra radius field.

The way I need to initialize the object type now requires to use a dummy argument for radius, which I would like to avoid. So at the moment what I have works, but I would like to know a fancier way to do this since it does not seem very practical if I need to have further derived types from object in the future.

I guess that using object as an abstract parent type would help in this sense? Or using generic procedures, but I don't really know how to do that.

The code is below.

module objectMod
  implicit none

  type :: object
     real,allocatable   :: x(:,:)           ! position vector (points) --- (M,{i,j})
     real               :: centre(2)        ! centre of the object
     integer            :: M=50             ! number of Lagrangian points of the object (default)
     real               :: eps=0.1          ! kernel of the surface (default)
   contains
     procedure :: init=>init_object
  end type object

contains

  subroutine init_object(a,centre,radius,M,eps)
    implicit none

    class(object),intent(inout)   :: a
    real,intent(in)               :: centre(2)
    integer,intent(in),optional   :: M
    real,intent(in),optional      :: eps
    real,intent(in),optional      :: radius ! ignored for object

    if(present(M)) a%M = M
    if(.not.allocated(a%x)) allocate(a%x(a%M,2))
    a%centre = centre
    if(present(eps)) a%eps = eps
  end subroutine init_object
end module objectMod

module geomMod
  use objectMod
  implicit none
  real,parameter :: PI = 3.14159265

  type,extends(object) :: circle
     real              :: radius    ! radius
   contains
     procedure :: init=>init_circle
  end type circle

contains

  subroutine init_circle(a,centre,radius,M,eps)
    implicit none
    class(circle),intent(inout) :: a
    real,intent(in)             :: centre(2)
    real,intent(in),optional    :: radius
    integer,intent(in),optional :: M
    real,intent(in),optional    :: eps

    integer :: i
    real    :: dtheta

    ! object type attributes initialization
    a%centre = centre
    if(present(M)) a%M = M
    if(.not.allocated(a%x)) allocate(a%x(a%M,2))
    if(present(eps)) a%eps = eps
    ! circle type attributes initialization
    a%radius = radius

    dtheta = 2.*PI/real(a%M-1)
    do i = 1,a%M
      a%x(i,1) = a%radius*cos(dtheta*(i-1))+a%centre(1)
      a%x(i,2) = a%radius*sin(dtheta*(i-1))+a%centre(2)
    end do
  end subroutine init_circle
end module geomMod

Upvotes: 2

Views: 718

Answers (1)

The mistake you are doing is to create constructors as type-bound procedures. The requirement, that the argument list must conform is against you.

Type-bound procedures are simply not the right tool for constructors (or initializers).

In Fortran we use functions returning an instance of the object to initialize it.

function init_object(centre,M,eps) result(a)
  type(object) :: a
  real,intent(in)               :: centre(2)
  integer,intent(in),optional   :: M
  real,intent(in),optional      :: eps
  !NO radius
end function init_object


interface object
  procedure init_object
end interface



....



obj = object(my_centre, my_m, my_eps)

This is also how the default structure constructors are called.


I see you took the example from some online tutorial. I find the example to be a poor design. If you disagree, you will have to live with these problems it brings.


So, basically, you want an initializer method instead of a constructor (see Why use an initialization method instead of a constructor? for some discussion. Objective-C uses something like that https://www.binpress.com/tutorial/objectivec-lesson-11-object-initialization/76 but more recent Swift uses constructors instead.).

You could employ generics in this way:

    module types

        type t1
          integer :: i
        contains
          generic :: init => init_t1
          procedure, private :: init_t1
        end type

        type, extends(t1) :: t2
          integer :: j
        contains
          generic :: init => init_t2
          procedure, private :: init_t2
        end type

        type, extends(t2) :: t3
          integer :: k
        contains
          generic :: init => init_t3
          procedure, private :: init_t3
        end type

    contains


      subroutine init_t1(self, i)
        class(t1) :: self
      end subroutine

      subroutine init_t2(self, i, j)
        class(t2) :: self
      end subroutine

      subroutine init_t3(self, i, j, k)
        class(t3) :: self
      end subroutine

    end module

In my opinion this is plain ugly and un-Fortranic, but it does what you want.

You will have to make special care that the user will not call a wrong version inadvertently by overloading it by something that will issue an error message.

My advice is still to rather follow common patterns rather than invent your own ways which will confuse those who are used to the mainstream.

Upvotes: 1

Related Questions