From: relaxmike on
On 17 avr, 14:27, Gary Scott <garylsc...(a)sbcglobal.net> wrote:
> I oppose preprocessors, because whatever gets put in the
> standard will basically be inferior to tools I had on one or another
> operating system in the past and I'll always know how inferior it is as
> I try to force it to do things it wasn't designed for.

You made a point here, because the pre-processing method is
difficult to debug directly, except if you debug the pre-processed
source code instead of the template one.
This can be easily solved though, via makefile commands such as :

%.f90 : %.F90
$(FPP) $(FPPOPTS) $< -P $@

%.o : %.f90
$(FC) $(F90OPTS) -c $< -o $@

That is to say that the .F90 contains the template to pre-process.
make start to compute the .f90 specific source code, depending
on the template one.
It is only the .f90 pre-processed source code which has to be
compiled. This allows to debug interactively the specific
source code, while maintaining only the template one.
The "-P" option of the fortran preprocessor may allow to remove
the lines introduced by the preprocessor in the preprocessed
file which points to the line number in the original template.

Now that I have revealed all my very secret tricks (!!!), I don't
see any main drawback to using pre-processing as a template method,
except that the features implemented in many pre-processor are very
poor, compared to what is possible in Python, m4 or Tcl for example.

Most compilers include a pre-processor inside the
compiler itself, for example Intel Fortran, gfortran, g95, Sun,
Ibm, etc...
Even if the pre-processor is not included in
your favorite compiler, or if you dislike the features
that it provides, you can use an external one, for example
the C pre-processor. If the C pre-processor is not available
on the machine (which has a very low probability, but is possible),
you can get the source of gcc and compile your own release.

In fact, the main reason why pre-processing macros are not used
widely in fortran is for historical reasons, I suppose, and by the
fact
that many fortran developers are developing only in fortran,
with high-level skills in science, most of the time, and, most
of the time, low-level skills in software : there are many,
many exceptions to this general fact, and these exceptions are
the core of this forum.
Put a C developer in front of a fortran source, and he will
introduce a new pre-processing macro every 3 minutes...

What I am curious is at the fortran 2003 features which
allows to do this. I think I'm going to read the standard
again.

Best regards,

Michaël

From: Richard Maine on
relaxmike <michael.baudin(a)gmail.com> wrote:

> Suppose you have to connect a fortran software
> with a Java platform and transfer data between the two with
> sockets. The subroutines which are used to manage the send/receive
> are complex and have to be able to manage all basic
> fortran data types : integer, real, character, and all possible
> arrays from rank 1 to rank 7. Let us count 3 * 7 = 21 subroutines
> to send data and 21 subroutines to receive data. But you also
> have to manage errors (21 more), etc...
> In short : the connection is not possible to do in fortran without
> fortran templates.

I agree that something like templates can be useful in some situations.
But note that, depending on details, the particular scenario described
above can usually be handled better in other ways. Typically, all you
need is an equivalent of a C void pointer, along with the data size.
Then you can do it all in a single procedure, with no need for
templates. I've done this exact thing for a long time, as I also have
Fortran code that passes data via sockets. So have other people. This
one is done a lot.

You end up with a single procedure, rather than the multiplicity of
procedures that the template approach involves. The template approach
makes it more practical to create that multiplicity of procedures, but
they still do end up having to get created.

In Fortran 90/95, you have to use tricks that aren't technically
standard conforming, but still have decent portability in practice, in
order to do the needed "type cheating". In Fortran 2003, you can use
either the C interop features or the unlimitted polymorphic feature to
make such code standard conforming.

--
Richard Maine | Good judgement comes from experience;
email: last name at domain . net | experience comes from bad judgement.
domain: summertriangle | -- Mark Twain
From: relaxmike on
On 17 avr, 18:29, nos...(a)see.signature (Richard Maine) wrote:
> I agree that something like templates can be useful in some situations.
> But note that, depending on details, the particular scenario described
> above can usually be handled better in other ways. Typically, all you
> need is an equivalent of a C void pointer, along with the data size.
> Then you can do it all in a single procedure, with no need for
> templates. I've done this exact thing for a long time, as I also have
> Fortran code that passes data via sockets. So have other people. This
> one is done a lot.
>
> You end up with a single procedure, rather than the multiplicity of
> procedures that the template approach involves. The template approach
> makes it more practical to create that multiplicity of procedures, but
> they still do end up having to get created.

