6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FortranのGenerics (template)の状況とそれに対する要望

Last updated at Posted at 2023-05-07

概要

ポエムです.

現状はこの記事の内容から変わっていたので,追加で記事を書きました.(2023年5月8日追記)

背景

Fortranでは,次(Fortran 2023)の次(Fortran 202Y)の規格にGenericsを入れる方向で検討が進んでおり,lfortranコンパイラにexperimental実装が追加されています.また,experimental実装を試してみたという記事も公開されています.

このtemplateの実装について,実際のFortranユーザの意見を聞くために,モダンFortran勉強会を開催しました.

現在Fortran202yでの導入に向けて仕様がされているgenericsについて、規格策定に携わっている日本の方から現状の仕様をご紹介いただきます。その後、参加者の皆様から、様々な目線で利用用途に合っているか等を議論する予定です。 検討段階の仕様は、lfortranにexperimentalとして実装されているので、動かしながら議論を深めていきます。

非常に熱い議論が行われ,予定時間を1時間以上超過するほどでした.

その議論の中で,個人的に感じたこと,個人的にこのように書きたいという要望を残すことにしました.

templeteの現状の例

lfortranに実装されているtemplate_addを例に,現状の仕様を確認してみます.(仕様自体はまだ文書化されていません)

template_add
module template_add_m
    implicit none
    private
    public :: add_t

    requirement R(T, F)
        type :: T; end type
        function F(x, y) result(z)
            type(T), intent(in) :: x, y
            type(T) :: z
        end function
    end requirement

    template add_t(T, F)
        requires R(T, F)
        private
        public :: add_generic
    contains
        function add_generic(x, y) result(z)
            type(T), intent(in) :: x, y
            type(T) :: z
            z = F(x, y)
        end function
    end template

contains

    real function func_arg_real(x, y) result(z)
        real, intent(in) :: x, y
        z = x + y
    end function

    integer function func_arg_int(x, y) result(z)
        integer, intent(in) :: x, y
        z = x + y
    end function

    subroutine test_template()
        instantiate add_t(real, func_arg_real), only: add_real => add_generic
        real :: x, y
        integer :: a, b
        x = 5.1
        y = 7.2
        print*, "The result is ", add_real(x, y)
        if (abs(add_real(x, y) - 12.3) > 1e-5) error stop

        instantiate add_t(integer, func_arg_int), only: add_integer => add_generic
        a = 5
        b = 9
        print*, "The result is ", add_integer(a, b)
        if (add_integer(a, b) /= 14) error stop
    end subroutine
end module

program template_add
use template_add_m
implicit none

call test_template()

end program template_add

まず,template内で参照される型と関数(いわゆるテンプレートパラメータ)を,requirementとして記述します.

requirement R(T, F)
    type :: T; end type
    function F(x, y) result(z)
        type(T), intent(in) :: x, y
        type(T) :: z
    end function
end requirement

その後,テンプレート化したい関数を,template構文の中に記述します.ここで,先ほどrequirementととして記述したRrequaireによって取り込みます.add_genericの引数と戻り値の型,add_generic内部で呼んでいる関数Fのインタフェースが取り込まれます.

template add_t(T, F)
    requires R(T, F)
    private
    public :: add_generic
contains
    function add_generic(x, y) result(z)
        type(T), intent(in) :: x, y
        type(T) :: z
        z = F(x, y)
    end function
end template

テンプレート関数が定義されたら,テンプレートパラメータに具体的な型や関数を与えて実体化します.現状では,instantiate文を用いることが想定されています.インスタンス化の処理について,私は

  • template add_t(T, F)のコンストラクタadd_tに型realと関数func_arg_realを与えてインスタンス化
  • その中で実体化されたadd_genericに対してadd_realという別名を与えている

と解釈しました.

instantiate add_t(real, func_arg_real), only: add_real => add_generic
real :: x, y
x = 5.1
y = 7.2
print*, "The result is ", add_real(x, y)

integer型の和を計算するように実体化するには,下記のようにしています.

instantiate add_t(integer, func_arg_int), only: add_integer => add_generic
integer :: a, b
a = 5
b = 9
print*, "The result is ", add_integer(a, b)

func_arg_realfunc_arg_intは,instantiateする場所から見えるスコープで,下記のように定義されています.

real function func_arg_real(x, y) result(z)
    real, intent(in) :: x, y
    z = x + y
end function

integer function func_arg_int(x, y) result(z)
    integer, intent(in) :: x, y
    z = x + y
end function

例に対する私の感想

