- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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>
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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?
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page