Reputation: 4137
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
Reputation: 59999
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