Fortran2003からオブジェクト指向がサポートされたFortranですが、何ができて何ができないのかがよくわからない印象です。この記事では、抽象型を使う方法についてメモを書いておきます。
オブジェクト指向Fortranについてはこちらを参考にしてください。
抽象型とは
Fortran2003の抽象型は
type,abstract::animal
end type
こんな感じでabstract
をつけて宣言します。abstractをつけると、この型はインスタンスを作ることができなくなります。つまり、実際の計算では常に何らかの具象型が生成されています。この場合では、
type,extends(animal)::dog
integer::dogage
end type
type,extends(animal)::cat
integer::catage
end type
dogとcatという具象型を定義します。ここでextendsをつけると派生型の意味になるのですが、animalが抽象型なのでdogとcatはanimalsに対する具象型となります。
型、とここで言っていますが、実際はclassです。ですので、
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
とコンストラクタを作っておきましょう。
抽象型を含むクラス
このままだとご利益があまりわかりませんので、以下のような型を用意します。
type life
class(animal),allocatable::ianimal
class(food),allocatable::ifood
end type
このlifeという型(というかクラス)は成分としてanimal型とfood型という二つの抽象型をもっています。実際の計算では、このlifeという型にはdogあるいはcatが入ったりして、dogかcatかで挙動が変わったりするわけです。
ここで注意点(私が引っかかった点)は、
type life
class(animal)::ianimal
class(food)::ifood
end type
はコンパイルエラーがおきます。これがよくわからなくて、抽象型をうまく使う方法がわからなかったのでした。しかし、考えてみると、どの型になるかわからないから抽象型ですから、具体的なデータを格納するためにはどのような型であるかを知る必要があり、「割付」つまりallocateが必須なわけです。
このlifeのコンストラクタは
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
型だよ、と宣言しています。
全体のコード
全体のコードはこんな感じです。
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が入っていることがわかります。