1

I am learning oop in Fortran. And I wonder about the interest of overloading type bound procedure when the type of an argument is not known at compile time.

Let me explain, step by step my problem (These programs are only examples to increase my skill in fortran)

First program : overload of a type bound procedure

module my_mod

  implicit none
  
  type :: my_type_t
    character(len=128) :: my_text
  contains
    procedure :: integer_print, real_print
    generic :: my_print => integer_print, real_print
  end type my_type_t
  
  contains
  
  subroutine integer_print(this, my_int)
    class(my_type_t), intent(in) :: this
    integer, intent(in) :: my_int
    write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int
  end subroutine integer_print
  
  subroutine real_print(this, my_real)
    class(my_type_t), intent(in) :: this
    real, intent(in) :: my_real
    write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real
  end subroutine real_print

end module my_mod

program my_pgm

  use my_mod
  implicit none
  
  type(my_type_t) :: my_var
  my_var%my_text = "Hello"
  
  call my_var%my_print(10)
  call my_var%my_print(9.9)

end program my_pgm

It gave the expected result. Either integer_print or real_print is used, depending of the type of my_print argument (integer or real).

Second program (or step, I should write) : an abstract type is used and there are two derived types : my_int_t and my_real_t. It is very similar to the first example. But, two types are used, one for integer and the other for real.

module my_mod

  implicit none
  
  type, abstract :: my_abstract_t
  end type  my_abstract_t
  
  type, extends(my_abstract_t) :: my_int_t
    integer :: an_integer
  end type my_int_t
  
  type, extends(my_abstract_t) :: my_real_t
    real :: a_real
  end type my_real_t
  
  type :: my_type_t
    character(len=128) :: my_text
  contains
    procedure :: integer_print, real_print
    generic :: my_print => integer_print, real_print
  end type my_type_t
  
  contains
  
  subroutine integer_print(this, my_int)
    class(my_type_t), intent(in) :: this
    type(my_int_t), intent(in) :: my_int
    write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int%an_integer
  end subroutine integer_print
  
  subroutine real_print(this, my_real)
    class(my_type_t), intent(in) :: this
    type(my_real_t), intent(in) :: my_real
    write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real%a_real
  end subroutine real_print

end module my_mod

program my_pgm

  use my_mod
  implicit none
  
  type(my_type_t) :: my_var
  type(my_int_t) :: my_int
  type(my_real_t) :: my_real
  
  my_var%my_text = "Hello"
  
  my_int%an_integer = 10
  my_real%a_real = 9.9
  
  call my_var%my_print(my_int)
  call my_var%my_print(my_real)

end program my_pgm

The type of my_int and my_real is known at compile type, so the output is correct. And the same call my_var%my_print(...) is used whatever the variable type (my_int_t or my_real_t), as in the first example.

But, now this is my problem.

program my_pgm

  use my_mod
  implicit none
  
  type(my_type_t) :: my_var
  class(my_abstract_t), allocatable :: my_number
  
  allocate(my_int_t::my_number)
  ! or allocate(my_real_t::my_number)
  
  my_var%my_text = "Hello"

  select type (my_number)
  type is (my_int_t)
      my_number%an_integer = 10
  type is (my_real_t)
      my_number%a_real = 9.9
  end select
  
  select type (my_number)
  type is (my_int_t)
      call my_var%my_print(my_number)
  type is (my_real_t)
      call my_var%my_print(my_number)
  end select

end program my_pgm

The type of my_number is not known at compile time. So, I must use a part of code that I found really redundant :

  select type (my_number)
  type is (my_int_t)
      call my_var%my_print(my_number)
  type is (my_real_t)
      call my_var%my_print(my_number)
  end select

I would have preferred to write only one line : call my_var%my_print(...) as in the first and second examples. Should I conclude that overloading of procedure have not interest in the third example and it is better to use integer_print and real_print directly in the select type block ? Or Is there something I haven't understood? ?

Edit 1

Following the comments of Francescalus, if I have well understood, I could not avoid the select type block. So, I modify the program in the following way.

module my_mod

  implicit none
  
  type, abstract :: my_abstract_t
  end type  my_abstract_t
  
  type, extends(my_abstract_t) :: my_int_t
    integer :: an_integer
  end type my_int_t
  
  type, extends(my_abstract_t) :: my_real_t
    real :: a_real
  end type my_real_t
  
  type :: my_type_t
    character(len=128) :: my_text
  contains
    procedure, private :: real_print, integer_print
    procedure, public :: my_print
  end type my_type_t
  
  contains
  
  subroutine integer_print(this, my_int)
    class(my_type_t), intent(in) :: this
    type(my_int_t), intent(in) :: my_int
    write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int%an_integer
  end subroutine integer_print
  
  subroutine real_print(this, my_real)
    class(my_type_t), intent(in) :: this
    type(my_real_t), intent(in) :: my_real
    write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real%a_real
  end subroutine real_print
  
  subroutine my_print(this,my_number)
    class(my_type_t), intent(in) :: this
    class(my_abstract_t), intent(in) :: my_number
    
    select type (my_number)
    type is (my_int_t)
        call this%integer_print(my_number)
    type is (my_real_t)
        call this%real_print(my_number)
    end select
    
  end subroutine my_print
    

