Fortran Advent Calendar 2024 の 19 日目の記事です。 Fortran 入門の三歩手前ぐらいの内容です。

背景

モチベーション

競プロ界で Fortran を代表する人類に MrTired がいます。彼/彼女の提出プログラムの頭には『ランダムウォーク猿』を始めとした乱数キャラが登場したり、さり気なく Discord で僕の懸念事項を質問してくれたりと、ユーモラスで素敵な方です。好きだ!

すべての言語を使いこなしたい、という意味で僕は Fortran に興味がありますが、ちゃんと MrTired のコードを読んだり、もう少しお近づきしたいなー、という気持ちもあって、普通の人よりはモチベーションが高いと思います。

やったみたこと

興味はありつつも、 Fortran 入門には二の足を踏んでいます。 みんなの Fortran を購入して読んでいなかったり、 Emacs の環境構築をしたり、

2024-12-19-emacs.png
Figure 1: Emacs

AtCoder の一番簡単な問題 を解いてみましたが、その後の進捗がありません。エアプで良いので、もう少し理解を進める手立ては無いものでしょうか?

program ABC086A
    use, intrinsic :: iso_fortran_env
    implicit none

    integer(int64) :: a, b
    read (input_unit, *) a, b

    if (mod(a*b, 2_int64) == 0_int64) then
        write (output_unit, '(A)') 'Even'
    else
        write (output_unit, '(A)') 'Odd'
    end if
end program ABC086A

Fortran 学習へのアプローチ……の検討

Fortran の学習曲線が急なのは間違い無いと思います。学習過程なので寛容に捉えて欲しいのですが、僕は以下の辺りに引っかかっています。

構文の難しさ

プログラム全体の構造がなかなか頭に定着しません。歴史的経緯が色々ありそうです。

program ABC086A
    use, intrinsic :: iso_fortran_env ! 1
    implicit none
    ! ~~
end program ABC086A

変数の宣言もなんだかピンと来ません。

integer(int64) :: a, b ! 2

標準入出力も構文が謎です。関数呼び出しの普遍的な構文ではなく、 read/write 文の専用構文が用意されている雰囲気があります。どうして関数呼び出しじゃないんだ! と悩んでいる間は集中力 50% 減です。これが慣れない言語の難しさですね。

read (input_unit, *) a, b ! 3
write (output_unit, '(A)') 'Even' ! 4

そして文字列が出てくるとノックアウトされます。

character(len=3) :: s ! 5

Fortran の revision によって関数や変数の型が変わるっぽいのも難しい。 Fortran 2018 では mod 関数の 2 引数の typekind がマッチする必要があります。たぶん。要は型が一致する必要があります。

mod(4_int64, 2_int32) ! コンパイルエラー
mod(4_int64, 2_int64) ! OK

ところで比較は型が合っていなくても良さそうです。どうしてここではキャストを強要しないのか……?!

mod(4_int64, 2_int64) == 0_int32

いずれも僕の練習不足を表すもので、 50 時間ぐらい Fortran を書けばあっさり慣れると思いますが、今はあぐらをかいたまま前に進みたい。そこで思いつきました。 AST だ!

AST を見てみよう

LFortran Design を覗いてみると、 AST に言及があります。 AST は単純に文法をパースしたものらしいので、これを見れば一通り文法を把握できるはずです。

AST.asdl にて文法が定義されており、これを元に <lfortran/ast.h> を生成できます。

$ python src/libasr/asdl_cpp.py grammar/AST.asdl src/lfortran/ast.h

まあでも .asdl の方が分かりやすそうです。 AST.asdl から一部抜粋すると、 read/write 文の文法が記載されています。

stmt
    = ..
    | Read(int label, expr? format, argstar* args,
            kw_argstar* kwargs, expr* values, trivia? trivia)
      ..
    | Write(int label, argstar* args, kw_argstar* kwargs, expr* values, trivia? trivia)
      ..

この文法があの read/write には見えません。特に文法中に read とか () の記載がありません。

read (input_unit, *) a, b
write (output_unit, '(A)') 'Even'

ということは、 AST の構築時に read() といった構文の情報が失われている可能性があります。 CST (concrete syntax tree) ではなかったか……! ソース文字列に変換するコードを覗いてみましょう。

    void visit_Read(const Read_t &x) {
        std::string r=indent;
        r += print_label(x);
        r += syn(gr::Keyword);
        r += "read"; ! 1
        r += syn();
        if (x.m_format) {
            r += " ";
            this->visit_expr(*x.m_format);
            r.append(s);
        }
        if(x.n_args || x.n_kwargs) {
            r += "(";
            for (size_t i=0; i<x.n_args; i++) {
                if (x.m_args[i].m_value == nullptr) {
                    r += "*";
                } else {
                    this->visit_expr(*x.m_args[i].m_value);
                    r += s;
                }
                if (i < x.n_args-1 || x.n_kwargs > 0) r += ", ";
            }
            for (size_t i=0; i<x.n_kwargs; i++) {
                r += x.m_kwargs[i].m_arg;
                r += "=";
                if (x.m_kwargs[i].m_value == nullptr) {
                    r += "*";
                } else {
                    this->visit_expr(*x.m_kwargs[i].m_value);
                    r += s;
                }
                if (i < x.n_kwargs-1) r += ", ";
            }
            r += ")";
        } else if(!x.m_format) {
            r += " *,";
        }
        if (x.n_values > 0) {
            if (x.m_format) {
                r += ",";
            }
            r += " ";
            for (size_t i=0; i<x.n_values; i++) {
                this->visit_expr(*x.m_values[i]);
                r += s;
                if (i < x.n_values-1) r += ", ";
            }
        }
        if(x.m_trivia){
            r += print_trivia_after(*x.m_trivia);
        } else {
            r.append("\n");
        }
        s = r;
    }

というわけで、 AST を見てもあまり文法を一望できませんでした。

まとめ

Fortran 難しい難しい言いながら LFortran の AST を見てみました。今回は空振りでしたが、次に Fortran にアプローチするときは、 LFortran のパーサを見てみようかと思います。さすがにパーサを見れば文法が分かるでしょう。

Fortran Advelnt Calender 19 日目の記事でした〜。明日の記事は、 雨崎 しのぶ さんの『できるだけ全部ビルドするPLplotインストール【Linux編】』です。お楽しみにー