Fortran における構造化　抽象データ構造の実現

# Fortran における構造化と抽象データ構造(Abstruct Data Type)の実現

プログラムの構造化は、制御の構造化、データの構造化、抽象データ構造化（制御とデータの一体化）に分けられると前の記事（[こちら](https://qiita.com/cure_honey/items/789c57a1b7b142353e5e)）で述べました。この記事では、Fortran での抽象データ構造の実現法について概観します。

## Fortran における抽象データ構造の実現方法

抽象データ構造とは、データ変数とそれを操作するサブルーチン・関数を一体として考えるもので、利用者側にはデータ変数の詳細は明かされず、それに対して許される操作のインターフェースのみが明かされることになります。「情報隠蔽（Encapsulation）」と言われるものです。

情報隠蔽の正否についてにわかには判断はつきませんが、F. P. Brooks の IBM System/360 の開発について記した有名な本『人月の神話』 (F. P. Brooks, *The Mythical Man-Month: Essays on Software Engineering*, (1975), Addison-Wesley) が参考になります。第７章で Brooks は全員がすべての詳細を知るべきと考え、D. L. Parnas の情報隠蔽の考え方を全否定しています。そして過剰な情報の波にのまれていく様子が描かれています。しかし20年後に出された改訂版の第１８章には、後に Parnas と直接話したことですっかり考えを改めたと書かれています。

抽象データ構造の実現法としては、オブジェクト指向プログラミング（OOP）がよく取り上げられますが、それ以外にもさまざまなアプローチがあります。データとプロシージャ（サブルーチン・関数）の結びつきの疎密によって分類することが可能です。

1. Fortran 90 で導入された Module を用いて、疎結合の極限を実現できます。データとプロシージャは同じプログラム単位に記述されていますが、呼び出し法などは従来と変わりません。
2. Fortran 2003 で導入されたオブジェクト指向 (OO) は、密な結合の極限にあたります。データとプロシージャは静的に結び付けられており、全てのインスタンスでプロシージャは共通になります。継承に応じた動的な変化は多態のような仕組みを用います。
3. その中間的なものとして、type-bound procedure を procedure pointer を使って、動的にデータと結び付ける方法があります。この場合はインスタンス毎にプロシージャを変えられます。

Fortran には、このどのやり方も実現することができる文法が用意されています。以下にその実例を挙げます。

## 実際の例

### 1. 疎結合モデル： Module による

Fortranでは `module` を用いることで、データと手続きの疎結合が可能です。`module` にデータ型や関数をまとめることで、異なるプログラム部品間で依存を最小化しつつ、コードの再利用を促進することができます。

```fortran
module SparseModule
    implicit none
    type :: SparseData
        integer :: id
        real :: value
    end type SparseData

contains
    subroutine print_sparse(data)
        type(SparseData), intent(in) :: data
        print *, 'ID:', data%id, ' Value:', data%value
    end subroutine print_sparse
end module SparseModule

program main
    use SparseModule
    implicit none
    type(SparseData) :: data_instance

    data_instance%id = 1
    data_instance%value = 3.14

    call print_sparse(data_instance)
end program main
```

このように、モジュールを使うことでデータ構造と手続きを明確に分離し、疎結合を実現できます。

### 2. 中間結合モデル： type-bound procedure による

Fortran では継承を使わずに、`type-bound procedure` の pointer（関数ポインタ）を利用して、柔軟なデータ構造を作成することが可能です。これにより、異なる振る舞いを持つオブジェクトを扱うことができます。

```fortran
module FunctionPointerModule
    implicit none

    type :: FunctionPointerData
        integer :: id
        procedure(print_proc), pointer :: print => null()
    end type FunctionPointerData

    abstract interface
        subroutine print_proc(this)
            import :: FunctionPointerData
            class(FunctionPointerData), intent(in) :: this
        end subroutine print_proc
    end interface

contains
    subroutine print_implementation(this)
        class(FunctionPointerData), intent(in) :: this
        print *, 'ID:', this%id
    end subroutine print_implementation
end module FunctionPointerModule

program main
    use FunctionPointerModule
    implicit none
    type(FunctionPointerData) :: data_instance

    data_instance%id = 2
    data_instance%print => print_implementation

    if (associated(data_instance%print)) then
        call data_instance%print()
    endif
end program main
```

この例では、継承なしに type-bound procedure を使って、異なる振る舞いをデータ型に結びつけることができます。このアプローチにより、柔軟で拡張可能な設計が可能となります。もちろん継承を使うことも可能なので、問題に応じて結合度を変えることができます。

type と type-bound procedure pointer の間に循環参照が生じるので、定義の import が必要になります。 

### 3. 密結合モデル： OOP の CLASS による

Fortran 2003以降では、`CLASS` を使ったオブジェクト指向プログラミングが可能になり、密結合なデータモデルを作成することができます。このモデルでは、派生型と多態性を用いることで、オブジェクト間に強い結合性を持たせています。ここでは抽象クラスと、抽象インターフェースも使ってみました。

オブジェクト指向は、名詞的と称され、データ変数をプロシージャより重視するものと言われたりもします。しかし抽象度が上がると、データの詳細は隠され、まず動作（メソッド）のみが列挙的に定義されます。これが抽象クラスにあたります。

```fortran
module OOPModule
    implicit none
  
    type, abstract :: Object
    contains
        procedure(aprint), deferred :: print
    end type Object
     
    abstract interface
        subroutine aprint(this)
            import
            implicit none
            class(Object), intent(in) :: this
        end subroutine aprint
    end interface
  
    type, extends(Object) :: BaseData
        integer :: id
    contains
        procedure :: print => base_print
    end type BaseData

    type, extends(BaseData) :: DerivedData
        real :: value
    contains
        procedure :: print => derived_print
    end type DerivedData
  
contains  
    subroutine base_print(this)
        class(BaseData), intent(in) :: this
        print *, 'Base ID:', this%id
    end subroutine base_print
    
    subroutine derived_print(this)
        class(DerivedData), intent(in) :: this
        print *, 'Derived ID:', this%id, ' Value:', this%value
    end subroutine derived_print
end module OOPModule

program main
    use OOPModule
    implicit none
    class(BaseData), allocatable :: obj
    type(DerivedData) :: derived_instance

    derived_instance%id = 3
    derived_instance%value = 1.618
    obj = derived_instance

    call obj%print()
end program main
```

ここでは、`CLASS` と継承を用いた密結合モデルを示しています。この方式では、多態性を活用し、異なる型のオブジェクトを同じインターフェースで扱うことができます。密結合のため、オブジェクト間の関係が強くなり、コード全体の構造が把握しやすくなるという特徴があります。

## まとめと結論

本記事では、Fortran における抽象データ構造の実現法を、データとプロシージャの結びつきの疎密度から三つの場合に分けて紹介しました。

それは、1. 疎結合の極限としての Module 2. 中間結合としての type-bound procedure pointer 3. 密結合の極限としての class による OOP です。

伝統的な Fortran は、疎結合として理解できると考えられます。一時期はオブジェクト指向ではないからということで、だいぶ肩身の狭い思いをさせられました。「オブジェクト指向に非ずば人に非ず」式にオブジェクト指向がもてはやされた時期がありましたが、2010 年代からはオブジェクト指向プログラミングに批判が高まっているように感じます。最近の流行りは中間結合的な方法に類するものに見受けられます。継承を使わず必要に応じてデータ型とプロシージャを結び付ける方法を、他言語は様々な形で実現しています。

なお、N. Wirth は 1990 年の以下の論文において、オブジェクト指向を批判的に論じ、procedure pointer を用いての中間結合的な方法の利点を述べています。
N. Wirth, From Modula to Oberon: The Programming Language Oberon. Information Processing Letters, 14(3), 193-204. (1990).

強調したいのは、抽象データ構造を実現する場合に、これらの結合様式の内のどれかが優れていて他が劣っているというものではないことです。さらに言えば、これらの文法要素を使えば必ず抽象データ構造が実現されるわけではなく、また逆に必ずしも抽象データ構造を構成しなければならないわけでもないことです。あくまで自分の問題の特質に応じて、より適切な方法を選べばよいのだと思います。

## 付記

### 倒置法

OOP や中間結合方式で type-bound procedure を用いた呼び出しを行う場合、語順が少し入れ替わりますが、これは自然言語の文法では倒置法にあたる構文に相当するとみなせます。（命令文なので主語の S は無いとして）V + O1 + O2 が O1 + V + O2 と目的語が先頭に飛び出すようなものです。

```
call sub(this, O2, ...)
```

を

```
call this%sub(O2, ...)
```

と書き換えることになります。Fortran の場合 call という動詞が付きますがw

### ChatGPT

この記事も ChatGPT 4o with canvas を用いて書きました。Version up したせいか、先週よりも利口になった気がします。Fortran のコードはほぼ自動生成されたものです。Intel Fortran で実行できるように、わずかに修正を加えました。

Canvas は日本語変換と相性が悪くて色々苦労させられました。また文章の末尾の方がよく消えます。文章を勝手に書き換えるので、時々困りました。バージョン管理も出鱈目です。しかし全体として見ると、驚異的な代物と感じました。