Carp で 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
```

# 今回用いた Carp の機能

## 言語機能

[Carp/docs/LanguageGuide.md at master · carp-lang/Carp · GitHub](https://github.com/carp-lang/Carp/blob/master/docs/LanguageGuide.md)

### 変数定義 def

```
(def 変数名 値)
```

変数「変数名」に「値」を割り当てる。

### 関数定義 defn

```
(defn 関数名 [引数名リスト] 処理内容)
```

関数を定義する。

### ローカル変数定義 let

```
(let [変数名1 値1 …] 処理内容)
```

リスト内の変数名に対応する値を割り当て、「処理内容」を実行する。

### 条件分岐 if

```
(if 条件式 条件式が真の場合の処理内容 条件式が偽の場合の処理内容)
```

条件分岐を行う。

### 連続条件分岐 cond

```
(cond
  条件式1 条件式1が真の場合の処理内容
  条件式2 条件式1が偽で、条件式2が真の場合処理内容
  …
  これまでの条件式が全て偽の場合の処理内容
)
```

複数の条件式を指定し、指定した中で最初に真になった条件式に対応する処理を行う。
真になった条件式が無かった場合は、最後に指定した処理を行う。

### 配列作成 []

```
[値1 値2 …]
```

配列 (`Array`) を作成する。

### Ref の取得 &

```
&値
```

「値」に対応する `Ref` を取得する。
`Ref` を要求する関数の引数として値を渡す際に用いる。
`(ref 値)` と同じ意味である。

## 標準ライブラリ

標準ライブラリの関数を用いる場合は、`モジュール名.関数名` のように書くとよい。
また、`モジュール名.` は省略できることもあるようである。

### 数値の加算 Int.+

```
(+ 値1 値2)
```

説明が書かれていないが、[`Int.+`](https://carp-lang.github.io/carp-docs/core/Int.html#+) は、2個の値の和を求めるようである。
3個以上の値の和を直接求めることはできないようである。(2個ずつに分けて求める)

### 数値の減算 Int.-

```
(- 値1 値2)
```

説明が書かれていないが、[`Int.-`](https://carp-lang.github.io/carp-docs/core/Int.html#-) は、最初の値から2番目の値を引いた差を求めるようである。

### 数値の乗算 Int.*

```
(* 値1 値2)
```

説明が書かれていないが、[`Int.*`](https://carp-lang.github.io/carp-docs/core/Int.html#*) は、2個の値の積を求めるようである。

### 数値の等価比較 Int.=

```
(= 値1 値2)
```

説明が書かれていないが、[`Int.=`](https://carp-lang.github.io/carp-docs/core/Int.html#=) は、2個の値が等しければ真、そうでなければ偽を返すと推測できる。

### 数値を範囲内にする Int.clamp

```
(clamp 最小値 最大値 値)
```

説明があまり書かれていないが、[`Int.clamp`](https://carp-lang.github.io/carp-docs/core/Int.html#clamp) は、「値」が「最小値」以上「最大値」以下であれば「値」をそのまま返し、そうでなければ「最小値」以上「最大値」以下の何らかの値を返すと推測できる。

今回は、結果が元の値と同じかどうかで値が範囲内かを判定するために用いた。

### 数値を文字列に変換する Int.prn

```
(prn 値)
```

説明が書かれていないが、[`Int.prn`](https://carp-lang.github.io/carp-docs/core/Int.html#prn) を用いると、「値」を十進数で表現した文字列に変換できるようである。

### 文字を生成する Char.from-int

```
(Char.from-int 値)
```

説明が書かれていないが、[`Char.from-int`](https://carp-lang.github.io/carp-docs/core/Char.html#from-int) を用いると、「値」からその値を文字コードとする文字 (`Char`) を生成できるようである。

### 配列への要素の追加 Array.push-back

```
(Array.push-back 配列 値)
```

[`Array.push-back`](https://carp-lang.github.io/carp-docs/core/Array.html#push-back) を用いると、「配列」の末尾に「値」を要素として追加した新しい配列を得ることができる。

### 文字列の生成 String.from-chars

```
(String.from-chars 配列)
```

説明が書かれていないが、[`String.from-chars`](https://carp-lang.github.io/carp-docs/core/String.html#from-chars) を用いると、文字 (`Char`) の「配列」からそれらの文字を内容とする文字列 (`String`) を得ることができるようである。
なお、この「配列」は `Ref` を要求している。

### 文字列の結合 String.concat

```
(String.concat 配列)
```

[`String.concat`](https://carp-lang.github.io/carp-docs/core/String.html#concat) を用いると、文字列の「配列」から、それらの文字列を結合した新しい文字列を得ることができる。
なお、この「配列」は `Ref` を要求している。

### 文字の入力 IO.Raw.get-char

```
(IO.Raw.get-char)
```

[`IO.Raw.get-char`](https://carp-lang.github.io/carp-docs/core/IO.Raw.html#get-char) を用いると、標準入力から1文字読み込み、その結果を整数として取得できる。
C言語の `getchar()` に相当する。

### 文字列の出力 IO.println

```
(IO.println 文字列)
```

[`IO.println`](https://carp-lang.github.io/carp-docs/core/IO.html#println) を用いると、文字列を標準出力に出力し、続いて改行を出力できる。
なお、この「文字列」は `Ref` を要求している。

# 提出コード

ほとんどの処理を行う関数 `process` を定義している。
この関数は、読み込んでいる入力のそれぞれの値 `a, b, c, s` およびどの入力を読み込んでいるかを表す値 `status` を引数としてとる。

`cond` を用いて `status` の値により分岐し、それぞれの値を読み込む。
有効な文字を読み込んだ場合は値を更新し、そうでない場合 (空白や改行を読み込んだ場合) は次の値を読み込む処理に進む。
いずれの場合でも、再帰により次の文字の処理に進む。

最後に、文字列 `s` の読み込みが完了したら、読み込んだ数値 `a, b, c` の和と文字列 `s` をそれぞれ文字列として配列で返す。
なお、文字列 `s` の最初に初期値として区切り用の空白を入れてあるので、空白はあらためて追加しない。

`process` の呼び出し元では、返された文字列の配列を結合し、出力する。

```
(defn process [a b c s status]
  (let [ch (IO.Raw.get-char)]
    (cond
      (= status 1)
        (if (= ch (clamp 48 57 ch))
          (process (+ (* a 10) (- ch 48)) b c s 1)
          (process a b c s 2)
        )
      (= status 2)
        (if (= ch (clamp 48 57 ch))
          (process a (+ (* b 10) (- ch 48)) c s 2)
          (process a b c s 3)
        )
      (= status 3)
        (if (= ch (clamp 48 57 ch))
          (process a b (+ (* c 10) (- ch 48)) s 3)
          (process a b c s 4)
        )
      (if (= ch 10)
        [(Int.prn (+ (+ a b) c)) (String.from-chars &s)]
        (process a b c (Array.push-back s (Char.from-int ch)) 4)
      )
    )
  )
)

