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の中身が変わればちゃんと値が変わるようになりました。