janou195
janou195

Reputation: 1215

Clean way to separate functions/subroutine declaration from definition in Fortran 90

I am working on a big Fortran 90 code, with a lot of modules. What bothers me is that when I modify the inner code of a function inside a module (without changing its mask), my Makefile (whose dependencies are based on "use") recompile every file that "use" that modified module, and recursively.

But when modifying the inner code of a function without touching its input/output, recompiling other files than the modified one is useless, no?

So I would like to separate the function declaration from their definition, like with the .h files in C or C++. What is the clean way to do this? Do I have to use Fortran include/preprocessor #include, or is there a "module/use" way of doing this?

I have tried something like this, but it seems to be quite nonsense...

main.f90

program prog

  use foomod_header

  integer :: i

  bar=0
  i=42
  call foosub(i)

end program prog

foomod_header.f90

module foomod_header

  integer :: bar

  interface 
    subroutine foosub(i)
      integer :: i
    end subroutine
  end interface

end module foomod_header

foomod.f90

module foomod

  use foomod_header

  contains

  subroutine foosub(i)
    integer ::i

    print *,i+bar

  end subroutine foosub

end module foomod

Upvotes: 2

Views: 600

Answers (2)

kdb
kdb

Reputation: 4426

Maybe the cleanest solution is to change the build system.

The real dependency introduced by a USE statement is not the source-code file, but the generated .mod file, which acts as a sort of "binary header file". I.e. where makefiles typically contain something like

MyProgram.o: MyModule.f90

what they really should contain is

MyProgram.o: MyModule.mod
MyModule.mod: MyModule.f90

with the creation of the .mod file being done in a way, that ensures an unchanged file-system timestamp, if the interface hasn't actually changed.

Sadly, compiler-support is awkward. Most compilers will overwrite the .mod file anyway, so the build process must at the same time detect, that the .mod file hasn't changed, e.g. by restoring the old modification time if the contents are unchanged, but at the same time needs to avoid recompiling the source file unnecessarily, which requires updating the modification time of the .mod file.

Additionally, some compilers (Intel, *cough*) add a binary time-stamp to the contents of the .mod files, that needs to be manually excluded from the comparison and has changed binary position across releases. This adds effort when supporting multiple compilers.

Upvotes: 1

IanH
IanH

Reputation: 21441

If submodules aren't an option (and they are ideal for this), then what you can do is make the procedure an external procedure and provide an interface for that procedure in a module. For example:

! Program.f90
PROGRAM p
  USE Interfaces
  IMPLICIT NONE
  ...
  CALL SomeProcedure(xyz)
END PROGRAM p

! Interfaces.f90
MODULE Interfaces
  IMPLICIT NONE
  INTERFACE
    SUBROUTINE SomeProcedure(some_arg)
      USE SomeOtherModule
      IMPLICIT NONE
      TYPE(SomeType) :: some_arg
    END SUBROUTINE SomeProcedure
  END INTERFACE
END MODULE Interfaces

! SomeProcedure.f90
SUBROUTINE SomeProcedure(some_arg)
  USE SomeOtherModule
  IMPLICIT NONE
  TYPE(SomeType) :: some_arg
  ...
END SUBROUTINE SomeProcedure

Some important notes:

  • There must only ever be one interface definition for a procedure accessible in a scope. Inside a subprogram the interface for the procedure defined by the subprogram is also considered defined - hence inside the subprogram you must not permit an interface block for procedures defined by the subprogram to be accessible. In terms of the example, this means that you must not have a USE Interfaces statement without an only clause inside the SomeProcedure external procedure.

  • If you do change the arguments or similar of the procedure inside SomeProcedure.f90 you had better make sure that you change the corresponding interface block inside the module!

  • If you can use F2003, the IMPORT statement can make life easier. Otherwise you might have to have additional modules (such as SomeOtherModule in the example) to share type definitions and the like between the Interfaces module and the external procedure.

  • If you have private entities or components relevant to the procedure then Fortran's rules entity and component accessibility may prevent you using this approach.

  • Typically some sort of whole program analysis is done at high levels of optimization. That analysis is typically much slower than the actual parsing of the code - splitting out procedures in this manner may not actually shorten build times significantly under these conditions.

Upvotes: 2

Related Questions