この例を見たとき,「C++のテンプレートと比較すると異様に複雑だ」という感想を持ちました.そもそも,func_arg_realfunc_arg_intのように,“演算内容は同じだけと型(や配列次元)だけが異なる処理”を書きたくないからtemplateを使いたいのであって,この例のようにインタフェースを定義して,そのインタフェースに沿った実装を書かないといけないのであれば,総称名でいいんじゃないかと思います.

あくまでこれは例であって,requirementに関数は必要なく,下記のように書くことが想定されていれば良いのですが…(Fortranらしく記述量が多いとは思いますが)

template add_t(T)
    requires R(T)
    private
    public :: add_generic
contains
    function add_generic(x, y) result(z)
        type(T), intent(in) :: x, y
        type(T) :: z
        z = x + y
    end function
end template

上記のように定義したテンプレートでは,lfortranで実行すると正しく加算が計算されません.少し心配です.

C++であれば,(テンプレートはいくらでも複雑にできることは知っているものの)この足し算を行う例は,もっと簡単に書けます.

template <typename T>
T add(T x, T y)
{
    return x + y;
}

呼び出す場合にも,明示的なインスタンス宣言は必要ありません.今時は引数からTを類推してくれるようで,<>を使ったインスタンス化すら必要ではないようです.

    int a=5, b=9;
    float x = 5.1, y=7.2;
    int c = add<int>(a, b);
    float z = add<float>(x,y);

experimental実装に対応させれば,template <typename T>に対応するのがrequirement

requirement R(T)
    type :: T; end type
end requirement

テンプレート関数定義

T add(T x, T y)
{
    return x + y;
}

に対応するのがtemplate構文

template add_t(T)
    requires R(T)
    private
    public :: add_generic
contains
    function add_generic(x, y) result(z)
        type(T), intent(in) :: x, y
        type(T) :: z
        z = x + y
    end function
end template

なのだろうと判断できます.

もう少し簡単にならないものでしょうか?Fortran規格のGenericsを検討するチームは,(勉強会にも参加していただいた)HPCチームからの「もっと簡略化できないか?」との質問に対して,「C++のテンプレートの問題を踏まえてこのような記述にした」と回答したとのことです.

C++は,templateがらみで非常に大量のエラーを出すことが知られています.それを発生させないために,このような記述が要求されているのでしょうか?例えば,z = x + yのように記述したとき,x, y+演算が定義されていない型だった場合にエラーを出さないといけません.先ほどのadd<T>のテンプレート型パラメータとしてstd::pair<int, int>を与えてコンパイルしてみると,大量のメッセージが表示されます.

std::cout << add<std::pair<int, int>>(std::make_pair(1,1),std::make_pair(2,2)) << std::endl;

こういう問題を避けるために,x, yに対する+演算はユーザの責任で定義し,インスタンス化する際にテンプレートパラメータとして渡すことを強制しているのでしょか?そうだとすると,個人的には利便性を犠牲にしすぎているような気がします.

私は,parameterized derived typeの記述を拡張しつつ,CUDA Fortranのattributeを取り込んで,

attribute(template(T)) add(x, y) result(z)
    ! ここでテンプレート型パラメータTを定義する.
    ! character(*), type :: Tとか,template, type :: Tとか
    type(T), intent(in) :: x, y
    type(T) :: z
    z = x + y
end add

! インスタンス化
instantiate add_i => add(integer)
c = add_i(a, b)
! あるいは直接呼び出し
c = add(integer)(a, b)
! 内部手続や総称名のように,引数から自動で型パラメータを推定
c = add(a, b)

のように書けるとうれしいと思っています.あとは,スカラか配列かの区別もなくせるようになると,うれしいですね.

勉強会の中では,そもそもテンプレートライブラリを作ったとして,どのように計算機性能を引き出すのかが見えないというコメントもありました.テンプレートに具体的な型を当てはめて関数を生成するのは,コンパイラがコンパイル時に行います.そうすると,ユーザの指定したコンパイルオプションに合わせたコードを生成したり,ユーザが実行する計算機で高い実効性能を出そうとしたりすると,ソースを提供して,ユーザが作成したプログラムと共にコンパイルをしなければならないのではないか?という危惧です.他の言語ではどうしているのでしょうか?ご存じの方がいれば,ぜひ教えてください.

まとめ

私の要望は2点

  • 異なる型,同じ演算をする関数を複数回書かせるのは止めてほしい
  • 最悪instantiateが必要だとしても,テンプレートの定義はもう少し簡単にしてほしい
6
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?