Fortran2008で導入されたsubmoduleが便利でしたので紹介します。こちらのsubmodule でインターフェース分離の記事を参考にさせていただきました。
依存関係あるある
Fortranで複数ファイルをmakeすることを考えます。例えば、二つのファイル
module mymodules1
implicit none
contains
subroutine sub1()
use mymodules2,only:print2
write(*,*) "in sub1"
call print2()
end
end
module mymodules2
implicit none
contains
subroutine sub2()
use mymodules1,only:sub1
write(*,*) "in sub2"
call sub1()
end
subroutine print2()
write(*,*) "in sub2, print"
end
end
という二つのファイルがあるとします。このファイル、普通のやり方だとコンパイルが通りません。なぜなら、mymodule1はmymodule2のサブルーチンを呼び、mymodule2はmymodule1のサブルーチンを呼んでいるため、どちらを先にコンパイルしてもエラーが出てしまうからです。つまり、依存関係がウロボロスの輪になっていて、どちらからもコンパイルができないのです。こうならないようにするには、どのmoduleにどのサブルーチンを属すべきかを慎重に考える必要がありますよね。
しかし、submoduleを使えば、そのようなことをせずにほとんどそのままコンパイルできます。
submodule
submoduleを使って書き換えてみます。まず、先ほどの二つのファイルを、moduleではなくsubmoduleにします。つまり、
submodule(mymodules1) mod1
implicit none
contains
subroutine sub1()
use mymodules2,only:print2
write(*,*) "in sub1"
call print2()
end
end submodule
と
submodule(mymodules2) mod2
implicit none
contains
subroutine sub2()
use mymodules1,only:sub1
write(*,*) "in sub2"
call sub1()
end
subroutine print2()
write(*,*) "in sub2, print"
end
end submodule
とします。やったことは、moduleをsubmoduleにしただけです。ただし、submoduleというからには親となるmoduleがいりまして、今回は
module mymodules1
implicit none
interface
module subroutine sub1()
end
end interface
end module
と
module mymodules2
implicit none
interface
module subroutine sub2()
end
module subroutine print2()
end
end interface
end module
とします。どのようなサブルーチンや関数が入っているかをこれらのmoduleのinterfaceブロック内に記述してあります。実は、これで、この二つのファイルを先にコンパイルすれば、先ほどのウロボロスの輪は解消されます。
makefileは
FCMP = gfortran
PROG = a.out
moduledir = ./modules
SRCmodule = $(wildcard $(moduledir)/*.f90)
OBJmodule = $(SRCmodule:.f90=.o)
maindir = ./main
SRCmain = $(wildcard $(maindir)/*.f90)
OBJmain = $(SRCmain:.f90=.o)
OBJ = \
$(OBJmodule) \
$(OBJmain) \
main.o
.SUFFIXES:
.SUFFIXES: .o .f90 $(SUFFIXES)
%.o: %.f90
$(FCMP) -c $< -o $@
$(PROG): $(OBJ)
$(FCMP) -o $(PROG) $(OBJ)
clean:
rm -f *.o *.*mod *~
rm -f $(PROG)
rm -f $(maindir)/*.o $(maindir)/*.*mod $(maindir)/*~
rm -f $(moduledir)/*.o $(moduledir)/*.*mod $(moduledir)/*~
とします。これは、modulesディレクトリとmainディレクトリ内に入っている.f90ファイルをコンパイルするようにしています。modulesディレクトリ内に、mymodules1.f90とmymodules2.f90があり、こちらを先にコンパイルするようにしています。
このように、submoduleを使うことでinterfaceを先にコンパイルしてしまうことで、実装部分でどのような順番でコンパイルするかということに悩むこともなく、お互いを参照しているようなmoduleたちもコンパイルすることができるようになります。
なお、これがどのような時に役に立つかといいますと、FORTRANのModernizeに使えるいくつかの機能に紹介されている「異様に多いソースファイル群」を改良するときです。これは一昔前によく行われていた「ファイルに1 procedureだけを書くというスタイル」のコードたちをmoduleにまとめようとした時に、元のコードがmoduleを使わずに関数たちが定義されている場合に問題になることがあります。それぞれの関数が互いに参照されている場合、機能ごとにmoduleに分けようとすると、これまでは小さなファイルを連結して大きな一つのファイルにして一つのmoduleとしないとコンパイルを通すことができませんでした。submoduleを使えば、一つ一つの小さなファイルをそのままに、最初と最後をsubmoduleでラップすることで、機能ごとに違うmoduleとしてまとめることができ、その結果ファイル群を整理することができます。