I'm writing some code in Fortran 2003 that does a lot of linear algebra with sparse matrices. I'm trying to exploit some of the more abstract features of the new standard so I have simpler programs without too much repeated code.
I have a procedure solver
which takes in a matrix, some vectors, the tolerance for the iterative method used etc. I'm passing a pointer to a procedure called matvec
to it; matvec
is the subroutine we use for matrix-vector multiplications.
The problem is, sometimes matvec
is a procedure which takes in extra arguments colorlist, color1, color2
above the usual ones sent to this procedure. I can think of several ways of dealing with this.
First idea: define two different abstract interfaces matvec1
, matvec2
and two different solvers. This works but it means duplicating some code, which is just what I'm trying to avoid.
Another idea: keep the same abstract interface matvec
, and make the extra arguments colorlist
, color1
, color2
optional. That means making them optional in every matvec routine -- even ones for which they're not really optional, and for routines where they're not even used at all. Pretty sure I'll go to hell if I do this.
I can think of plenty of other less than optimal solutions. I'd like some input on this -- I'm sure there's some elegant way to do it, I'm just not sure what it is.
The question is really, whether the additional arguments must be passed every time the procedure is invoked (because they change between two invocations), or they can be initialized at some point and then just used in the function. In the later case you could create a class with an abstract interface, which defines your subroutine matvec
with the essential arguments. You can then extend that class with more specialized ones, which can hold the additional options needed. They will still have to define the same matvec
interface as the parent class (with the same argument list), but they can use the additional values stored in them when their matvec
procedure is called.
You find a detailed example in this answer for a similar case (look for the second example showing module rechercheRacine
).
Instead of passing the procedure pointer as an explicit argument, you could put the various matvec
routines behind a generic interface:
interface matvec
module procedure matvec1, matvec2
end interface
Then your solver
routine can just use the generic name with or without the extra arguments. The same approach can of course also be taken when using Bálint's suggested approach of defining a solver
as a derived type with type-bound procedures:
type :: solver
real, allocatable :: matrix(:,:), v1(:), v2(:)
contains
procedure, pass :: matvec1
procedure, pass :: matvec2
generic :: matvec => matvec1, matvec2
end type
The main difference is that this does not use polymorphism to determine the correct procedure to invoke, but rather the characteristics of the dummy arguments.
I'm not sure of your intentions for the procedure pointer; if you wish to change its target at runtime (or perhaps assign some special meaning to its 'undefined' status), then pointers are the only way and all targets need to match the same abstract interface. If instead you just need to select one of several procedures based on their arguments, then you can exploit interfacing (my example) or overloading (Bálint's example). Each extension of a type can extend an inherited generic
binding with new procedures, or overload an inherited specific binding.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With