Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28867 Discussions

Extensibility and procedure pointer components

avinashs
New Contributor I
1,294 Views

I need conceptual help understanding the PASS attribute for procedure pointer components in conjunction with EXTENDS.

1. I have defined a base class (BC) that has a procedure pointer (AF) to provide access to that class with an argument of class BC defined as the first passed argument. Such an access function, for example, allows customizable printing of results by the user.
2. The base class is extended to a derived class (DC) where some procedures and types of the base class are overridden.
3. The DC inherits the AF of the BC as expected. However, the passed argument must be of type BC whereas I would like to override AF with an implementation within the DC, having the same name, that would be passed a derived-class argument. However, this results in an error.
4. It seems that the only way around this situation is to retain all relevant information within the base class definition and define a compatible procedure pointer which takes a variable of type base class as the first argument. But the user would then have to know that there is a base class whereas I would like to work only with the derived class.

Is my understanding correct? Is there a way around this? The key objective here is to be able to customize printing or setting of parameters from within the base or derived class with a user-defined function.

I constructed an example (see attached code) to illustrate the situation.

0 Kudos
8 Replies
FortranFan
Honored Contributor III
1,283 Views

To the best of my understanding, in current Fortran standard the semantics of type extension when it comes to procedures i.e., the "methods which operate on the data of a class" (per OO terminology) is limited to what the standard calls "Type-bound procedure overriding".  That is, it does not extend to type components which are procedure pointers even though the passed-object dummy argument is required to be polymorphic (an oversight in the standard in my opinion).

Unless you have a lot of time that allows to you to deviate from KISS principle, you can review what you ae trying to do - "key objective here is to be able to customize printing or setting of parameters from within the base or derived class with a user-defined function." - and try something simple such as optional dummy arguments of PROCEDURE type for your user-defined function in "methods" of your class.

 

0 Kudos
FortranFan
Honored Contributor III
1,248 Views
FortranFan wrote:

.. try something simple such as optional dummy arguments of PROCEDURE type for your user-defined function in "methods" of your class.

 

So with the situation presented in the original post as an example, OP and any other reader interested in similar derived type design toward their 'classes' can consider something like the following.  Keep in mind one can credibly argue OO-style is more 'art' than science and what measure of effectiveness or elegance - a la beauty - lies in the eyes of the beholder!

