この文書の URL は http://www.cc.kyoto-su.ac.jp/~mtkg/lecture/comp_B/2012/08.html です。

モジュールとは

モジュール (module) は Fortran 90 で導入された新しいプログラム単位です。 モジュールを利用すると、大域変数(複数の副プログラム間で共有する変数)と複数の副プログラムをひとつにまとめることができます。

モジュールに属する変数や副プログラムは他のプログラム単位(メインプログラム、副プログラム、モジュール)から参照することができます。 個々の変数や副プログラムごとに公開・非公開を指定することにより、必要なものだけ外部に公開し、公開する必要のない変数や副プログラムを隠蔽しておくことができます。

定数・変数参照モジュール

次の例は、物理定数を定義するモジュールです。 必要な定数を一カ所にまとめておくと値の変更漏れなどを防ぐことができます。

! 定数モジュール
module pconst
  implicit none

  real(8), parameter ::         &
       & cc = 2.99792458e+08_8, & ! 光速 [m/s]
       & hc = 6.62606876e-34_8, & ! プランク定数
       & kb = 1.38065030e-23_8    ! ボルツマン定数

end module pconst

! メインプログラム
program work0801
  use pconst
  implicit none

  write(*,*) cc

  stop
end program work0801

モジュールの定義

モジュールは次のような構造をしています。

module モジュール名
  [宣言文]
[contains
  モジュール副プログラム]
end module モジュール名

「宣言文」の部分に外部に公開する定数や変数を宣言します。 この部分で宣言した変数はモジュール内の副プログラム間で共有されます。

pconst モジュールではモジュール副プログラムを定義していないので contains 以下が省略されています。

モジュールの参照

モジュールを他のプログラム単位から参照するには use 文を使います。 use 文は implicit 文や型宣言の前に置かなければいけません。

only オプションを使うと参照する変数や副プログラムを制限することができます。

use pconst, only : cc     ! const モジュールの cc だけを使う

特に指定しなければ、モジュールが提供するすべての変数と副プログラムを参照できます。

継続行

Fortran 90 のソースファイルの各行は 132 文字までに制限されています。 1行に収まらない長い文や、別の行に分けて書いた方がわかりやすい行は、は39行までの「継続行」に分割することができます。 行を分割するには継続マーク & を行末に置きます。 第1行を開始行、2行目以降を継続行といいます。

  s = x * x &    ! s = x*x + y*y + z*z と同じ
  & + y * y &
  & + z * z

継続行の最初の & は省略可能ですが、 & で始めた方が視覚的にはっきりしてよいでしょう。

モジュール副プログラム

今度は副プログラムを含んだモジュールの例をみてみましょう。

次の counter モジュールにはモジュール副プログラム inc と dec が含まれます。 これらの副プログラムはモジュール変数 c を共有しており、呼び出されるたびに c の値を 1 だけ増加または減少させます。

!------------------------------------------------------------
! カウンターモジュール
!------------------------------------------------------------
module counter
  implicit none

  ! モジュール変数
  integer, save :: c = 0

contains
  ! c の値を 1 増やす
  subroutine inc()
    implicit none
    c = c + 1
    return
  end subroutine inc

  ! c の値を 1 減らす
  subroutine dec()
    implicit none
    c = c - 1
    return
  end subroutine dec
end module counter

!------------------------------------------------------------
! メインプログラム
!------------------------------------------------------------
program work0802
  use counter
  implicit none

  write(*,*) c

  call inc()
  write(*,*) c

  call inc()
  write(*,*) c

  call dec()
  write(*,*) c

  stop
end program work0802

プログラムを実行して変数 c の値がどのように変化しているか確認してみましょう。

情報の隠蔽

counter モジュールではモジュール変数 c が外部から変更可能になっており、カウンタの値がユーザによってリセットされてしまう危険があります。 そこで、変数 c を非公開とし、代わりに c の値を参照するための関数を新たに付け加えてみましょう。 モジュール変数や副プログラムの公開・非公開を変更するには public 属性と private 属性を使います。

!------------------------------------------------------------
! カウンターモジュール(改良版)
!------------------------------------------------------------
module counter2
  implicit none
  private

  ! 公開する副プログラム
  public ::                     &
       & inc,                   &
       & dec,                   &
       & getc

  ! private モジュール変数
  integer, save :: c = 0

contains
  ! c の値を 1 増やす
  subroutine inc()
    implicit none
    c = c + 1
    return
  end subroutine inc

  ! c の値を 1 減らす
  subroutine dec()
    implicit none
    c = c - 1
    return
  end subroutine dec

  ! c の値を返す関数
  integer function getc()
    implicit none
    getc = c
    return
  end function getc