(def result (process 0 0 0 [(Char.from-int 32)] 1))
(def result2 (String.concat &result))
(IO.println &result2)
```

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

# うまくいかなかった方法

最初は、以下のように、それぞれの整数や文字列を別々の関数で読み込む方法を試みた。
(検証用に、読み込んだデータをそのまま出力するようにしてある)

```
(defn read-int [acc]
  (let [ch (IO.Raw.get-char)]
    (if (= ch (clamp 48 57 ch))
      (read-int (+ (* acc 10) (- ch 48)))
      acc
    )
  )
)
(defn read-chars [acc]
  (let [ch (IO.Raw.get-char)]
    (if (= ch 10)
      (String.from-chars &acc)
      (read-chars (Array.push-back acc (Char.from-int ch)))
    )
  )
)

(def a (read-int 0))
(def b (read-int 0))
(def c (read-int 0))
(def s (read-chars []))

(def strs [(prn a) (prn b) (prn c) (prn s)])
(def data (String.join-with-char (Char.from-int 32) &strs))
(IO.println &data)
```

このプログラムに入力例2

```text
72
128 256
myonmyon
```

を与えると、以下の出力が得られた。

```text
0 256 128 @"72"
```

なぜか、`def` を書いたのとは**逆順で値が読み込まれている**ようである。

さらに、このプログラムの入力を読み込む部分を、以下のように逆順にして同様に実行してみた。

```
(def s (read-chars []))
(def c (read-int 0))
(def b (read-int 0))
(def a (read-int 0))
```

すると驚くべきことに、**順番を変えたにもかかわらず、以下の同様の出力が得られた。**

```text
0 256 128 @"72"
```

次に、関数の順番を戻し、変数名を逆順にしてみた。

```
(def s (read-int 0))
(def c (read-int 0))
(def b (read-int 0))
(def a (read-chars []))

