地元の図書館で、古い本のコレクションの整理を手伝う仕事に就きました。学生の利用者は、学期末レポートに引用したい、うろ覚えの一節を探していることがよくあります。1冊ずつ最初から最後まで手作業で読む代わりに、それらの本をスキャンして、そうしたうろ覚えの一節を探す小さなツールを作ることにしました。
ファイルから検索文字列に一致する行を探し、一致したすべての行を返します。
Unixのgrepコマンドは、正規表現に一致する行をファイルから探します。
ここでの課題は、固定文字列の検索に対応した簡略版のgrepコマンドを実装することです。
grepコマンドは3つの引数を取ります。
次に、指定されたファイルの内容を(指定された順に)読み込み、検索文字列を含む行を探し、最後に見つかった順にそれらの行を返します。
複数のファイルを検索する場合、一致した各行の先頭にはファイル名とコロン(:)が付きます。
grepコマンドは次のフラグに対応しています。
-n 出力の各行の先頭に行番号とコロン(:)を付けます。行番号はファイル名の後ろに置きます(ファイル名がある場合)。-l 一致する行を1つ以上含むファイルの名前だけを出力します。-i 大文字と小文字を区別せずに比較して照合します。-v プログラムの動作を反転させ、一致しなかったすべての行を集めます。-x 検索文字列が行全体と一致する行だけを探します。ほかの演習とは異なり、grepは「外界」とやり取りする必要があります。つまり、ファイルの読み込み、システムエラーの処理、stdoutへの出力です。
Leanでは、システムとのやり取りは通常IOモナドの中で行います。モナドを使うと、副作用を閉じ込めた環境の中で発生させ、ほかのほとんどの関数の純粋な状態に影響を与えずに済みます。
Leanでは、ファイルをスクリプトとして呼び出せます。lean --runで単体のスクリプトとして直接実行する方法と、lakeを使う方法があります。
Leanのプロジェクトにはエントリーポイントが1つだけあり、mainという関数です。この関数は、渡されたすべての引数を含むList Stringを受け取ります。
この演習では、Grep.leanを実行ファイルとして実行する状況を再現するために、同じ方法で引数が渡されます。
Leanには、IO.FSモナドでファイルを操作するための関数が用意されています。
たとえば、IO.FS.readFileを使うと、指定したパスにあるファイルの内容をStringとして読み込めます。
この演習では、値を返す代わりに、結果を標準出力または標準エラーに書き出す必要があります。 そのための関数がLeanには用意されています。
Exceptのエラーとは異なり、IOの例外は実際には実行時の例外です。
これらを処理するには、try/catchを使います。
try
/-
code that may possibly raise an exception
-/
catch msg =>
/-
code that is executed only if an exception was thrown
-/