<< 9.モジュール >>

   

1.commonに代わる、第三のプログラム単位


 もうこのHPのページも、最後に近づいていますね。時間が経つのは本当に早いもので、書いていたらもうこのページまできてしまいました。皆さんも、このページを読んでいるということは、(初心者としてはじめた方は)熱心にFortranを勉強しているのだということが分かります。なぜかというと、熱心じゃなければこのページに到達することはまずないからです。気合が入っていない人ならば、おそらくDOループあたりで挫折してしまうことでしょう。ここまで来るには、結構な精神力が要ります。ソース文の書き方の検討・手打ち入力・面倒なコンパイル・実行エラー潰し、というように、面倒で大変な作業がいっぱいあります。それらを乗り越えてこのHPを活用していただいている皆さん、本当にありがとうございます。その調子で、このページもがんばっていきましょう。
 さて、今回学習する大きなテーマは、「モジュール」です。モジュールというのは、僕が勉強した範囲ではC言語には出てこなかった単語です。サブルーチンのことをモジュール化といいますが、ここでいうモジュールの意味とは少し違います。主プログラムから切り離すという意味では同じですが。 モジュールというのは、簡単にいってしまえば「COMMON文と同じような働きをする、様々なものに関する定義プログラム単位」です。キーワードは、「大域要素」と「プログラム単位」と「定義」です。
 モジュールは主プログラムや副プログラムのように、それで1つのプログラム単位です。ですから、主プログラムとは別の位置に書きます。そして、このモジュールの中では、定義が行なわれます。具体的に何を定義するのかというと、変数や、関数・サブルーチン副プログラム、関数の総称名などです。これにより、定義されたものが大域要素になります。大域要素というのは、定義したプログラム単位だけではなく、全てのプログラム単位においてその定義内容が適用されるもののことです。その点では、COMMON文と似ていますが、一般的にCOMMON文よりも好んで使用されます。
 COMMON文は非常に便利な文です。COMMON文を使用し、各プログラム単位が同じ変数にアクセスして変数を共有するので、型宣言をする必要がなくなり、型宣言による手間や宣言間違いがなくなります。また各プログラム単位がお互いに参照する手段を持っていないときにも、変数を共有することができるCOMMON文は有効です。
 しかし、COMMON文の問題点は、副プログラムで共有変数を参照する際、主プログラム(または初期値設定プログラム)で定めたCOMMON文中の変数群の並びを、正確に写さなければならないということです。主プログラムで「common /p/a,b,c」と並んでいたら、「common /p/a,b,c」と全く同じに移さなければなりません。「common /p/a,c,b」と書いてしまえば、bとcが入れ替わり、コンパイルエラーや実行エラーが出たり、少なくとも正しい処理結果は実現しません。COMMON文が名前で変数を判別してくれるのなら問題ないのですが、変数の並び順で定義と参照を一致させているので、どうしても副プログラムでの変数の使用時に苦労することになります。またこれが原因かどうか分かりませんが、COMMON文を使用していると、原因不明のエラーが出ることがあったり、共通ブロックに指定したある変数に別の名前をつけることができるEQIVALENCEを使用した場合も、最適化ができなくなって問題がおきることがあるらしいです。こうした理由から、COMMON文は新しくFortran90で登場したモジュールにその立場を取って代わられることとなりました。
 モジュールもCOMMON文のように変数を大域要素として定義することができます。しかし、使用する際は「USE」文を使用するだけでいいので、COMMON文のように正確に定義文を写す必要はなく、安全性が高まります。また、後で紹介するONLY句などを使うことにより、プログラム単位によってアクセスできる変数を指定することができる点でも、安全性の高いプログラムを組むことに適しています。ただ、モジュールを使用しなくてもプログラムを組むことは問題なくできるので、その必要性を感じない人は使用しなくても構いません。

 では、実際にモジュールを使用してみましょう。モジュールはそれで一つのプログラム単位ですから、MODULEで始まり、END+MODULEで終了します。その中に、様々な定義内容を書いていきます。まずは、変数を共通変数として定義し、使用してみることからはじめましょう。共通変数をモジュール文の下に書き、その下に副プログラムを、内部プログラムとしてモジュール内部に書きます。共通変数と副プログラムの仮引数が違うのであれば内部副プログラムでなくてもいいのですが、同じである場合、コンパイル時にエラーが出てしまうので、共通変数を仮引数として使用するのであれば、副プログラムはモジュールの中に書くようにしてください。
 下の例は、モンテカルロ法による、円周率πの計算方法です。半径1、原点0の円(-1  モンテカルロ法というのは、物体AとBが用意されたとき、何度も何度もあくまでデタラメにAかBを選んでいくと、そのA(またはB)を選んだ個数と選んだ全体の回数の比は、A+BとA(またはB)の実際の個数の比(あるいはAの出る確率)になるというものです。10円玉を例にとると、10円玉の表と裏のどちらかをデタラメに選んでいきます(地面に落とすのでも構いません)。これを何万回と繰り返します。その結果、選んだ回数と表の出た回数の比を出してみると、ほぼ2分の1(表の出る確率は2分の1)になります。
 これを応用すると、面積の比に適用することができます。「4分円の面積/正方形の面積=0.25π/1=π/4」となりますね。正方形の中に乱数をn回発生させ、その中で4分円の中に発生した回数がm回だったとしたら、回数の比「m/n」は面積の比「π/4」とほぼ等しくなり、両者をイコールで結び、πを求めることができるのです。それが、下のプログラムです。


module teigi
 real::pai
 integer::n
 contains
  subroutine monte(n,pai)
   real::a(999),b(999),x,y
   integer::v(8),m,i
   m=0
   do i=1,n
    call random_number(a)
    call random_number(b)
    call date_and_time(values=v)
    x=a(v(8))
    y=b(v(8))
    if(((x**2+y**2)<=1.0))m=m+1
   enddo
   pai=(4.0*real(m))/real(n)
  end subroutine monte
end module


program main
 use teigi
 print *,'正方形に打つ乱数の個数を入力'
 read *,n
 call monte(n,pai)
 print *,pai
end program


   今回は乱数の発生方法で、「SYSTEM_CLOCK」を使用せずに行ないました。時間を利用していることには変わりないのですが、同じく見込みサブルーチンである「DATE_AND_TIME」を使っています。乱数を発生させる前に毎回パソコンの時計のミリ秒を取り出し、そのミリ秒を添え字として、999種類の乱数から一つだけ選ぶようにしています。このやり方はいいのかどうかよく分からないのですが、起動のたびに同じ乱数を発声させない方法として、こういうのもあるんだ、という程度で見てみてください。
 PROGRAM文の前にあるブロックが、モジュールの内容です。副プログラムは主プログラムの前に書いても後ろでも構わないのですが、モジュールは主プログラムよりも前に書かないと動作しないので、必ず主プログラムの前に書くようにしてください。このモジュールの、2・3行目が、共通変数の定義です。ここで定義した変数が、主プログラムの2行目にある「use モジュール名」で使用することができます。既に定義してあるので、主プログラムにおいて再び定義する必要はありません。共通変数の定義の下にあるCONTAINSから、副プログラムの定義が始まります。これは、以前にお話した内部プログラムと同じです。今まで書いてきたのと同じように書いた副プログラムの定義を、CONTAINSの下にはめ込むだけです。主プログラムでUSE文でモジュール使用を宣言すれば、共通変数と同様に、この副プログラムも使用することができるようになります。
 モジュールを使用すると、共通変数の宣言部分も主プログラムから切り離されるので、邪魔なものがなくなり、主プログラムで書く、プログラムの全体的な大まかな処理内容がぐっと見やすくなります。またモジュールの中でまとめられるので、要素の区分がはっきりしてきます。

 もう一つ例を出しましょう。構造体も共通変数として利用することができます。下の例文は、男子・女子別に、身長のが最高の人の名前を表示するプログラムです。


module teigi
 type student
  character(len=50) name
  real height
 end type student
 contains
  subroutine saidai(male,female)
   type(student) male(5),female(5)
   integer xmax_male,xmax_female
   xmax_male=maxloc(male%height,1)
   xmax_female=maxloc(female%height,1)
   print "(/'男子で身長が最高の人は',a30/'女子で身長が最高の人は',a30)",male(xmax_male)%name,female(xmax_female)%name
  end subroutine saidai

  subroutine nyuryoku(male,female)
   integer i
   type(student) male(5),female(5)
   print *,'まず男を名前、身長の順に入力'
   do i=1,5
    read *,male(i)
   enddo
   print *,'次に女子を、同様に行なう。'
   do i=1,5
    read *,female(i)
   enddo
  end subroutine nyuryoku
end module


program main
 use teigi
 type(student) male(5),female(5)
 call nyuryoku(male,female)
 call saidai(male,female)
end program


 構造体の定義はTYPE文とEND文が付くので、結構長めに感じてしまいます。モジュールを使えば、USE文だけで参照が可能となるので、非常にスッキリしていていいと思います。分量が少なくなるというのは、見る上で結構重要です。
 また、モジュールを使わないと、各プログラム単位で構造体の定義を行なわないといけなくなります。つまり、主プログラムと各サブルーチンで、TYPE〜ENDまでをいちいち、正確に、同じことを書かなければならないのです。これはコピーアンドペーストすればまあいいのですが、いちいちその元の部分を探し出してコピーするのは面倒ですし、手書きでそのまま書いて間違えたら問題です。モジュールで一度宣言しておき、後はUSE文で引用するだけでいいというのは、構造体の使用を簡単にしてくれ、面倒さをかなり少なくしてくれます。

 モジュールはCOMMON文の代わりに登場したといっても過言ではありません。これだけ見ても、いちいち定義したときと同じ順序で変数名を並べていかなければならないCOMMON文よりも使用しやすいことが分かると思います。そのCOMMON文では、サブルーチンを使用する際、引数を省略することができました。モジュールではどうなのでしょうか。もちろん、COMMONの代用なのですから、それも可能です。
 モジュールもCOMMONと同様、変数を書くプログラム単位で共有します。ですから、主プログラムで使用する変数も、副プログラムで使用する変数も中身が同じものになるので、引数は不要になります。ですから、下のようなプログラムが書けるようになります。

module data1
 implicit none
 real::d(4),hei
 integer::i
 contains
  subroutine heikin
   hei=0.0
   do i=1,4
    hei=hei+d(i)
   enddo
   hei=hei/4.0
  end subroutine heikin
end module data1

program main
 implicit none
 use data1
 read d
 call heikin
 print *,d
end program

 普通、サブルーチンを使用する際には主プログラムでは実引数が、副プログラムで図り引数が必要になりますが、モジュールで変数を共有すれば、わざわざそれらを用意する必要はありません。それは、両プログラムで同じ変数を使用しているので、副プログラムで書き換えられたheiをそのまま主プログラムが使用することができるからです。また主プログラムでのheiは副プログラムでのheiに相当するので、データの送受信も不要になります。
 引数を省略すると、見た目がとてもスッキリします。引数がなくなるだけで、これほどまでに見やすくなるのか、と思います。特に引数が5,6個もあるときとくらべると、その感想はとても大きいものになると思います。引数を省略することで何の処理をしているサブルーチンなのかが分かりにくくなる場合もあるかもしれませんが、それはサブルーチン名をもっと分かりやすいものに変えたり、注釈をつけたりすることで対処できます。
 また引数を共有できるわけですから、宣言が少なくなります。これは前にも話しましたね。既に宣言された変数を使用するので、わざわざ宣言をしなおすことがなくなるのです。実際、こちらのほうがモジュール(変数の共有)のよさを感じることができます。本当に、変数の宣言って面倒ですからね。
 単純な宣言だけでも結構面倒です。いちいちREALだのINTEGERだのするだけですこしいらだってしまいます(プログラミングに疲れてくると)。プログラミングが上達してくると、変数の動的割付をするようになり、ALLOCATABLEも宣言に書かなければならなくなったり、コンパイル時のエラーチェックを強化するためにINTENTも加えるようになったりします。これらを加えると、これらがない変数とある変数とを分けて宣言しなくてはなりません(a,bの2つの変数があったとき、aはただの整数型、bは割付の整数型だと、integer::aとinteger,allocatable::b(:)というように、宣言文を一緒にまとめられない)。すると書く手間が増え、宣言に使うスペースも増え、と、面倒・見づらいのオンパレードになってしまいます。
 ですから、変数を共有し、こうした弊害をなく競ることは、ソースの作成のときに非常に威力を発揮します。

 しかし、変数の共有にも問題はあります。例えば、上のように変数を共有して引数を省略する場合、サブルーチンの再利用ができなくなる、ということです。
 完全にできなくなるというわけではありません。例えば一度「平均」を求め、もう一度同じ変数にデータを入れなおして、再び「平均」を求める、というように、使用する変数名が同じであれば再利用することができます。
 しかし、それは「変数名が同じ場合」です。違う変数名だと無理です。
 どういう場合かというと、まず「d」という変数にデータを入れて、サブルーチンheikinで平均を求めます。そのあと今度は「a」という変数にデータを入れて、これについても平均を出したいという場合です。heikinでは上の例のように「d」と「hei」をモジュールで共有宣言し、借り引数を省略しています。
 この場合、サブルーチンheikinでは「d」と「hei」で処理が構成されているので、「a」を使って平均を求めることができません。「d」と「hei」が共有変数ではなく仮引数として設定されている(「subroutine heikin(d,hei)」)とされているのであれば、「a」を実引数とすることで、このサブルーチンを使用して平均を求めることができます。
 しかしこの場合は「d」と「hei」を共有宣言していて、引数を設定していないので、このサブルーチンでは「a」の平均は求められません。「call heikin(a,hei2)」としても副プログラム側に仮引数がなくコンパイルエラーが出るだけですし、「call heikin」としても再び「d」と「hei」による処理が行なわれるだけで、もちろん「a」の平均は出ません。「heikin」の内容を変えたくないなら、新しく「a」用の平均を求めるサブルーチンを作成するしかありません。
 ですから、このように変数名が違うだけで同じ処理を行なうことがわかってる場合・そうなる可能性が高い場合は、面倒でも引数を設定したほうがいいと思います。主プログラムと副プログラムの双方に引数を設定すれば、違う変数であってもそのサブルーチンを共用することが可能になります。副プログラムでは通常主プログラムと同じ変数名の変数にしても違う変数として認識されてしまい、面倒な手続きをしなければならず、嫌な機能だなと思っていましたが、その機能のよさはここにあったのですね。
 引数の省略を行なうときは、基本的にサブルーチンの再利用を行なわず、そのデータの処理専用の副プログラムにするんだ、という意識が大事だと思います。



前のページへ

目次へ戻る

次のページへ