(def strs [(prn s) (prn c) (prn b) (prn a)])
```

すると、以下の出力が得られた。

```text
128 256 0 @"72"
```

整数の順番は改善がみられるが、**最後に書いた文字列の読み込みがなぜか最初に行われてしまっている**のは改善しない。
さらに `read-char` の名前を `read-int` より辞書順で後ろになる `read-z` に変えてみても、出力は同様になり、改善はみられなかった。

別のアプローチとして、`let` を用いた読み込み順の指定を試みた。

```
(defn read-int [acc]
  (let [ch (IO.Raw.get-char)]
    (if (= ch (clamp 48 57 ch))
      (read-int (+ (* acc 10) (- ch 48)))
      acc
    )
  )
)
(defn read-chars [acc]
  (let [ch (IO.Raw.get-char)]
    (if (= ch 10)
      (String.from-chars &acc)
      (read-chars (Array.push-back acc (Char.from-int ch)))
    )
  )
)

(let [a (read-int 0)]
   (let [b (read-int 0)]
      (let [c (read-int 0)]
         (let [s (read-chars [])]
           (let [strs [(prn a) (prn b) (prn c) (prn s)]]
             (let [data (String.join-with-char (Char.from-int 32) &strs)]
               (IO.println &data)
             )
           )
         )
      )
   )
)
```

すると、以下のエラーが出て、そもそも実行ができなかった。

```text
bash: line 2:     2 Killed                  carp -b --optimize Main.carp 1>&2
```

これらを踏まえ、1回の関数呼び出しで1個の項目だけを読み込むのは諦め、1回の関数呼び出しでほとんどの処理を行う今回の提出コードの方式を採用した。

## この章でのみ用いた機能

この章では、提出コードでは用いなかった以下の機能を用いた。

### String.prn

[`String.prn`](https://carp-lang.github.io/carp-docs/core/String.html#prn) は、説明が書かれていないが、文字列を受け取り、それに沿った内容の文字列を返すようである。
文字列について `prn` を用いずに直接

```
(def strs [(prn a) (prn b) (prn c) s])
```

のように渡してしまうと、以下のエラーになった。

```text
You’re using a given-away value `[(Int.prn a) (Int.prn b) (Int.prn c) s]` at line 23, column 11 in '/judge/Main.carp'.

You’ll have to copy the value using `@`. at /judge/Main.carp:23:2.

Traceback:
  (def strs [(prn a) (prn b) (prn c) s]) at /judge/Main.carp:23:1.
```

エラーメッセージには「`@` を使え」的なことが書かれているようだが、

```
(def strs [(prn a) (prn b) (prn c) @s])
```

とすると、今度は以下のエラーになった。

```text
I can’t match the types `String` and `(Ref d r9)`. within `(copy s)`

  s : String
  At line 23, column 37 in '/judge/Main.carp'

  Expected first argument to 'copy' : (Ref d r9)
  At line 23, column 36 in '/judge/Main.carp' at /judge/Main.carp:23:2.

Traceback:
  (def strs [(prn a) (prn b) (prn c) (copy s)]) at /judge/Main.carp:23:1.
```

そこで `prn` を用いてこのエラーを回避した。

とはいえ、よく見ると `Ref` 関係のエラーなので、これまでのパターンに従って `&` を加えてもエラーを回避できるようである。

```
(def strs [(prn a) (prn b) (prn c) @&s])
```

このときの出力は以下のようになった。

```text
0 256 128 72
```

`prn` を用いたときは `@"72"` だった部分が、`@&` を用いたときは `72` になっている。

### String.join-with-char

[`String.join-with-char`](https://carp-lang.github.io/carp-docs/core/String.html#join-with-char) を用いると、文字列 (`String`) の配列 (`Array`) の各要素を、間に指定の文字 (`Char`) を挟んで結合した文字列を得ることができる。
第1引数に間に挟む文字を、第2引数に結合する配列をとる。
第2引数は `Ref` がついているので、値に `&` をつけて渡すとよいようだ。