end module counter2

!------------------------------------------------------------
! メインプログラム
!------------------------------------------------------------
program work0803
  use counter2
  implicit none

  write(*,*) getc()

  call inc()
  write(*,*) getc()

  call inc()
  write(*,*) getc()

  call dec()
  write(*,*) getc()

  stop
end program work0803

public と private

型宣言に public 属性と private 属性を付加するとモジュール変数の外部への公開・非公開を制御することができます。

real(8), save, private :: a, b     ! 非公開
real(8), save, public  :: x, y     ! 公開

モジュール副プログラムの公開・非公開を制御するには public 文と private 文を使います。 この形式でモジュール変数を制御することも可能です。

public :: inc, dec
private :: c

特に指定しなければモジュール変数と副プログラムはすべて public として扱われます。 デフォルトの動作を変更するには、引数なしで private 文を用います。

private                            ! デフォルトを private に変更
real(8), save          :: a, b     ! 非公開
real(8), save, public  :: x, y     ! 公開

モジュールの用途の限定

モジュールによる変数の共有は便利ですが、値が変更される場所やタイミングが分かりにくくなるという問題点もあります。 気象庁で使われている Fortran 標準コーディングルールでは、モジュールの利用方法を次の3つの用途に限定することが提案されています。 必ずしもこの提案に従う必要はありませんが、モジュールは一定のルールのもとで利用しないと、かえってプログラムの見通しが悪くなるということでしょう。

定数参照型モジュール

定数を参照するためのモジュール。

変数参照型モジュール

変数を参照するためのモジュール。 モジュール変数の値の変更には別途、一定のルールを設ける。

パッケージ型モジュール

パッケージを扱うためのモジュール。 パッケージとは、高速フーリエ変換など、ある機能をもったサブルーチン群のことです。

スタックモジュール

スタック (stack) とは「最後に入れた値が一番先に取り出される」という特徴をもつデータ構造の一種です。 値をスタックに入れる操作をプッシュ (push)、値をスタックから取り出す操作をポップ (pop) といいます。

例として、整数を要素をスタックを考えましょう。 [2,3,5] というスタックに対して 6 をプッシュするとスタックは [2,3,5,6] に変化します。 逆に、[2,3,5] というスタックに対してポップを行うと、最後の要素 5 が返され、スタックは [2,3] に変化します。

整数を要素とするスタックは、十分な長さの整数配列と、一番最後の要素の位置(スタックに納められたデータの個数)を記憶しておく整数があれば実現できます。 以下の stack モジュールでは、スタックを表現する整数配列 buf と、最後の要素の位置を記憶する整数 p をモジュール変数とし、プッシュとポップを行うサブルーチン push と pop とスタックの内容を表示するサブルーチン print_stack をモジュール副プログラムとしています。

!------------------------------------------------------------
! スタックモジュール
!------------------------------------------------------------
module stack
  implicit none
  private

  public ::                     &
       & push,                  &
       & pop,                   &
       & print_stack

  ! モジュール変数 (private)
  integer, parameter :: pmax = 512 ! スタックの最大長
  integer, save :: buf(1:pmax)     ! スタック配列
  integer, save :: p = 0           ! 最後(トップ)の要素の位置

contains

  ! スタックに n をトップノードとして付け加える
  subroutine push(n)
    implicit none
    integer, intent(IN) :: n

    if (p < pmax) then
       p = p + 1
       buf(p) = n
    else
       ! これ以上スタックに格納できない場合
       write(*,*) 'Stack overflow!'
    end if

    return
  end subroutine push

  ! スタックからトップノードを取り出す
  integer function pop()
    implicit none

    if (p > 0) then
       pop = buf(p)
       p = p - 1
    else
       ! スタックが空の場合
       write(*,*) 'Stack underflow!'
       pop = - huge(buf)
    end if

    return
  end function pop

  ! スタックの内容を表示する
  subroutine print_stack()
    implicit none

    write(*,'(10I5)') buf(1:p)

    return
  end subroutine print_stack

end module stack

!------------------------------------------------------------
! メインプログラム
!------------------------------------------------------------
program work0804
  use stack
  implicit none
  integer :: n

  call push(1)                  ! [] => [1]
  call print_stack()
  call push(2)                  ! [1] => [1,2]
  call print_stack()
  call push(3)                  ! [1,2] => [1,2,3]
  call print_stack()
  n = pop()                     ! [1,2,3] => [1,2]
  write(*,'(A,I5)') 'pop value:', n
  call print_stack()

  stop
end program work0804

プログラムを実行すると次のような結果が得られます。

% ./a.out
    1
    1    2
    1    2    3
pop value:    3
    1    2

本日の課題

TBD