Mercury で Welcome to AtCoder を解いてみた

[Qiita Engineer Festa 2024（キータ・エンジニア・フェスタ 2024） - Qiita](https://qiita.com/official-campaigns/engineer-festa/2024)
において、約1ヶ月で38記事という大量の記事の投稿を要求されることがわかった。
そこで、あまりコストをかけずに記事数を稼ぐ方法を考えた結果、「Welcome to AtCoder を様々な言語で解く」ことを思いついた。
単に解くだけでなく、使用する言語仕様の解説を入れれば、記事として一応成立するだろう。

# Welcome to AtCoder

[PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

Welcome to AtCoder では、以下の形式で整数 $a$, $b$, $c$ および文字列 $s$ が入力として与えられる。

```text
a
b c
s
```

この入力をもとに、与えられた整数の和 $sum = a + b + c$ および文字列 $s$ を、以下の形式で出力することが求められる。

```text
sum s
```

# 今回用いる Mercury の機能

## プログラムの構造

[Mercury 入門 (1) Hello, World! - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/06/012345)

Mercury のプログラムは、以下の構造である。
各行の `%` 以降はコメントを表す。

```
:- module main.

:- interface.

% 外部に公開する述語の宣言

:- implementation.

% 述語の定義や、外部に公開しない述語の宣言

:- end_module main.
```

`module` および `end_module` の後の `main` の部分はモジュール名であり、ファイル名に合わせなければならない。
AtCoder の環境では `main` と書けば動くようである。

## 述語と変数の定義・入出力

[Mercury 入門 (1) Hello, World! - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/06/012345)
[よく使う述語: 入出力編 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/09/222803)
[Mercury サンプル: 入力を大文字に変換する - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/09/224519)

Mercuryにおける「述語」とは、引数の関係を定義することで計算を表現するものである。
述語には**英大文字**から始まる名前を、変数には**英小文字**から始まる名前を用いる。

述語の**宣言**は、以下のように行う。

```
:- pred 名前(引数リスト) is det.
```

最後の `det` は、この述語が入力に対して**必ず1個だけ**の出力を返すことを表す。
他の種類もあるが、ここでは扱わない。

述語の宣言における引数は、`型::使用方法` の形式で表す。
型は、整数 `int`、文字列 `string`、入出力の状態 `io` などがある。
使用方法は、入力 `in`、出力 `out` などがある。

述語の**定義**は、以下のように行う。

```
名前(引数リスト) :- 定義内容.
```

述語の定義における引数は、その引数に対応する変数名を書く。
定義内容は、この述語の引数の関係を表す述語などを書く。
定義内容を複数の述語などで表す場合は、コンマで区切る。

入出力は、以下のように行うことができる。

```
:- module main.

:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.

main(IO1, IO3) :-
  read_line_as_string(Line_raw, IO1, IO2),
  (
    Line_raw = ok(Line),
    write_string(Line, IO2, IO3)
  ;
    Line_raw = error(Err),
    IO3 = IO2
  ;
    Line_raw = eof,
    IO3 = IO2
  ).

:- end_module main.
```

述語 `main` は、第1引数の入出力の状態から、第2引数の入出力の状態への変化を定義する。

述語 `read_line_as_string` により、標準入力から1行読み込むことができる。
引数は、左から順に、読み込んだ結果、読み込む前の入出力の状態、読み込んだ後の入出力の状態である。
読み込んだ結果からは、パターンマッチを用いて文字列 (読み込んだ行) を取り出すことができる。
この例では、`Line_raw = ok(Line),` の後 `;` の前の部分が読み込みに成功した場合の処理である。
読み込んだ行を表す文字列は変数 `Line` に割り当てられる。この文字列は (読み込んだ場合) 改行文字を**含む**。

述語 `write_string` により、標準出力に文字列を出力することができる。
引数は、左から順に、出力する文字列、出力前の入出力の状態、出力後の入出力の状態である。

まとめると、この例は標準入力から1行読み込むことを試み、読み込めた場合はそれを出力し、読み込めなかった場合は何もしないプログラムである。

## semidet の述語の扱い

[Mercury 入門 (2) 式の計算と独自の述語の定義 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/06/030848)
[Mercury 入門 (6) 準決定的な述語と非決定的な述語 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/06/075429)

標準ライブラリで定義されている述語の中には、`semidet` のものがある。
この種類の述語は、入力に対して成功すれば**1個だけ**の出力を返すが、**成功しない可能性**がある。

このような述語を用いる際は、以下のように **if then else ゴール**を用いると、述語が成功したかどうかで処理を分け、`if` の中で用いる述語が失敗しても定義中の述語が失敗しないようにできる。

```
(
  if 失敗する可能性のある述語
  then 成功した場合の処理
  else 失敗した場合の処理
)
```

## 数値の処理

[Mercury 入門 (2) 式の計算と独自の述語の定義 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/06/030848)

```
:- import_module int.
```

を書くことで、`int` モジュールをインポートし、数値の演算ができるようになる。

```
A + B
```

により、数値 `A` と `B` の加算ができる。

## 文字列の処理

[よく使う述語: 文字列編 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/10/051554)

```
:- import_module string.
```

を書くことで、`string` モジュールをインポートし、文字列の処理ができるようになる。

```
A ++ B
```

により、文字列 `A` と `B` を結合した文字列を求めることができる。

```
strip(A)
```

により、文字列 `A` の最初および最後に連続する空白文字や改行文字を取り除いた文字列を得ることができる。

```
words(A)
```

により、文字列 `A` を空白で分割した文字列のリストを得ることができる。

```
from_int(A)
```

により、数値 `A` を文字列に変換した文字列を得ることができる。

```
to_int(A, B)
```

により、文字列 `A` を数値に変換した結果を `B` に割り当てることができる。
**この述語は semidet である。**

## リストの処理

[よく使う述語: リスト編 - Mercury 勉強メモ](https://mercury-memo.hatenablog.com/entry/2013/07/11/185917)

```
:- import_module list.
```

を書くことで、`list` モジュールをインポートし、リストの処理ができるようになる。

```
index0(A, B, C)
```

により、リスト `A` の `B` 番目 (0-origin) の要素を `C` に割り当てることができる。
**この述語は semidet である。**

# 提出コード

今回はエラー処理を省略するため、(入力を含む) 失敗する可能性のある述語についてラッパーの述語を定義し、もとの述語が失敗した場合は適当な値を返すようにすることで必ず成功するようにしている。

```
:- module main.

:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module string.
:- import_module list.
:- import_module int.

:-pred read_line_as_string_det(string::out, io::di, io::uo) is det.
read_line_as_string_det(Res, IO1, IO2) :-
  read_line_as_string(Read, IO1, IO2),
  (
    Read = ok(Str),
    Res = Str
  ;
    Read = error(Err),
    Res = ""
  ;
    Read = eof,
    Res = ""
  ).

:-pred to_int_det(string::in, int::out) is det.
to_int_det(Str, Int) :-
  (
    if to_int(Str, Int_imm)
    then Int = Int_imm
    else Int = 0
  ).

:-pred index0_det(list(T)::in, int::in, T::in, T::out) is det.
index0_det(List, Idx, Default, Item) :-
  (
    if index0(List, Idx, Item_imm)
    then Item = Item_imm
    else Item = Default
  ).

main(IO1, IO5) :-
  read_line_as_string_det(Line1, IO1, IO2),
  read_line_as_string_det(Line2, IO2, IO3),
  read_line_as_string_det(Line3, IO3, IO4),
  A_str = strip(Line1),
  BC = words(strip(Line2)),
  index0_det(BC, 0, "", B_str),
  index0_det(BC, 1, "", C_str),
  to_int_det(A_str, A),
  to_int_det(B_str, B),
  to_int_det(C_str, C),
  Sum = A + B + C,
  write_string(from_int(Sum) ++ " " ++ Line3, IO4, IO5).

:- end_module main.
```

[提出 #55158749 - AtCoder Beginners Selection](https://atcoder.jp/contests/abs/submissions/55158749)