|
From: relaxmike on 17 Apr 2008 11:18 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 17 Apr 2008 12:29 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 18 Apr 2008 06:07 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 18 Apr 2008 08:51 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 18 Apr 2008 11:13
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 |