module base_class

   type :: base

      integer(kind = 4) :: n
      real(kind =  :: x

   contains

      procedure, pass(handle) :: access_b
      procedure, pass(handle) :: power => base_power
      generic :: access => access_b

   end type base

contains

   function base_power( handle ) result(r)
      class(base), intent(in) :: handle
      r = handle%x**handle%n ! use intrinsic exponentiation operator
   end function base_power

   subroutine access_b( handle )
      class(base), intent(in) :: handle
      ! elided are the instructions
      print *, 'n = ', handle%n
   end subroutine access_b

end module base_class

module derived_class

   use base_class

   type, extends(base) :: derived

   contains

      procedure, pass(handle) :: access_d
      procedure, pass(handle) :: power => derived_power
      generic :: access => access_d

   end type derived

   abstract interface
      subroutine IPrintUser_d(handle)
         import :: derived
         type(derived), intent(in) :: handle
      end subroutine
   end interface

contains

   function derived_power(handle) result(r)
      class(derived), intent(in) :: handle
      r = exp(handle%n*log(handle%x)) ! use property of power function: y^x = exp(x ln(x))
   end function derived_power

   subroutine access_d(handle, PrintUser)
      class(derived), intent(in) :: handle
      procedure(IPrintUser_d), pointer :: PrintUser
      if ( associated(PrintUser) ) then
         call PrintUser( handle )
      else
         ! Elided are default 'class' instructions
      end if
   end subroutine access_d

end module derived_class

program main

   use derived_class

   type(derived) :: a
   real(kind =  :: y
   procedure(IPrintUser_d), pointer :: pPrinter

   a%n = 5
   a%x = 2
   call a%access()  ! access the type with the base/default approach

   pPrinter => my_af
   call a%access( pPrinter ) ! access it with a user-defined function

   y = a%power()
   print *, 'y = ', y
   read *

contains

   subroutine my_af(T)
      type(derived), intent(in) :: T
      ! defines customizable print function that will be called internally
      print *, 'n = ', T%n
      print *, 'x = ', T%x
   end subroutine my_af

end program main

 

C:\Temp>ifort /standard-semantics /warn:all /stand:f18 p.f90
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.2.254 Build 20200623
Copyright (C) 1985-2020 Intel Corporation.  All rights reserved.

p.f90(18): warning #6717: This name has not been given an explicit type.   [R]
   function base_power( handle ) result(r)
----------------------------------------^
p.f90(54): warning #6717: This name has not been given an explicit type.   [R]
   function derived_power(handle) result(r)
-----------------------------------------^
Microsoft (R) Incremental Linker Version 14.26.28806.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:p.exe
-subsystem:console
p.obj

C:\Temp>p.exe
 n =  5
 n =  5
 x =  2.00000000000000
 y =  32.0000000000000


C:\Temp>

 

0 Kudos
avinashs
New Contributor I
1,236 Views

Thank-you @FortranFan - that is an elegant solution and clarifies a few things for me. With your earlier comment and that of @IanH, here is what I concluded:

1. The extensibility applies to contained procedures through the passed argument referencing the class (handle).

2. Although handle can be passed to a procedure pointer function that is a data member (variable) of the class, it cannot be extended. This was the major reason for my confusion - since I could pass handle to the procedure access, I assumed it could be extended.

3. From your example, you can use generic across parent and extended types (child classes) and generic can take one argument.

4. Again from your example, I cannot make PrintUser a member of the class. It must be an abstract class that is passed to a member function and defined as needed in the main program. So unlike my original example, you cannot call access_b or access_d from within either class.

5. What I was trying to do in my example ... Let us suppose I have a class with many user-defined types (UDFs) and I define several child classes. And let us suppose there is a procedure defined once only in the base class that executes many iterations each of which change the UDFs (original or derived). Then, my access function was being called at the end of each iteration (say) so that the user could print certain variables depending on the application. In my base class this is not a problem. But in my extended class, I cannot do that unless I package all the UDFs in the base class and call the access function defined in the base class.

6. One solution is to define a different access function for each derived class with a different name for each function. However, these different names cannot be unified with the GENERIC clause. See attached example.

Thank-you @FortranFan - that is an elegant solution and clarifies a few things for me. With your earlier comment and that of @IanH, here is what I concluded:

1. The extensibility applies to contained procedures through the passed argument referencing the class (handle).

2. Although handle can be passed to a procedure pointer function that is a data member (variable) of the class, it cannot be extended. This was the major reason for my confusion.

3. From your example, you can use generic across parent and extended types (child classes) and generic can take one argument.

4. Again from your example, I cannot make PrintUser a member of the class. It must be an abstract class that is passed to a member function and defined as needed in the main program. So unlike my original example,  you cannot call access_b or access_d from within either class.

5. What I was trying to do in my example ... Let us suppose I have a class with many user-defined types (UDFs) and I define several child classes. And let us suppose there is a procedure defined only in the base class that executes many iterations each of which change the UDFs (original or derived). Then, my access function was being called at the end of each iteration (say) so that the user could print certain variables depending on the application. In my base class this is not a problem. But in my extended class, I cannot do that unless I package all the UDFs in the base class and call the access function defined in the base class.

6. One solution is to define a different access function for each derived class with a different name for each function. However, these different names cannot be unified with the GENERIC clause. See attached example. The inconvenience here is that the user has to define different functions for the base and each derived class.

0 Kudos
IanH
Honored Contributor III
1,230 Views

It isn't clear to me why the procedure that provides output needs to be referenced via a pointer component, rather than being an override of a binding in an extension provided by the user.

0 Kudos
avinashs
New Contributor I
1,208 Views

I agree that the solution showed by @FortranFan is the correct way to extend the type. My conceptual understanding was flawed in that I thought that a passed argument to a procedure pointer could also be extended. However, to answer your question, there is a difference between the procedure pointer in my example and that of the override in a binding in an extension.

The procedure pointer can be called from within the class function of the base class as I have done in my example but the override can be called only in the main program. If you notice, I want to call ACCESS from within POWER

Let us suppose that I have written a base class for integrating ODEs from T0 to TF at intervals T0+H, T0+2H, ..., T0+M*H=TF. The base class defines a subroutine SOLVEODE that in turn calls a subroutine ONESTEP to integrate from T to T+H in a loop that goes from 1 to M. After each call to ONESTEP in the base class, I can call the procedure pointer ACCESS (if associated) and the user-defined printing of intermediate results can be done by accessing the specific procedure defined in the main program. (Note: if the base class is not extended, there is no error or flaw in logic by doing so).

The base class can then be extended to implement specific ODE solvers such as RK2, RK4 etc. that override ONESTEP and store the necessary coefficients and parameters of that method. But SOLVEODE and ACCESS are defined once in the parent class. I wrongly assumed that since ACCESS was being passed the current instance of HANDLE, the derived class would pass the extended version of HANDLE to ACCESS. This is not possible as has been explained but if so would be convenient. Referring to Metcalf and Reid, this would be convenient to avoid the reverse communication mode and common blocks that are extensively used in legacy Fortran 77 ODE solvers to allow communication with the user.

A possible solution would be to redefine SOLVEODE in the derived class but this would be repetition of code that would have to be redefined in each extension if a change were made, which is against OOP principles.

In summary, if I wish to achieve what I did in my original example with a procedure pointer, then the PP must accept the base class as an argument in the main program. The derived class binding can be useful for printing before or after calling the methods of the derived class.

0 Kudos
FortranFan
Honored Contributor III
1,195 Views

@avinashs ,

Your questions and interests and discussion points in this particular thread have to do with general Fortran and scientific software design and OO.  If you are keen on broader peer-to-peer feedback on your interests, I strongly recommend you also try other forums such as the newer Fortran Discourse site as well as the long-standing one of comp.lang.fortran.

You must also be aware of resources such as this.

My hunch is if you attempt perform a thorough OO analysis first of you're trying to with procedure pointer component pursuit, you will convince yourself that is ineffective with type inheritance and extension and consequently abandon it.

0 Kudos
avinashs
New Contributor I
1,160 Views
0 Kudos
IanH
Honored Contributor III
1,268 Views

The interface of the procedure pointer in the parent type says "I take an argument that is type compatible with this type (which happens to be the parent type)".  That is not the same as "I take an argument that is the same as the dynamic type of whatever object I am being referenced through".  You can't express the concept in Fortran, and it would be problematic if you could for a component (a component is part of the value of an object, a binding is not).

Why is the procedure pointer in the base class?  Could it be moved to be a component in the extension, referenced via a binding that is present in the base class and that is overridden in the extension type to invoke the procedure pointer?

0 Kudos
Reply