オブジェクト指向
fortran2008
Fortan

複数の引数を持つ関数を一つの引数の関数として関数等から呼びたいin Fortran

Fortranで、一変数関数f(x)を引数とする関数g(f)があった時、このgの中にf2(x,y)のyを固定したものf2(x,10)等を入れる方法についてです。多分Fortran2008からできるらしいです。

https://stackoverflow.com/questions/31208594/how-to-pass-a-function-with-multiple-arguments-to-a-subroutine-that-expects-a-fu/31208717#31208717

これを応用すると、オブジェクト指向で作ったオブジェクトに付随する関数も引数として呼べるようになります。


バージョン

gfortran: gcc version 8.2.0 (Homebrew GCC 8.2.0)


コード

Fortranで以下のmoduleを考えます。

module test

contains
subroutine tasu(a)
implicit none
real(8),intent(in)::a
write(*,*) "a+10=",a+10
end subroutine tasu

subroutine kakeru(a)
implicit none
real(8),intent(in)::a
write(*,*) "a*10=",a*10
end subroutine kakeru

subroutine keisan(tasuorkakeru)
interface
subroutine tasuorkakeru(a)
real(8),intent(in)::a
end subroutine
end interface
real(8)::a,b

a = 100
call tasuorkakeru(a)

a = 1000
call tasuorkakeru(a)

end subroutine keisan

このmoduleのkeisanというsubroutineは、引数に関数を入れることができます。

例えば、

program main

use test
implicit none

call keisan(tasu)
call keisan(kakeru)

end program

 a+10=   110.00000000000000     

a+10= 1010.0000000000000
a*10= 1000.0000000000000
a*10= 10000.000000000000

というような形で、関数を切り替えて使えるわけです。

ここで入れられる関数は一変数関数だけです。なので、

    subroutine tasu_a(a,b)

implicit none
real(8),intent(in)::a,b
write(*,*) "a+b=",a+b
end subroutine tasu_a

という関数は

call keisan(tasu_a)

と入れることはできません。

しかし、bの値を固定してxの関数として引数に使いたいこともあるかと思います。

そのような場合には、

containsを使って中にラッパー関数を定義すると、bの値を固定した関数を作ることができます。

つまり、

module test

contains
subroutine tasu(a)
implicit none
real(8),intent(in)::a
write(*,*) "a+10=",a+10
end subroutine tasu

subroutine kakeru(a)
implicit none
real(8),intent(in)::a
write(*,*) "a*10=",a*10
end subroutine kakeru

subroutine tasu_a(a,b)
implicit none
real(8),intent(in)::a,b
write(*,*) "a+b=",a+b
end subroutine tasu_a

subroutine keisan(tasuorkakeru)
interface
subroutine tasuorkakeru(a)
real(8),intent(in)::a
end subroutine
end interface
real(8)::a,b

a = 100
call tasuorkakeru(a)

a = 1000
call tasuorkakeru(a)

end subroutine keisan
end module test

program main
use test
implicit none
real(8)::b

call keisan(tasu)
call keisan(kakeru)

b = 100d0
call keisan(wrapper)
b = 20d0
call keisan(wrapper)

contains
subroutine wrapper(a)
real(8),intent(in)::a
call tasu_a(a,b)
end subroutine

end program

とすれば、出力は

 a+10=   110.00000000000000     

a+10= 1010.0000000000000
a*10= 1000.0000000000000
a*10= 10000.000000000000
a+b= 200.00000000000000
a+b= 1100.0000000000000
a+b= 120.00000000000000
a+b= 1020.0000000000000

となり、ちゃんとbを固定した関数ができていることがわかります。


オブジェクトに付随する関数を引数としたい

以下のようなFortran2003以降でできるようになったオブジェクト指向なFortranコードを用意しました。

module test2

implicit none
type testtype
real(8)::a
contains
procedure::plus => plus1
end type testtype

contains

subroutine plus1(self,a,b)
class(testtype)::self
real(8),intent(in)::a
real(8),intent(out)::b
write(*,*) "self%a",self%a
b = self%a*2+a
write(*,*) "plus1"
return
end subroutine

subroutine plus0(a,b)
real(8),intent(in)::a
real(8),intent(out)::b
b = a*2
write(*,*) "plus0"
end subroutine

subroutine matome(func)
interface
subroutine func(a,b)
implicit none
real(8),intent(in)::a
real(8),intent(out)::b
end
end interface
real(8)::a,b
a = 10d0
b = 0d0
call func(a,b)
write(*,*) b
end subroutine
end module

ここで定義したplus1(self,a,b)はtype testtypeに付随するようにしたので、

program main

use test2
implicit none
type(testtype)::kk
real(8)::a,b

kk%a = 5d0
a = 15d0
call kk%plus(a,b)
write(*,*) b

call plus0(a,b)
write(*,*) b
end program main

 self%a   5.0000000000000000     

plus1
25.000000000000000
plus0
30.000000000000000
plus0
20.000000000000000

のように、type testtypeの変数kkのsubroutineとしてkk%plus(a,b)で呼べるようになっています。

この引数は見た目二つなので、plus0(a,b)を引数にしたkeisan(plus0)のように、keisan(kk%plus)とできそうに見えます。

call matome(kk%plus)

しかし、これは

     call matome(kk%plus)

1
Error: Expected argument list at (1)

というエラーが出てしまってコンパイルできません。

これではオブジェクトに付随した関数は引数にできません。

これを解決するには、



program main
use test2
implicit none
type(testtype)::kk
real(8)::a,b

kk%a = 5d0
a = 15d0
call kk%plus(a,b)
write(*,*) b

call plus0(a,b)
write(*,*) b

call matome(plus0)
call matome(wrapper)

kk%a = 25d0
call matome(wrapper)

contains

subroutine wrapper(a,b)
real(8),intent(in)::a
real(8),intent(out)::b
call kk%plus(a,b)
return
end subroutine

end program main

 self%a   5.0000000000000000     

plus1
25.000000000000000
plus0
30.000000000000000
plus0
20.000000000000000
self%a 5.0000000000000000
plus1
20.000000000000000
self%a 25.000000000000000
plus1
60.000000000000000

のようにラッパー関数をつけてやればよいです。これによって、testtypeの中身が変わればちゃんと値が変わるようになりました。