I don't understand how to implement this way. What kind of fortran
data type corresponds to a C void pointer ?
Let's take a practical example or reading a data in a string.
The following source code read a data in an internal file
and returns the value read, depending on the type of variable.
It is over-simplified to make the point shorter.
It does not use fortran templates, but only standard fortran.

interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data

subroutine readfile_data_logical ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
logical , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_logical

subroutine readfile_data_integer ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
integer , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_integer

It is very important to factorise this small code, because the
interface
could be extended way beyond the current state, for example reading
more
complex data types :

interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
module procedure readfile_data_real
module procedure readfile_data_double
module procedure readfile_data_logical_array1
module procedure readfile_data_integer_array1
module procedure readfile_data_real_array1
module procedure readfile_data_double_array1
module procedure readfile_data_logical_array2
module procedure readfile_data_integer_array2
module procedure readfile_data_real_array2
module procedure readfile_data_double_array2
[etc... until the maximum rank size available in fortran is reached
= 7]
end interface readfile_data

with the following header for the readfile_data_double_array2
subroutine :

subroutine readfile_data_double_array2 ( current_data_line ,
myvalue , status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
real(dp), dimension(:,:) , intent (out) :: myvalue
integer, intent(out) :: status

Developing the code with standard fortran is near to impossible,
although hiring a PhD with European funds to do this is allways
possible...
With fortran templates, it is really easy.
Suppose that the file "readfile_data_template.F90" contains the
following
source code, parametrized with the "_DATATYPE" macro.

subroutine readfile_data__DATATYPE ( current_data_line , myvalue ,
status )
implicit none
character ( len = 200 ) , intent(in) :: current_data_line
_DATATYPE , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine readfile_data_integer

The previous source code would be using the template like this :

interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data

#define _DATATYPE logical
#include "readfile_data_template.F90"

#define _DATATYPE integer
#include "readfile_data_template.F90"

And now, with only 6 lines of source code, extending it to
manage for several data types, and arrays of all possible
sizes is possible. The same problem would be even easier to
manage with Forpedo, I suppose.

Now, how do you suggest to solve the problem with your method
Richard ? What is the fortran type which corresponds to the
C void pointer ?

Regards,

Michaël

From: relaxmike on
I still try to experiment the idea, so here is a sample
full demonstration of the pre-processing way.
Here is the file "test2_template.F90" :

subroutine _READFILE_DATA_NAME ( current_data_line , myvalue ,
status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
_DATATYPE , intent (out) :: myvalue
integer, intent(out) :: status
status = 0
read ( current_data_line , * , err = 100 , end = 100) myvalue
return
100 continue
status = 1
end subroutine _READFILE_DATA_NAME

And this is the test file :

module m_moduletest2
interface readfile_data
module procedure readfile_data_logical
module procedure readfile_data_integer
end interface readfile_data
contains

#define _DATATYPE logical
#define _READFILE_DATA_NAME readfile_data_logical
#include "test2_template.F90"
#undef _DATATYPE
#undef _READFILE_DATA_NAME

#define _DATATYPE integer
#define _READFILE_DATA_NAME readfile_data_integer
#include "test2_template.F90"
#undef _DATATYPE
#undef _READFILE_DATA_NAME

end module m_moduletest2

program test2
use m_moduletest2
character (len=200) :: current_data_line
integer :: myintvalue1
logical :: mylogicalvalue1
integer :: status
current_data_line = "1"
call readfile_data ( current_data_line , myintvalue1 , status )
write ( * , * ) "status:", status
write ( * , * ) "Integer : ", myintvalue1
current_data_line = ".true."
call readfile_data ( current_data_line , mylogicalvalue1 , status )
write ( * , * ) "status:", status
write ( * , * ) "Logical : ", mylogicalvalue1
end program test2

I tried to use "include" only statements without pre-processing,
and that lead to a source code which I am not proud of.
But the "C void pointer" idea leads to the following source.
The "void" idea is managed with a derived type containing all
possible fortran basic data types.
The "pointer" is the class name, a string containing "integer",
or "logical", depending on the type to manage.
This is the source code :

module m_moduletest4
type :: DATATYPE
integer :: value_integer
logical :: value_logical
character ( len = 200 ) :: classname
end type DATATYPE
contains
subroutine readfile_data ( current_data_line , classname , myvalue ,
status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
character ( len = * ) , intent(in) :: classname
type ( DATATYPE ) , intent(out) :: myvalue
integer, intent(out) :: status
status = 0
myvalue % classname = classname
select case ( classname )
case ( "integer" )
read ( current_data_line , * , err = 100 , end = 100) myvalue %
value_integer
case ( "logical" )
read ( current_data_line , * , err = 100 , end = 100) myvalue %
value_logical
case default
write(6,*) "Bad classname."
end select
return
100 continue
status = 1
end subroutine readfile_data
subroutine printdata ( myvalue )
implicit none
type ( DATATYPE ) , intent(in) :: myvalue
write ( * , * ) trim(myvalue % classname) , ":"
select case ( myvalue % classname )
case ( "integer" )
write ( * , * ) myvalue % value_integer
case ( "logical" )
write ( * , * ) myvalue % value_logical
case default
write(6,*) "Bad classname."
end select
end subroutine printdata
end module m_moduletest4

program test4
use m_moduletest4
character (len=200) :: current_data_line
type ( DATATYPE ) :: myvalue
integer :: status
current_data_line = "1"
call readfile_data ( current_data_line , "integer", myvalue ,
status )
write ( * , * ) "status:", status
call printdata ( myvalue )
current_data_line = ".true."
call readfile_data ( current_data_line , "logical" , myvalue ,
status )
write ( * , * ) "status:", status
call printdata ( myvalue )
end program test4


Of course, dealing with that implementation consumes a lot of memory,
especially if we include arrays of integers, arrays of logicals,
etc...
To solve that, we can use fortran 90 pointers and allocate only the
type under use. Since the abstract data type is more complex, it
is now time for full OO.

module m_moduletest5
type :: DATATYPE
integer, pointer :: value_integer => NULL()
logical, pointer :: value_logical => NULL()
end type DATATYPE
contains
subroutine data_newfromstring ( this , current_data_line ,
classname , status )
implicit none
character ( len = * ) , intent(in) :: current_data_line
character ( len = * ) , intent(in) :: classname
type ( DATATYPE ) , intent(out) :: this
integer, intent(out) :: status
status = 0
select case ( classname )
case ( "integer" )
allocate ( this % value_integer )
read ( current_data_line , * , err = 100 , end = 100) this %
value_integer
case ( "logical" )
allocate ( this % value_logical )
read ( current_data_line , * , err = 100 , end = 100) this %
value_logical
case default
write(6,*) "Bad classname."
end select
return
100 continue
status = 1
end subroutine data_newfromstring
subroutine data_print ( this )
implicit none
type ( DATATYPE ) , intent(in) :: this
if ( associated ( this % value_integer ) ) then
write ( *, * ) "integer :"
write ( * , * ) this % value_integer
elseif ( associated ( this % value_logical ) ) then
write ( *, * ) "logical :"
write ( * , * ) this % value_logical
endif
end subroutine data_print
subroutine data_free ( this )
implicit none
type ( DATATYPE ) , intent(in) :: this
if ( associated ( this % value_integer ) ) then
deallocate ( this % value_integer )
elseif ( associated ( this % value_logical ) ) then
deallocate ( this % value_logical )
endif
end subroutine data_free

end module m_moduletest5

program test5
use m_moduletest5
character (len=200) :: current_data_line
type ( DATATYPE ) :: myvalue
integer :: status
current_data_line = "1"
call data_newfromstring ( myvalue , current_data_line , "integer",
status )
write ( * , * ) "status:", status
call data_print ( myvalue )
call data_free ( myvalue )
current_data_line = ".true."
call data_newfromstring ( myvalue , current_data_line , "logical" ,
status )
write ( * , * ) "status:", status
call data_print ( myvalue )
call data_free ( myvalue )
end program test5

I admit that the current code is still manageable, but have many
limitations
that the pre-processing version have not, including :
- the memory is managed at hand, which can lead to memory leaks.
This is easy to manage with only 2 basic types, but what if there are
21 ?
- the abstract data type cannot handle user-defined derived-types.
The pre-processing system allows to define whatever type you want to,
without any complication in the client source code.
This is not the case with the hand-crafted "pointer to everything"
abstract data type.
On the good side, the source code is very easy to debug and uses only
standard fortran 90 statements.

Still the current "pointer to everything" is a heavy solution.
What if we wand to define a "writetostring" method : another 21*3
block of source code. And what if we want to define an error
system for the class : another 21*3 block of source code !
This is not practical.

But you may suggest another way ?

Best regards,

Michaël
From: Richard Maine on
relaxmike <michael.baudin(a)gmail.com> wrote:

> What kind of fortran
> data type corresponds to a C void pointer ?

Type(C_PTR) in the F2003 C interop stuff.

--
Richard Maine | Good judgement comes from experience;
email: last name at domain . net | experience comes from bad judgement.
domain: summertriangle | -- Mark Twain