Fortran Advent Calendar 2024 の
19 日目の記事です。 Fortran 入門の三歩手前ぐらいの内容です。
背景
モチベーション
競プロ界で Fortran を代表する人類に
MrTired
がいます。彼/彼女の提出プログラムの頭には『ランダムウォーク猿』を始めとした乱数キャラが登場したり、さり気なく Discord
で僕の懸念事項を質問してくれたりと、ユーモラスで素敵な方です。好きだ!
すべての言語を使いこなしたい、という意味で僕は Fortran に興味がありますが、ちゃんと MrTired
のコードを読んだり、もう少しお近づきしたいなー、という気持ちもあって、普通の人よりはモチベーションが高いと思います。
やったみたこと
興味はありつつも、 Fortran 入門には二の足を踏んでいます。
みんなの Fortran
を購入して読んでいなかったり、 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
-
1: 特殊な構文に見えてしまうが、
#pragma
的な一律の記法であって欲しい?
変数の宣言もなんだかピンと来ません。
-
2
どうして
::
なんだろう
標準入出力も構文が謎です。関数呼び出しの普遍的な構文ではなく、 read/write
文の専用構文が用意されている雰囲気があります。どうして関数呼び出しじゃないんだ! と悩んでいる間は集中力
50% 減です。これが慣れない言語の難しさですね。
そして文字列が出てくるとノックアウトされます。
-
5
len
の出自が不明…… (コンストラクタの構文だったりする?)
Fortran の revision によって関数や変数の型が変わるっぽいのも難しい。 Fortran 2018 では
mod
関数の 2 引数の type と
kind がマッチする必要があります。たぶん。要は型が一致する必要があります。
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;
}
-
1
ああー、
read( .. )
を復元している……!
というわけで、 AST を見てもあまり文法を一望できませんでした。
まとめ
Fortran 難しい難しい言いながら LFortran の AST を見てみました。今回は空振りでしたが、次に
Fortran にアプローチするときは、 LFortran
のパーサを見てみようかと思います。さすがにパーサを見れば文法が分かるでしょう。
Fortran Advelnt Calender 19 日目の記事でした〜。明日の記事は、
雨崎 しのぶ
さんの『できるだけ全部ビルドするPLplotインストール【Linux編】』です。お楽しみにー