オブジェクト指向Fortranで抽象型を使う

Fortran2003からオブジェクト指向がサポートされたFortranですが、何ができて何ができないのかがよくわからない印象です。この記事では、抽象型を使う方法についてメモを書いておきます。
オブジェクト指向Fortranについては[こちら](https://qiita.com/cometscome_phys/items/f87080286c6fdf72e49a)を参考にしてください。

# 抽象型とは
Fortran2003の抽象型は

```fortran
    type,abstract::animal
    end type
```
こんな感じで```abstract```をつけて宣言します。abstractをつけると、この型はインスタンスを作ることができなくなります。つまり、実際の計算では常に何らかの具象型が生成されています。この場合では、

```fortran
    type,extends(animal)::dog
        integer::dogage
    end type

    type,extends(animal)::cat
        integer::catage
    end type
```
dogとcatという具象型を定義します。ここでextendsをつけると派生型の意味になるのですが、animalが抽象型なのでdogとcatはanimalsに対する具象型となります。
型、とここで言っていますが、実際はclassです。ですので、

```fortran
    interface dog
        module procedure::init_dog
    end interface

    interface cat
        module procedure::init_cat
    end interface

contains 

    type(dog) function init_dog(age) result(d)
        integer::age
        d%dogage = age
    end function

    type(cat) function init_cat(age) result(c)
        integer::age
        c%catage = age
    end function
```
とコンストラクタを作っておきましょう。

# 抽象型を含むクラス
このままだとご利益があまりわかりませんので、以下のような型を用意します。

```fortran
    type life
        class(animal),allocatable::ianimal
        class(food),allocatable::ifood
    end type
```
このlifeという型（というかクラス）は成分としてanimal型とfood型という二つの抽象型をもっています。実際の計算では、このlifeという型にはdogあるいはcatが入ったりして、dogかcatかで挙動が変わったりするわけです。
ここで注意点（私が引っかかった点）は、

```fortran
    type life
        class(animal)::ianimal
        class(food)::ifood
    end type
```
はコンパイルエラーがおきます。これがよくわからなくて、抽象型をうまく使う方法がわからなかったのでした。しかし、考えてみると、どの型になるかわからないから抽象型ですから、具体的なデータを格納するためにはどのような型であるかを知る必要があり、「割付」つまりallocateが必須なわけです。
このlifeのコンストラクタは

```fortran
    interface life
        module procedure::init_life
    end interface

contains
    type(life) function init_life(str) result(l)
        character(len=*)::str
        if (str == "dog") then
            allocate(dog::l%ianimal)
            allocate(dogfood::l%ifood)
            l%ianimal = dog(4)
            l%ifood = dogfood(10)

        else if(str == "cat") then
            allocate(cat::l%ianimal)
            allocate(catfood::l%ifood)
            l%ianimal = cat(10)
            l%ifood = catfood(2)
        else
            write(*,*) "we need cat or dog!"
        end if
    end
```
こんな感じにしました。もしstrという文字列がdogなら型はdogだよとallocateし、catなら型はcatとallocateします。この```allocate(dog::l%ianimal)```がポイントで、```l%ianimal```は```dog```型だよ、と宣言しています。

# 全体のコード
全体のコードはこんな感じです。

```fortran
module test
    implicit none

    type life
        class(animal),allocatable::ianimal
        class(food),allocatable::ifood

        contains
        procedure::showdata
    end type

    type,abstract::animal
    end type

    type,extends(animal)::dog
        integer::dogage
    end type

    interface dog
        module procedure::init_dog
    end interface

    type,extends(animal)::cat
        integer::catage
    end type

    interface cat
        module procedure::init_cat
    end interface

    type,abstract::food
        integer::num
    end type

    type,extends(food)::dogfood
    end type

    type,extends(food)::catfood
    end type

    interface life
        module procedure::init_life
    end interface

    interface dogfood
        module procedure::init_dogfood
    end interface

    interface catfood
        module procedure::init_catfood
    end interface

    contains 

    subroutine showdata(self)
        class(life)::self
        select type(p => self%ifood)
            type is (dogfood)
                write(*,*) "num of dogfoods is ",p%num
            type is (catfood)
                write(*,*) "num of catfoods is ",p%num
        end select

        select type(p => self%ianimal)
            type is (dog)
                write(*,*) "dog's age is ",p%dogage
            type is (cat)
                write(*,*) "cat's age is ",p%catage
        end select

    end subroutine


    type(dog) function init_dog(age) result(d)
        integer::age
        d%dogage = age
    end function

    type(dogfood) function init_dogfood(num) result(d)
        integer::num
        d%num = num
    end function

    type(cat) function init_cat(age) result(c)
        integer::age
        c%catage = age
    end function

    type(catfood) function init_catfood(num) result(c)
        integer::num
        c%num = num
    end function

    type(life) function init_life(str) result(l)
        character(len=*)::str
        if (str == "dog") then
            allocate(dog::l%ianimal)
            allocate(dogfood::l%ifood)
            l%ianimal = dog(4)
            l%ifood = dogfood(10)

        else if(str == "cat") then
            allocate(cat::l%ianimal)
            allocate(catfood::l%ifood)
            l%ianimal = cat(10)
            l%ifood = catfood(2)
        else
            write(*,*) "we need cat or dog!"
        end if
    end

end module

program main
    use test
    implicit none
    type(life)::life1,life2

    life1 = life("dog")
    call life1%showdata()

    life2 = life("cat")
    call life2%showdata()

end program
```
実行すると、

```
 num of dogfoods is           10
 dog's age is            4
 num of catfoods is            2
 cat's age is           10
```
と出力されます。ちゃんとlifeという変数にdogやcatが入っていることがわかります。