end module my_mod

program my_pgm

  use my_mod
  implicit none
  
  type(my_type_t) :: my_var
  class(my_abstract_t), allocatable :: my_number1, my_number2
  
  my_number1 = my_int_t(an_integer = 10)
  my_number2 = my_real_t(a_real = 9.9)
  
  my_var%my_text = "Hello"

  call my_var%my_print(my_number1)
  call my_var%my_print(my_number2)
  
end program my_pgm

Any comments would be appreciated.

9
  • 2
    You may find another question relevant. Commented Apr 8 at 17:03
  • 1
    Entirely unrelated to the main part of your question, but a more general suggestion: your ALLOCATE followed by a SELECT TYPE can be replaced by intrinsic assignment like my_number = my_int_t(10) or my_number = my_real_t(9.9). Commented Apr 8 at 17:07
  • 1
    Nothing significant in allowing multiple dispatch. Commented Apr 8 at 19:18
  • 1
    Since you specificalky have only two arguments, you could use the double dispatch, also called the visitor, pattern. It is a workaround and nit completely general and not completely extensible. See the link in my answer or, for example, eli.thegreenplace.net/2016/… (this is for C++). Commented Apr 9 at 5:57
  • 1
    It is well known that multiple dispatch can be enjoyed in Julia and Julia users and evangelists often show that as a great advantage. There are ways, how to do that in Python with third-party modules medium.com/@AlexanderObregon/… Commented Apr 9 at 6:05

1 Answer 1

1

One can use double dispatch, also called the visitor pattern.

This is a sample implementation for your code. It has the obvious limitation that the my_type_t type must already be known. However, the use of select_type is avoided and further types extending my_abstract_type can be added freely.

module my_mod

  implicit none
  

  type :: my_type_t
    character(len=128) :: my_text
  contains
    procedure, public :: my_print
  end type my_type_t

  type, abstract :: my_abstract_t
  contains
    procedure(get_printed_from_my_type_t_interface), deferred :: get_printed_from_my_type_t
  end type  my_abstract_t

  abstract interface
    subroutine get_printed_from_my_type_t_interface(this, printer)
      import
      class(my_abstract_t), intent(in) :: this
      type(my_type_t), intent(in) :: printer
    end subroutine
  end interface
  
  
  type, extends(my_abstract_t) :: my_int_t
    integer :: an_integer
  contains
    procedure :: get_printed_from_my_type_t => integer_print   
  end type my_int_t
  
  type, extends(my_abstract_t) :: my_real_t
    real :: a_real
  contains
    procedure :: get_printed_from_my_type_t => real_print
  end type my_real_t
  

  contains
  
  subroutine integer_print(this, printer)
    class(my_int_t), intent(in) :: this
    type(my_type_t), intent(in) :: printer
    write (*,"(a,a,i0)") trim(printer%my_text),' integer ', this%an_integer
  end subroutine integer_print
  
  subroutine real_print(this, printer)
    class(my_real_t), intent(in) :: this
    type(my_type_t), intent(in) :: printer
    write (*,"(a,a,f0.3)") trim(printer%my_text),' real ', this%a_real
  end subroutine real_print
  
  subroutine my_print(this,my_number)
    class(my_type_t), intent(in) :: this
    class(my_abstract_t), intent(in) :: my_number
    
    call my_number%get_printed_from_my_type_t(this)
    
  end subroutine my_print
    

end module my_mod

program my_pgm

  use my_mod
  implicit none
  
  type(my_type_t) :: my_var
  class(my_abstract_t), allocatable :: my_number1, my_number2
  
  my_number1 = my_int_t(an_integer = 10)
  my_number2 = my_real_t(a_real = 9.9)
  
  my_var%my_text = "Hello"

  call my_var%my_print(my_number1)
  call my_var%my_print(my_number2)
  
end program my_pgm

The main point is doing another dispatch when having type(my_type_t) already available and shifting the implementations of the printing to the individual types with the numeric data deriving from my_abstract_t which have to know the structure of my_type_t - that is a drawback.

Sign up to request clarification or add additional context in comments.

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.